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:
@@ -1,2 +1,3 @@
|
|||||||
from . import models
|
from . import models
|
||||||
from . import commons
|
from . import commons
|
||||||
|
from . import controllers
|
||||||
|
|||||||
1
sf_base/controllers/__init__.py
Normal file
1
sf_base/controllers/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from . import controllers
|
||||||
40
sf_base/controllers/controllers.py
Normal file
40
sf_base/controllers/controllers.py
Normal 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)
|
||||||
@@ -256,18 +256,21 @@ class ToolGroups(models.Model):
|
|||||||
|
|
||||||
# ==========机床刀具组接口==========
|
# ==========机床刀具组接口==========
|
||||||
def _register_tool_groups(self, obj):
|
def _register_tool_groups(self, obj):
|
||||||
create_url = '/AutoDeviceApi/FeedBackOut'
|
create_url = '/AutoDeviceApi/MaintenanceToolGroups'
|
||||||
sf_sync_config = self.env['res.config.settings'].get_values()
|
sf_sync_config = self.env['res.config.settings'].get_values()
|
||||||
token = sf_sync_config['token']
|
token = sf_sync_config['token']
|
||||||
sf_secret_key = sf_sync_config['sf_secret_key']
|
sf_secret_key = sf_sync_config['sf_secret_key']
|
||||||
headers = Common.get_headers(obj, token, sf_secret_key)
|
headers = Common.get_headers(obj, token, sf_secret_key)
|
||||||
strurl = sf_sync_config['sf_url'] + create_url
|
strurl = sf_sync_config['sf_url'] + create_url
|
||||||
device_id = ''
|
device_id = ''
|
||||||
for equipment_id in obj.equipment_ids:
|
name = None
|
||||||
device_id = '%s,%s' % (device_id, equipment_id.name)
|
if obj:
|
||||||
|
for equipment_id in obj.equipment_ids:
|
||||||
|
device_id = '%s,%s' % (device_id, equipment_id.name)
|
||||||
|
name = obj.name
|
||||||
val = {
|
val = {
|
||||||
'DeviceId': device_id,
|
'DeviceId': device_id,
|
||||||
'GroupName': obj.name,
|
'GroupName': name,
|
||||||
}
|
}
|
||||||
kw = json.dumps(val, ensure_ascii=False)
|
kw = json.dumps(val, ensure_ascii=False)
|
||||||
r = requests.post(strurl, json={}, data={'kw': kw, 'token': token}, headers=headers)
|
r = requests.post(strurl, json={}, data={'kw': kw, 'token': token}, headers=headers)
|
||||||
@@ -279,7 +282,7 @@ class ToolGroups(models.Model):
|
|||||||
|
|
||||||
# def write(self, vals):
|
# def write(self, vals):
|
||||||
# obj = super().write(vals)
|
# obj = super().write(vals)
|
||||||
# self._register_tool_groups(obj)
|
# self._register_tool_groups(self)
|
||||||
# return obj
|
# return obj
|
||||||
#
|
#
|
||||||
# @api.model_create_multi
|
# @api.model_create_multi
|
||||||
|
|||||||
@@ -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,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_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,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_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,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
|
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_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_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
|
||||||
|
@@ -33,7 +33,8 @@ class Sf_Bf_Connect(http.Controller):
|
|||||||
aa = request.env['sale.order'].sudo().search([('name', '=', order_id.name)])
|
aa = request.env['sale.order'].sudo().search([('name', '=', order_id.name)])
|
||||||
logging.info('get_bfm_process_or===================================:%s' % order_id.name)
|
logging.info('get_bfm_process_or===================================:%s' % order_id.name)
|
||||||
aa.default_code = kw['order_number']
|
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)
|
logging.info('get_bfm_process_order_listaaaaaaaaaaaaaaaaaaaaaaaaaaaa================:%s' % aa.default_code)
|
||||||
for item in bfm_process_order_list:
|
for item in bfm_process_order_list:
|
||||||
product = request.env['product.template'].sudo().product_create(product_id, item, order_id,
|
product = request.env['product.template'].sudo().product_create(product_id, item, order_id,
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ class Http(models.AbstractModel):
|
|||||||
def _auth_method_sf_token(cls):
|
def _auth_method_sf_token(cls):
|
||||||
# 从headers.environ中获取对方传过来的token,timestamp,加密的校验字符串
|
# 从headers.environ中获取对方传过来的token,timestamp,加密的校验字符串
|
||||||
datas = request.httprequest.headers.environ
|
datas = request.httprequest.headers.environ
|
||||||
|
logging.info(datas)
|
||||||
if 'HTTP_TOKEN' in datas:
|
if 'HTTP_TOKEN' in datas:
|
||||||
_logger.info('token:%s' % datas['HTTP_TOKEN'])
|
_logger.info('token:%s' % datas['HTTP_TOKEN'])
|
||||||
# 查询密钥
|
# 查询密钥
|
||||||
@@ -40,6 +41,7 @@ class Http(models.AbstractModel):
|
|||||||
raise AuthenticationError('请求已过期')
|
raise AuthenticationError('请求已过期')
|
||||||
check_str = '%s%s%s' % (datas['HTTP_TOKEN'], post_time, factory_secret.sf_secret_key)
|
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()
|
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']:
|
if check_sf_str != datas['HTTP_CHECKSTR']:
|
||||||
raise AuthenticationError('数据校验不通过')
|
raise AuthenticationError('数据校验不通过')
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ class JdEclp(models.Model):
|
|||||||
res = super(JdEclp, self).button_validate()
|
res = super(JdEclp, self).button_validate()
|
||||||
if self.check_out == 'OUT':
|
if self.check_out == 'OUT':
|
||||||
if self.logistics_way != '自提':
|
if self.logistics_way != '自提':
|
||||||
if self.logistics_status != '3':
|
if self.logistics_status != '2':
|
||||||
raise ValidationError('非自提订单,必须先下物流单,并获取物流面单后才可出库!')
|
raise ValidationError('非自提订单,必须先下物流单,并获取物流面单后才可出库!')
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|||||||
@@ -8,14 +8,14 @@
|
|||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<xpath expr="//div[hasclass('app_settings_block')]/div" position="before">
|
<xpath expr="//div[hasclass('app_settings_block')]/div" position="before">
|
||||||
<div>
|
<div>
|
||||||
<h2>bfm环境配置</h2>
|
<h2>业务平台参数配置</h2>
|
||||||
<div class="row mt16 o_settings_container" id="jd_api">
|
<div class="row mt16 o_settings_container" id="jd_api">
|
||||||
<div class="col-12 col-lg-6 o_setting_box">
|
<div class="col-12 col-lg-6 o_setting_box">
|
||||||
<div class="o_setting_left_pane"/>
|
<div class="o_setting_left_pane"/>
|
||||||
<div class="o_setting_right_pane">
|
<div class="o_setting_right_pane">
|
||||||
<div class="text-muted">
|
<div class="text-muted">
|
||||||
<label for="bfm_url"/>
|
<label for="bfm_url" />
|
||||||
<field name="bfm_url"/>
|
<field name="bfm_url" string="访问地址"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- </div> -->
|
<!-- </div> -->
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
'security/ir.model.access.csv',
|
'security/ir.model.access.csv',
|
||||||
'wizard/workpiece_delivery_views.xml',
|
'wizard/workpiece_delivery_views.xml',
|
||||||
'views/mrp_views_menus.xml',
|
'views/mrp_views_menus.xml',
|
||||||
|
'views/stock_lot_views.xml',
|
||||||
'views/mrp_production_addional_change.xml',
|
'views/mrp_production_addional_change.xml',
|
||||||
'views/mrp_routing_workcenter_view.xml',
|
'views/mrp_routing_workcenter_view.xml',
|
||||||
'views/production_line_view.xml',
|
'views/production_line_view.xml',
|
||||||
|
|||||||
@@ -20,9 +20,9 @@ class Manufacturing_Connect(http.Controller):
|
|||||||
res = {'Succeed': True, 'Datas': []}
|
res = {'Succeed': True, 'Datas': []}
|
||||||
datas = request.httprequest.data
|
datas = request.httprequest.data
|
||||||
ret = json.loads(datas)
|
ret = json.loads(datas)
|
||||||
logging.info('RfidCode:%s' % ret)
|
logging.info('RfidCode:%s' % ret['RfidCode'])
|
||||||
workorder = request.env['mrp.workorder'].sudo().search(
|
workorder = request.env['mrp.workorder'].sudo().search(
|
||||||
[('production_id.name', '=', 'WH/MO/00071'), ('routing_type', '=', '装夹')])
|
[('rfid_code', '=', ret['RfidCode']), ('routing_type', '=', '装夹预调')])
|
||||||
if workorder:
|
if workorder:
|
||||||
for item in workorder:
|
for item in workorder:
|
||||||
res['Datas'].append({
|
res['Datas'].append({
|
||||||
|
|||||||
@@ -133,6 +133,7 @@ class ResMrpWorkOrder(models.Model):
|
|||||||
preset_program_information = fields.Char(string="预调程序信息")
|
preset_program_information = fields.Char(string="预调程序信息")
|
||||||
workpiece_delivery_ids = fields.One2many('sf.workpiece.delivery', 'workorder_id', '工件配送')
|
workpiece_delivery_ids = fields.One2many('sf.workpiece.delivery', 'workorder_id', '工件配送')
|
||||||
is_delivery = fields.Boolean('是否配送完成', default=False)
|
is_delivery = fields.Boolean('是否配送完成', default=False)
|
||||||
|
rfid_code = fields.Char('RFID')
|
||||||
|
|
||||||
@api.onchange('is_ok')
|
@api.onchange('is_ok')
|
||||||
def _onchange_inspection_user_id(self):
|
def _onchange_inspection_user_id(self):
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import base64
|
import base64
|
||||||
|
import qrcode
|
||||||
from collections import defaultdict, namedtuple
|
from collections import defaultdict, namedtuple
|
||||||
import logging
|
import logging
|
||||||
import json
|
import json
|
||||||
@@ -12,6 +13,7 @@ from odoo.tools import float_compare
|
|||||||
from odoo.addons.stock.models.stock_rule import ProcurementException
|
from odoo.addons.stock.models.stock_rule import ProcurementException
|
||||||
from odoo.addons.sf_base.commons.common import Common
|
from odoo.addons.sf_base.commons.common import Common
|
||||||
from odoo.exceptions import UserError
|
from odoo.exceptions import UserError
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
|
|
||||||
class StockRule(models.Model):
|
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-%s-%03d" % (product.cutting_tool_model_id.code, now, 1)
|
||||||
return "%s-%03d" % (product.name, 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):
|
class StockPicking(models.Model):
|
||||||
_inherit = 'stock.picking'
|
_inherit = 'stock.picking'
|
||||||
|
|||||||
@@ -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_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,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_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,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_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
|
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
|
||||||
|
|||||||
|
@@ -176,6 +176,8 @@
|
|||||||
attrs='{"invisible": [("routing_type","!=","装夹预调")]}'/>
|
attrs='{"invisible": [("routing_type","!=","装夹预调")]}'/>
|
||||||
<field name="functional_fixture_type_id" force_save="1"
|
<field name="functional_fixture_type_id" force_save="1"
|
||||||
attrs='{"invisible": [("routing_type","!=","装夹预调")]}'/>
|
attrs='{"invisible": [("routing_type","!=","装夹预调")]}'/>
|
||||||
|
<field name="rfid_code"
|
||||||
|
attrs='{"invisible": [("routing_type","!=","装夹预调")]}'/>
|
||||||
</group>
|
</group>
|
||||||
<group attrs='{"invisible": [("routing_type","=","获取CNC加工程序")]}'>
|
<group attrs='{"invisible": [("routing_type","=","获取CNC加工程序")]}'>
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
18
sf_manufacturing/views/stock_lot_views.xml
Normal file
18
sf_manufacturing/views/stock_lot_views.xml
Normal 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>
|
||||||
@@ -13,8 +13,8 @@ class ResConfigSettings(models.TransientModel):
|
|||||||
token = fields.Char(string='TOKEN', default='b811ac06-3f00-11ed-9aed-0242ac110003')
|
token = fields.Char(string='TOKEN', default='b811ac06-3f00-11ed-9aed-0242ac110003')
|
||||||
sf_secret_key = fields.Char(string='密钥', default='wBmxej38OkErKhD6')
|
sf_secret_key = fields.Char(string='密钥', default='wBmxej38OkErKhD6')
|
||||||
sf_url = fields.Char(string='访问地址', default='https://sf.cs.jikimo.com')
|
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')
|
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_host = fields.Char(string='FTP的ip')
|
||||||
ftp_port = fields.Char(string='FTP端口')
|
ftp_port = fields.Char(string='FTP端口')
|
||||||
ftp_user = fields.Char(string='FTP用户')
|
ftp_user = fields.Char(string='FTP用户')
|
||||||
|
|||||||
@@ -8,8 +8,8 @@
|
|||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<xpath expr="//div[hasclass('app_settings_block')]/div" position="before">
|
<xpath expr="//div[hasclass('app_settings_block')]/div" position="before">
|
||||||
<div>
|
<div>
|
||||||
<h2>同步参数配置</h2>
|
<h2>云平台参数配置</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="col-12 col-lg-6 o_setting_box">
|
||||||
<div class="o_setting_left_pane"/>
|
<div class="o_setting_left_pane"/>
|
||||||
<div class="o_setting_right_pane">
|
<div class="o_setting_right_pane">
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2>FTP参数配置</h2>
|
<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="col-12 col-lg-6 o_setting_box">
|
||||||
<div class="o_setting_left_pane"/>
|
<div class="o_setting_left_pane"/>
|
||||||
<div class="o_setting_right_pane">
|
<div class="o_setting_right_pane">
|
||||||
@@ -61,14 +61,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2>业务平台参数配置</h2>
|
<h2>特征识别参数配置</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="col-12 col-lg-6 o_setting_box">
|
||||||
<div class="o_setting_left_pane"/>
|
<div class="o_setting_left_pane"/>
|
||||||
<div class="o_setting_right_pane">
|
<div class="o_setting_right_pane">
|
||||||
<div class="text-muted">
|
<div class="text-muted">
|
||||||
<label for="bfm_url" string="访问地址"/>
|
<label for="model_parser_url" string="访问地址"/>
|
||||||
<field name="bfm_url"/>
|
<field name="model_parser_url"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
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,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
|
access_sf_action_plan_all_wizard,sf.action.plan.all.wizard,model_sf_action_plan_all_wizard,base.group_user,1,1,1,1
|
||||||
|
@@ -11,7 +11,7 @@ class QualityCheck(models.Model):
|
|||||||
|
|
||||||
# ==========零件特采接口==========
|
# ==========零件特采接口==========
|
||||||
def _register_tool_groups(self):
|
def _register_tool_groups(self):
|
||||||
create_url = '/AutoDeviceApi/FeedBackOut'
|
create_url = '/AutoDeviceApi/ModSpecial'
|
||||||
sf_sync_config = self.env['res.config.settings'].get_values()
|
sf_sync_config = self.env['res.config.settings'].get_values()
|
||||||
token = sf_sync_config['token']
|
token = sf_sync_config['token']
|
||||||
sf_secret_key = sf_sync_config['sf_secret_key']
|
sf_secret_key = sf_sync_config['sf_secret_key']
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
from . import sale_order
|
from . import sale_order
|
||||||
from . import quick_easy_order
|
from . import quick_easy_order
|
||||||
from . import auto_quatotion_common
|
from . import auto_quatotion_common
|
||||||
|
from . import parser_and_calculate_work_time
|
||||||
|
from . import preload_datas_functions
|
||||||
|
|
||||||
|
|||||||
472
sf_sale/models/parser_and_calculate_work_time.py
Normal file
472
sf_sale/models/parser_and_calculate_work_time.py
Normal 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)
|
||||||
201
sf_sale/models/preload_datas_functions.py
Normal file
201
sf_sale/models/preload_datas_functions.py
Normal 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)
|
||||||
@@ -2,17 +2,19 @@ import logging
|
|||||||
import base64
|
import base64
|
||||||
import hashlib
|
import hashlib
|
||||||
import os
|
import os
|
||||||
|
import platform
|
||||||
import json
|
import json
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import requests
|
import requests
|
||||||
from OCC.Extend.DataExchange import read_step_file
|
from odoo import http
|
||||||
from OCC.Extend.DataExchange import write_stl_file
|
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 import models, fields, api
|
||||||
from odoo.modules import get_resource_path
|
from odoo.modules import get_resource_path
|
||||||
from odoo.exceptions import ValidationError, UserError
|
from odoo.exceptions import ValidationError, UserError
|
||||||
from odoo.addons.sf_base.commons.common import Common
|
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.03', '±0.03mm'),
|
||||||
('0.02', '±0.02mm'),
|
('0.02', '±0.02mm'),
|
||||||
('0.01', '±0.01mm')], string='加工精度', default='0.10')
|
('0.01', '±0.01mm')], string='加工精度', default='0.10')
|
||||||
material_id = fields.Many2one('sf.production.materials', '材料', compute='_compute_material_model', store=True)
|
material_id = fields.Many2one('sf.production.materials', '材料')
|
||||||
material_model_id = fields.Many2one('sf.materials.model', '型号', compute='_compute_material_model', store=True)
|
material_model_id = fields.Many2one('sf.materials.model', '型号')
|
||||||
# process_id = fields.Many2one('sf.production.process', string='表面工艺')
|
# process_id = fields.Many2one('sf.production.process', string='表面工艺')
|
||||||
parameter_ids = fields.Many2many('sf.production.process.parameter', 'process_item_order_rel', string='可选参数')
|
parameter_ids = fields.Many2many('sf.production.process.parameter', 'process_item_order_rel', string='可选参数')
|
||||||
quantity = fields.Integer('数量', default=1)
|
quantity = fields.Integer('数量', default=1)
|
||||||
@@ -77,11 +79,11 @@ class QuickEasyOrder(models.Model):
|
|||||||
if len(item[2]) > 0:
|
if len(item[2]) > 0:
|
||||||
logging.info('create-attachment:%s' % int(item[2][0]))
|
logging.info('create-attachment:%s' % int(item[2][0]))
|
||||||
attachment = self.env['ir.attachment'].sudo().search([('id', '=', int(item[2][0]))])
|
attachment = self.env['ir.attachment'].sudo().search([('id', '=', int(item[2][0]))])
|
||||||
base64_data = base64.b64encode(attachment.datas)
|
# base64_data = base64.b64encode(attachment.datas)
|
||||||
base64_datas = base64_data.decode('utf-8')
|
# base64_datas = base64_data.decode('utf-8')
|
||||||
model_code = hashlib.sha1(base64_datas.encode('utf-8')).hexdigest()
|
# model_code = hashlib.sha1(base64_datas.encode('utf-8')).hexdigest()
|
||||||
report_path = attachment._full_path(attachment.store_fname)
|
# report_path = attachment._full_path(attachment.store_fname)
|
||||||
vals['model_file'] = self.transition_glb_file(report_path, model_code)
|
vals['model_file'] = self.model_analyze(attachment)
|
||||||
# logging.info('create-model_file:%s' % len(vals['model_file']))
|
# logging.info('create-model_file:%s' % len(vals['model_file']))
|
||||||
|
|
||||||
obj = super(QuickEasyOrder, self).create(vals)
|
obj = super(QuickEasyOrder, self).create(vals)
|
||||||
@@ -91,6 +93,147 @@ class QuickEasyOrder(models.Model):
|
|||||||
obj.state = '待接单'
|
obj.state = '待接单'
|
||||||
return obj
|
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文件
|
# 将attach的datas内容转为glb文件
|
||||||
def transition_glb_file(self, report_path, model_code):
|
def transition_glb_file(self, report_path, model_code):
|
||||||
shapes = read_step_file(report_path)
|
shapes = read_step_file(report_path)
|
||||||
@@ -116,24 +259,7 @@ class QuickEasyOrder(models.Model):
|
|||||||
raise ValidationError('只允许上传一个文件')
|
raise ValidationError('只允许上传一个文件')
|
||||||
if item.upload_model_file:
|
if item.upload_model_file:
|
||||||
file_attachment_id = item.upload_model_file[0]
|
file_attachment_id = item.upload_model_file[0]
|
||||||
# 附件路径
|
item.model_file = self.model_analyze(file_attachment_id)
|
||||||
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)
|
|
||||||
self._get_price(item)
|
self._get_price(item)
|
||||||
else:
|
else:
|
||||||
item.model_file = False
|
item.model_file = False
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
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_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_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
|
access_sale_order_director,sale_order_director,model_sale_order,sf_base.group_sale_director,1,1,1,0
|
||||||
|
|||||||
|
@@ -47,7 +47,7 @@
|
|||||||
</h1>
|
</h1>
|
||||||
<group>
|
<group>
|
||||||
<group>
|
<group>
|
||||||
<field name="customer_id" readonly="1" force_save="1"/>
|
<field name="customer_id" />
|
||||||
<field name="material_id"/>
|
<field name="material_id"/>
|
||||||
<field name="material_model_id"/>
|
<field name="material_model_id"/>
|
||||||
<!-- <field name="process_id"/>-->
|
<!-- <field name="process_id"/>-->
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ from odoo.http import request
|
|||||||
|
|
||||||
class Manufacturing_Connect(http.Controller):
|
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="*")
|
cors="*")
|
||||||
def get_equipment_tool_Info(self, **kw):
|
def get_equipment_tool_Info(self, **kw):
|
||||||
"""
|
"""
|
||||||
机床当前刀库实时信息
|
机床刀库实时信息
|
||||||
:param kw:
|
:param kw:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
@@ -31,6 +31,7 @@ class Manufacturing_Connect(http.Controller):
|
|||||||
for equipment_tool_id in item.product_template_ids:
|
for equipment_tool_id in item.product_template_ids:
|
||||||
functional_tool_id = self.env['sf.functional.cutting.tool.entity'].sudo().search(
|
functional_tool_id = self.env['sf.functional.cutting.tool.entity'].sudo().search(
|
||||||
[('code', '=', equipment_tool_id.tool_code)])
|
[('code', '=', equipment_tool_id.tool_code)])
|
||||||
|
|
||||||
alarm_time = None
|
alarm_time = None
|
||||||
if functional_tool_id.functional_tool_status == '报警':
|
if functional_tool_id.functional_tool_status == '报警':
|
||||||
alarm_time = self.env['sf.functional.tool.warning'].sudo().search(
|
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}
|
res = {'Succeed': False, 'ErrorCode': 202, 'Error': e}
|
||||||
logging.info('get_equipment_tool_Info error:%s' % e)
|
logging.info('get_equipment_tool_Info error:%s' % e)
|
||||||
return json.JSONEncoder().encode(res)
|
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)
|
||||||
@@ -180,7 +180,7 @@ class FunctionalCuttingToolEntity(models.Model):
|
|||||||
|
|
||||||
# ==========刀具组接口==========
|
# ==========刀具组接口==========
|
||||||
def _register_functional_tool_groups(self, obj):
|
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()
|
sf_sync_config = self.env['res.config.settings'].get_values()
|
||||||
token = sf_sync_config['token']
|
token = sf_sync_config['token']
|
||||||
sf_secret_key = sf_sync_config['sf_secret_key']
|
sf_secret_key = sf_sync_config['sf_secret_key']
|
||||||
@@ -956,18 +956,6 @@ class FunctionalToolAssembly(models.Model):
|
|||||||
return functional_tool
|
return functional_tool
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def automated_assembly(self):
|
|
||||||
"""
|
|
||||||
todo 自动组装
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
|
|
||||||
def automatic_printing_of_QR_code(self):
|
|
||||||
"""
|
|
||||||
todo 自动打印二维码
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
|
|
||||||
def assemble_single_print(self):
|
def assemble_single_print(self):
|
||||||
"""
|
"""
|
||||||
todo 组装单打印
|
todo 组装单打印
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ class CNCprocessing(models.Model):
|
|||||||
|
|
||||||
# ==========MES装刀指令接口==========
|
# ==========MES装刀指令接口==========
|
||||||
def register_cnc_processing(self, cnc_processing):
|
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()
|
sf_sync_config = self.env['res.config.settings'].get_values()
|
||||||
token = sf_sync_config['token']
|
token = sf_sync_config['token']
|
||||||
sf_secret_key = sf_sync_config['sf_secret_key']
|
sf_secret_key = sf_sync_config['sf_secret_key']
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
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_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,1
|
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,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,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_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,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,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_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,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,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,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,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,0
|
||||||
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_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
|
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
|
||||||
|
|||||||
|
@@ -967,10 +967,10 @@
|
|||||||
}"
|
}"
|
||||||
attrs="{'invisible': [('assemble_status', '!=', '0')]}" groups="sf_base.group_sf_mrp_user"
|
attrs="{'invisible': [('assemble_status', '!=', '0')]}" groups="sf_base.group_sf_mrp_user"
|
||||||
class="btn-primary"/>
|
class="btn-primary"/>
|
||||||
<button string="组装单打印" name="assemble_single_print" type="object"
|
<!-- <button string="组装单打印" name="assemble_single_print" type="object"-->
|
||||||
groups="sf_base.group_sf_mrp_user"
|
<!-- groups="sf_base.group_sf_mrp_user"-->
|
||||||
attrs="{'invisible': [('assemble_status', '=', '0')]}" class="btn-primary"
|
<!-- attrs="{'invisible': [('assemble_status', '=', '0')]}" class="btn-primary"-->
|
||||||
confirm="是否确认打印组装单"/>
|
<!-- confirm="是否确认打印组装单"/>-->
|
||||||
</tree>
|
</tree>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
@@ -1006,14 +1006,10 @@
|
|||||||
attrs="{'invisible': [('assemble_status', '!=', '0')]}"
|
attrs="{'invisible': [('assemble_status', '!=', '0')]}"
|
||||||
class="btn-primary"/>
|
class="btn-primary"/>
|
||||||
|
|
||||||
<button string="打印二维码" name="automatic_printing_of_QR_code" type="object"
|
<!-- <button string="组装单打印" name="assemble_single_print" type="object"-->
|
||||||
groups="sf_base.group_sf_mrp_user"
|
<!-- groups="sf_base.group_sf_mrp_user"-->
|
||||||
attrs="{'invisible': [('assemble_status', '=', '0')]}" class="btn-primary"
|
<!-- attrs="{'invisible': [('assemble_status', '=', '0')]}" class="btn-primary"-->
|
||||||
confirm="是否确认打印二维码"/>
|
<!-- 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"/>
|
<field name="assemble_status" widget="statusbar" statusbar_visible="0,1"/>
|
||||||
</header>
|
</header>
|
||||||
<sheet>
|
<sheet>
|
||||||
|
|||||||
@@ -445,6 +445,21 @@ class FunctionalToolAssemblyOrder(models.TransientModel):
|
|||||||
if obj.after_assembly_functional_tool_length == 0:
|
if obj.after_assembly_functional_tool_length == 0:
|
||||||
raise ValidationError('组装参数信息【伸出长】不能为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):
|
def functional_tool_assembly(self):
|
||||||
"""
|
"""
|
||||||
功能刀具组装
|
功能刀具组装
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import datetime
|
import datetime
|
||||||
import logging
|
import logging
|
||||||
|
import base64
|
||||||
|
import qrcode
|
||||||
|
import io
|
||||||
from odoo import api, fields, models, _
|
from odoo import api, fields, models, _
|
||||||
from odoo.osv import expression
|
from odoo.osv import expression
|
||||||
from odoo.exceptions import UserError
|
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
|
# return area_type_barcode + self.channel + self.direction + '-' + self.barcode + '-' + i_str + '-' + j_str
|
||||||
|
|
||||||
|
|
||||||
class ShelfLocation(models.Model):
|
class SfShelf(models.Model):
|
||||||
_name = 'sf.shelf.location'
|
_name = 'sf.shelf'
|
||||||
_description = '货架货位'
|
_description = '货架'
|
||||||
_order = 'name'
|
_order = 'name'
|
||||||
|
|
||||||
# current_location_id = fields.Many2one('sf.shelf.location', string='当前位置')
|
name = fields.Char('货架名称', required=True, size=20)
|
||||||
# # 目的位置
|
barcode = fields.Char('编码', copy=False, size=15, required=True)
|
||||||
# destination_location_id = fields.Many2one('sf.shelf.location', string='目的位置')
|
# 货位
|
||||||
current_move_ids = fields.One2many('stock.move.line', 'current_location_id', '当前位置调拨单')
|
location_ids = fields.One2many('sf.shelf.location', 'shelf_id', string='货位')
|
||||||
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)
|
|
||||||
|
|
||||||
check_state = fields.Selection([
|
check_state = fields.Selection([
|
||||||
('enable', '启用'),
|
('enable', '启用'),
|
||||||
@@ -253,48 +241,26 @@ class ShelfLocation(models.Model):
|
|||||||
def action_check(self):
|
def action_check(self):
|
||||||
self.check_state = 'enable'
|
self.check_state = 'enable'
|
||||||
|
|
||||||
# 仓库类别(selection:库区、库位、货位)
|
|
||||||
location_type = fields.Selection([
|
|
||||||
('货架', '货架'),
|
|
||||||
('货位', '货位')
|
|
||||||
], string='存储类型')
|
|
||||||
# 绑定库区
|
# 绑定库区
|
||||||
shelf_location_id = fields.Many2one('stock.location', string='所属库区', domain=[('location_type', '=', '库区')])
|
shelf_location_id = fields.Many2one('stock.location', string='所属库区')
|
||||||
location_id = fields.Many2one('stock.location', string='所属库区', domain=[('location_type', '=', '库区')])
|
|
||||||
# 产品类别 (关联:product.category)
|
|
||||||
# product_type = fields.Many2many('product.category', string='产品类别')
|
|
||||||
|
|
||||||
# picking_product_type = fields.Many2many('stock.picking', string='调拨产品类别', related='location_dest_id.product_type')
|
|
||||||
# 货架独有字段:通道、方向、货架高度(m)、货架层数、层数容量
|
# 货架独有字段:通道、方向、货架高度(m)、货架层数、层数容量
|
||||||
channel = fields.Char(string='通道')
|
channel = fields.Char(string='通道', required=True, size=10)
|
||||||
direction = fields.Selection([
|
direction = fields.Selection([
|
||||||
('R', 'R'),
|
('R', 'R'),
|
||||||
('L', 'L')
|
('L', 'L')
|
||||||
], string='方向')
|
], string='方向', required=True)
|
||||||
shelf_height = fields.Float(string='货架高度(m)')
|
shelf_height = fields.Float(string='货架高度(m)')
|
||||||
shelf_layer = fields.Integer(string='货架层数')
|
shelf_layer = fields.Integer(string='货架层数')
|
||||||
layer_capacity = fields.Integer(string='层数容量')
|
layer_capacity = fields.Integer(string='层数容量')
|
||||||
|
|
||||||
# 货位独有字段:货位状态、产品(关联产品对象)、产品序列号(关联产品序列号对象)
|
# 是否有货位
|
||||||
location_status = fields.Selection([
|
is_there_area = fields.Boolean(string='是否有货位', compute='_compute_is_there_area', default=False, store=True)
|
||||||
('空闲', '空闲'),
|
|
||||||
('占用', '占用'),
|
|
||||||
('禁用', '禁用')
|
|
||||||
], 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='产品序列号')
|
|
||||||
|
|
||||||
hide_shelf = fields.Boolean(compute='_compute_hide_what', string='隐藏货架')
|
@api.depends('location_ids')
|
||||||
hide_location = fields.Boolean(compute='_compute_hide_what', string='隐藏货位')
|
def _compute_is_there_area(self):
|
||||||
|
for record in self:
|
||||||
# 修改货位状态为禁用
|
record.is_there_area = bool(record.location_ids)
|
||||||
def action_location_status_disable(self):
|
|
||||||
self.location_status = '禁用'
|
|
||||||
|
|
||||||
# 修改货位状态为空闲
|
|
||||||
def action_location_status_enable(self):
|
|
||||||
self.location_status = '空闲'
|
|
||||||
|
|
||||||
@api.onchange('shelf_location_id')
|
@api.onchange('shelf_location_id')
|
||||||
def _onchange_shelf_location_id(self):
|
def _onchange_shelf_location_id(self):
|
||||||
@@ -307,53 +273,24 @@ class ShelfLocation(models.Model):
|
|||||||
for location in all_location:
|
for location in all_location:
|
||||||
location.location_id = record.shelf_location_id.id
|
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):
|
def create_location(self):
|
||||||
"""
|
"""
|
||||||
当仓库类型为货架时,自动生成其下面的货位,数量为货架层数*层数容量
|
当仓库类型为货架时,自动生成其下面的货位,数量为货架层数*层数容量
|
||||||
"""
|
"""
|
||||||
if self.location_type == '货架':
|
area_obj = self.env['sf.shelf.location']
|
||||||
for i in range(self.shelf_layer):
|
for i in range(self.shelf_layer):
|
||||||
for j in range(self.layer_capacity):
|
for j in range(self.layer_capacity):
|
||||||
location_name = self.name + '-' + str(i + 1) + '层' + '-' + str(j + 1) + '位置'
|
location_name = self.name + '-' + str(i + 1) + '层' + '-' + str(j + 1) + '位置'
|
||||||
# 检查是否已经有同名的位置存在
|
# 检查是否已经有同名的位置存在
|
||||||
existing_location = self.search([('name', '=', location_name)])
|
existing_location = area_obj.search([('name', '=', location_name)])
|
||||||
if not existing_location:
|
if not existing_location:
|
||||||
self.create({
|
area_obj.create({
|
||||||
'name': location_name,
|
'name': location_name,
|
||||||
'location_id': self.shelf_location_id.id,
|
'location_id': self.shelf_location_id.id,
|
||||||
'location_type': '货位',
|
'barcode': self.generate_barcode(i, j),
|
||||||
'barcode': self.generate_barcode(i, j),
|
'location_status': '空闲',
|
||||||
'location_status': '空闲',
|
'shelf_id': self.id
|
||||||
})
|
})
|
||||||
|
|
||||||
def generate_barcode(self, i, j):
|
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
|
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):
|
class Sf_stock_move_line(models.Model):
|
||||||
_inherit = 'stock.move.line'
|
_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 = fields.Many2one('stock.location', string='目标库位')
|
||||||
location_dest_id_product_type = fields.Many2many(related='location_dest_id.product_type')
|
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)
|
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):
|
# def button_test(self):
|
||||||
# print(self.picking_id.name)
|
# print(self.picking_id.name)
|
||||||
@@ -652,3 +775,35 @@ class SfStockScrap(models.Model):
|
|||||||
|
|
||||||
def action_check(self):
|
def action_check(self):
|
||||||
self.check_state = 'enable'
|
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
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
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_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,sf.shelf.location,model_sf_shelf_location,sf_warehouse.group_sf_stock_manager,1,1,1,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_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
|
access_stock_warehouse_manager,stock.warehouse.manager,stock.model_stock_warehouse,sf_warehouse.group_sf_stock_user,1,1,1,0
|
||||||
|
|||||||
|
@@ -11,7 +11,6 @@
|
|||||||
</xpath>
|
</xpath>
|
||||||
<xpath expr="//field[@name='location_dest_id'][2]" position="after">
|
<xpath expr="//field[@name='location_dest_id'][2]" position="after">
|
||||||
<field name="destination_location_id" domain="[
|
<field name="destination_location_id" domain="[
|
||||||
('location_type', '=', '货位'),
|
|
||||||
('location_id', '=', location_dest_id_value),
|
('location_id', '=', location_dest_id_value),
|
||||||
('location_status', '=', '空闲')
|
('location_status', '=', '空闲')
|
||||||
]"/>
|
]"/>
|
||||||
@@ -51,6 +50,22 @@
|
|||||||
</xpath>
|
</xpath>
|
||||||
<xpath expr="//form//sheet//group//group//field[@name='location_dest_id']" position="after">
|
<xpath expr="//form//sheet//group//group//field[@name='location_dest_id']" position="after">
|
||||||
<field name="destination_location_id" options="{'no_create': False}"/>
|
<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>
|
</xpath>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|||||||
@@ -1,14 +1,78 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<odoo>
|
<odoo>
|
||||||
<data>
|
<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">
|
<record id="view_shelf_location_tree" model="ir.ui.view">
|
||||||
<field name="name">shelf.location.tree</field>
|
<field name="name">shelf.location.tree</field>
|
||||||
<field name="model">sf.shelf.location</field>
|
<field name="model">sf.shelf.location</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<tree string="Shelf Location">
|
<tree string="Shelf Location">
|
||||||
<field name="name" string="名称"/>
|
<field name="barcode"/>
|
||||||
<field name="barcode" string="编码"/>
|
<field name="name"/>
|
||||||
<field name="location_type"/>
|
<field name="location_id"/>
|
||||||
<!-- <field name="check_state" widget="label_selection"-->
|
<!-- <field name="check_state" widget="label_selection"-->
|
||||||
<!-- options="{'classes': {'unchecked':'warning','checked': 'success'}}"/>-->
|
<!-- options="{'classes': {'unchecked':'warning','checked': 'success'}}"/>-->
|
||||||
<!-- <button name="action_check" string="审核" type="object"-->
|
<!-- <button name="action_check" string="审核" type="object"-->
|
||||||
@@ -53,9 +117,9 @@
|
|||||||
<field name="model">sf.shelf.location</field>
|
<field name="model">sf.shelf.location</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<form string="Shelf Location">
|
<form string="Shelf Location">
|
||||||
|
|
||||||
<header>
|
<header>
|
||||||
<field name="location_status" invisible="1"/>
|
<field name="location_status" invisible="1"/>
|
||||||
|
|
||||||
<button string="生成货位" name="create_location" type="object" class="oe_highlight"
|
<button string="生成货位" name="create_location" type="object" class="oe_highlight"
|
||||||
attrs="{'invisible': [('hide_shelf', '=', False)]}"/>
|
attrs="{'invisible': [('hide_shelf', '=', False)]}"/>
|
||||||
<button string="禁用货位" name="action_location_status_disable" type="object"
|
<button string="禁用货位" name="action_location_status_disable" type="object"
|
||||||
@@ -64,6 +128,11 @@
|
|||||||
<button string="启用货位" name="action_location_status_enable" type="object"
|
<button string="启用货位" name="action_location_status_enable" type="object"
|
||||||
class="oe_highlight"
|
class="oe_highlight"
|
||||||
attrs="{'invisible': ['|', ('hide_shelf', '=', True), ('location_status', '!=', '禁用')]}"/>
|
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>
|
</header>
|
||||||
<sheet>
|
<sheet>
|
||||||
<div class="oe_button_box" name="button_box">
|
<div class="oe_button_box" name="button_box">
|
||||||
@@ -72,7 +141,6 @@
|
|||||||
class="oe_stat_button"
|
class="oe_stat_button"
|
||||||
context="{'search_default_current_location_id': [active_id]}"
|
context="{'search_default_current_location_id': [active_id]}"
|
||||||
icon="fa-exchange">
|
icon="fa-exchange">
|
||||||
|
|
||||||
<field string="当前位置历史" name="current_move_ids" widget="statinfo"/>
|
<field string="当前位置历史" name="current_move_ids" widget="statinfo"/>
|
||||||
</button>
|
</button>
|
||||||
<button name="%(stock_move_line_action1)d"
|
<button name="%(stock_move_line_action1)d"
|
||||||
@@ -80,36 +148,17 @@
|
|||||||
class="oe_stat_button"
|
class="oe_stat_button"
|
||||||
context="{'search_default_destination_location_id': [active_id]}"
|
context="{'search_default_destination_location_id': [active_id]}"
|
||||||
icon="fa-exchange">
|
icon="fa-exchange">
|
||||||
|
|
||||||
<field string="目标位置历史" name="destination_move_ids" widget="statinfo"/>
|
<field string="目标位置历史" name="destination_move_ids" widget="statinfo"/>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<group>
|
<group>
|
||||||
<field name="hide_shelf" invisible="1"/>
|
<field name="barcode"/>
|
||||||
<field name="hide_location" invisible="1"/>
|
<field name="name"/>
|
||||||
<field name="name" string="名称"/>
|
<field name="shelf_id"/>
|
||||||
<field name="barcode" string="编码"/>
|
<field name="location_id"/>
|
||||||
<field name="location_type"/>
|
<field name="product_sn_id"/>
|
||||||
<field name="shelf_location_id" attrs="{'invisible': [('location_type', '=', '货位')]}"/>
|
<field name="product_id"/>
|
||||||
<field name="location_id"
|
<field name="location_status"/>
|
||||||
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="storage_time" widget="datetime"/>
|
<field name="storage_time" widget="datetime"/>
|
||||||
</group>
|
</group>
|
||||||
</sheet>
|
</sheet>
|
||||||
@@ -128,13 +177,13 @@
|
|||||||
#{record.location_status.raw_value == '空闲' ? 'kanban_color_1' : ''}
|
#{record.location_status.raw_value == '空闲' ? 'kanban_color_1' : ''}
|
||||||
#{record.location_status.raw_value == '占用' ? 'kanban_color_2' : ''}
|
#{record.location_status.raw_value == '占用' ? 'kanban_color_2' : ''}
|
||||||
#{record.location_status.raw_value == '禁用' ? 'kanban_color_3' : ''}">
|
#{record.location_status.raw_value == '禁用' ? 'kanban_color_3' : ''}">
|
||||||
<!-- 标题 -->
|
<!-- 标题 -->
|
||||||
<div class="o_kanban_card_header">
|
<div class="o_kanban_card_header">
|
||||||
<div class="o_kanban_card_header_title">
|
<div class="o_kanban_card_header_title">
|
||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 内容 -->
|
<!-- 内容 -->
|
||||||
<div class="o_kanban_record_bottom">
|
<div class="o_kanban_record_bottom">
|
||||||
<field name="location_status"/>
|
<field name="location_status"/>
|
||||||
</div>
|
</div>
|
||||||
@@ -145,31 +194,31 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</t>
|
</t>
|
||||||
<!-- <t t-name="kanban-box"> -->
|
<!-- <t t-name="kanban-box"> -->
|
||||||
<!-- <div t-attf-class="oe_kanban_card oe_kanban_global_click -->
|
<!-- <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_1' : ''} -->
|
||||||
<!-- #{record.location_status.raw_value == '占用' ? 'kanban_color_2' : ''} -->
|
<!-- #{record.location_status.raw_value == '占用' ? 'kanban_color_2' : ''} -->
|
||||||
<!-- #{record.location_status.raw_value == '禁用' ? 'kanban_color_3' : ''}"> -->
|
<!-- #{record.location_status.raw_value == '禁用' ? 'kanban_color_3' : ''}"> -->
|
||||||
<!-- --><!-- 看板内容 -->
|
<!-- 看板内容 -->
|
||||||
<!-- </div> -->
|
<!-- </div> -->
|
||||||
<!-- <div t-attf-class="oe_kanban_card"> -->
|
<!-- <div t-attf-class="oe_kanban_card"> -->
|
||||||
<!-- --><!-- 标题 -->
|
<!-- 标题 -->
|
||||||
<!-- <div class="o_kanban_card_header"> -->
|
<!-- <div class="o_kanban_card_header"> -->
|
||||||
<!-- <div class="o_kanban_card_header_title"> -->
|
<!-- <div class="o_kanban_card_header_title"> -->
|
||||||
<!-- <field name="name"/> -->
|
<!-- <field name="name"/> -->
|
||||||
<!-- </div> -->
|
<!-- </div> -->
|
||||||
<!-- </div> -->
|
<!-- </div> -->
|
||||||
<!-- --><!-- 内容 -->
|
<!-- 内容 -->
|
||||||
<!-- <div class="o_kanban_record_bottom"> -->
|
<!-- <div class="o_kanban_record_bottom"> -->
|
||||||
<!-- <field name="location_status"/> -->
|
<!-- <field name="location_status"/> -->
|
||||||
<!-- </div> -->
|
<!-- </div> -->
|
||||||
<!-- <div class="o_kanban_record_bottom"> -->
|
<!-- <div class="o_kanban_record_bottom"> -->
|
||||||
<!-- <field name="product_sn_id"/> -->
|
<!-- <field name="product_sn_id"/> -->
|
||||||
<!-- <span> | </span> -->
|
<!-- <span> | </span> -->
|
||||||
<!-- <field name="product_id"/> -->
|
<!-- <field name="product_id"/> -->
|
||||||
<!-- </div> -->
|
<!-- </div> -->
|
||||||
<!-- </div> -->
|
<!-- </div> -->
|
||||||
<!-- </t> -->
|
<!-- </t> -->
|
||||||
</templates>
|
</templates>
|
||||||
</kanban>
|
</kanban>
|
||||||
</field>
|
</field>
|
||||||
@@ -180,10 +229,12 @@
|
|||||||
<field name="name">shelf.location.search</field>
|
<field name="name">shelf.location.search</field>
|
||||||
<field name="model">sf.shelf.location</field>
|
<field name="model">sf.shelf.location</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<search string="货架货位">
|
<search string="货位">
|
||||||
<searchpanel class="account_root">
|
<searchpanel class="account_root">
|
||||||
<!-- <field name="location_type" icon="fa-filter"/> -->
|
<!-- <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"/> -->
|
<!-- <field name="location_status" icon="fa-filter"/> -->
|
||||||
</searchpanel>
|
</searchpanel>
|
||||||
</search>
|
</search>
|
||||||
@@ -191,11 +242,11 @@
|
|||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="shelf_location_kanban_action_id" model="ir.actions.act_window">
|
<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="type">ir.actions.act_window</field>
|
||||||
<field name="res_model">sf.shelf.location</field>
|
<field name="res_model">sf.shelf.location</field>
|
||||||
<field name="view_mode">kanban,form</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>
|
||||||
|
|
||||||
<!-- <record id="example_action" model="ir.actions.act_window"> -->
|
<!-- <record id="example_action" model="ir.actions.act_window"> -->
|
||||||
@@ -212,7 +263,7 @@
|
|||||||
|
|
||||||
|
|
||||||
<record id="action_sf_shelf_location" model="ir.actions.act_window">
|
<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="type">ir.actions.act_window</field>
|
||||||
<field name="res_model">sf.shelf.location</field>
|
<field name="res_model">sf.shelf.location</field>
|
||||||
<field name="view_mode">tree,form</field>
|
<field name="view_mode">tree,form</field>
|
||||||
@@ -235,15 +286,14 @@
|
|||||||
<!-- sequence="50" -->
|
<!-- sequence="50" -->
|
||||||
<!-- action="kanban_action_id"/> -->
|
<!-- action="kanban_action_id"/> -->
|
||||||
|
|
||||||
|
|
||||||
<menuitem id="shelf_location_kanban_menu" name="货位看板" parent="stock.menu_stock_root"
|
<menuitem id="shelf_location_kanban_menu" name="货位看板" parent="stock.menu_stock_root"
|
||||||
sequence="51"
|
sequence="51"
|
||||||
action="shelf_location_kanban_action_id"
|
action="shelf_location_kanban_action_id"
|
||||||
groups="sf_warehouse.group_sf_stock_user"/>
|
groups="sf_warehouse.group_sf_stock_user"/>
|
||||||
|
|
||||||
<menuitem id="menu_sf_shelf_location" name="货架货位" parent="stock.menu_warehouse_config"
|
<menuitem id="menu_sf_shelf_location" name="货架货位" parent="stock.menu_warehouse_config"
|
||||||
sequence="2"
|
sequence="20"/>
|
||||||
action="action_sf_shelf_location"
|
|
||||||
groups="sf_warehouse.group_sf_stock_user"/>
|
|
||||||
|
|
||||||
|
|
||||||
</data>
|
</data>
|
||||||
|
|||||||
Reference in New Issue
Block a user