diff --git a/odoo.conf b/odoo.conf
index 48ce1738..8db2413f 100644
--- a/odoo.conf
+++ b/odoo.conf
@@ -4,7 +4,7 @@ csv_internal_sep = ,
data_dir = /var/lib/odoo
db_host = 172.17.0.2
db_maxconn = 64
-db_name = sf_t2cs_003
+db_name = sf_t_0430
db_password = sf
db_port = 5432
db_sslmode = prefer
diff --git a/sf_base/security/ir.model.access.csv b/sf_base/security/ir.model.access.csv
index 3795e69b..88e056e6 100644
--- a/sf_base/security/ir.model.access.csv
+++ b/sf_base/security/ir.model.access.csv
@@ -11,7 +11,8 @@ access_sf_machine_control_system,sf_machine_control_system,model_sf_machine_cont
access_sf_machine_control_system_admin,sf_machine_control_system_admin,model_sf_machine_control_system,base.group_system,1,1,1,0
access_sf_production_process_group_sale_director,sf_production_process_group_sale_director,model_sf_production_process,sf_base.group_sale_director,1,0,0,0
access_sf_production_process_group_sale_salemanager,sf_production_process_group_sale_salemanager,model_sf_production_process,sf_base.group_sale_salemanager,1,0,0,0
-
+access_res_partner_category_group_sale_salemanager,res_partner_category_group_sale_salemanager,base.model_res_partner_category,sf_base.group_sale_salemanager,1,0,1,0
+access_res_partner_category_group_sale_director,res_partner_category_group_sale_director,base.model_res_partner_category,sf_base.group_sale_director,1,0,1,0
access_sf_production_process,sf_production_process,model_sf_production_process,base.group_user,1,1,1,0
access_sf_production_process_admin,sf_production_process_admin,model_sf_production_process,base.group_system,1,1,1,0
access_sf_production_materials,sf_production_materials,model_sf_production_materials,base.group_user,1,1,1,0
diff --git a/sf_dlm/models/product_supplierinfo.py b/sf_dlm/models/product_supplierinfo.py
index 4141a363..2784cba0 100644
--- a/sf_dlm/models/product_supplierinfo.py
+++ b/sf_dlm/models/product_supplierinfo.py
@@ -37,7 +37,7 @@ class ResMrpBomMo(models.Model):
'product_qty': 1,
'product_uom_id': 1
}
- return self.env['mrp.bom.line'].create(vals)
+ return self.env['mrp.bom.line'].sudo().create(vals)
# 业务平台分配工厂后在智能工厂先创建销售订单再创建该产品后再次进行创建bom
def bom_create(self, product, bom_type, product_type):
@@ -64,7 +64,7 @@ class ResMrpBomMo(models.Model):
qty = 1
if round(embryo.volume * raw_bom_line.materials_type_id.density / 1000000) > 1:
qty = round(embryo.volume * raw_bom_line.materials_type_id.density / 1000000)
- bom_line = self.env['mrp.bom.line'].create({
+ bom_line = self.env['mrp.bom.line'].sudo().create({
'bom_id': self.id,
'product_id': raw_bom_line.id,
'product_tmpl_id': raw_bom_line.product_tmpl_id.id,
diff --git a/sf_manufacturing/__manifest__.py b/sf_manufacturing/__manifest__.py
index 1c49c88b..54538a7d 100644
--- a/sf_manufacturing/__manifest__.py
+++ b/sf_manufacturing/__manifest__.py
@@ -13,6 +13,7 @@
'depends': ['sf_base', 'sf_maintenance', 'web_widget_model_viewer', 'sf_warehouse'],
'data': [
'data/stock_data.xml',
+ 'data/empty_racks_data.xml',
'security/group_security.xml',
'security/ir.model.access.csv',
'wizard/workpiece_delivery_views.xml',
diff --git a/sf_manufacturing/controllers/controllers.py b/sf_manufacturing/controllers/controllers.py
index 1568f7c7..80ae21e7 100644
--- a/sf_manufacturing/controllers/controllers.py
+++ b/sf_manufacturing/controllers/controllers.py
@@ -8,7 +8,7 @@ from odoo.http import request
class Manufacturing_Connect(http.Controller):
- @http.route('/AutoDeviceApi/GetWoInfo', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False,
+ @http.route('/AutoDeviceApi/GetWoInfo', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
cors="*")
def get_Work_Info(self, **kw):
"""
@@ -21,6 +21,8 @@ class Manufacturing_Connect(http.Controller):
res = {'Succeed': True, 'Datas': []}
datas = request.httprequest.data
ret = json.loads(datas)
+ request.env['center_control.interface.log'].sudo().create(
+ {'content': ret, 'name': 'AutoDeviceApi/GetWoInfo'})
logging.info('RfidCode:%s' % ret['RfidCode'])
if 'RfidCode' in ret:
workorder = request.env['mrp.workorder'].sudo().search([('rfid_code', '=', ret['RfidCode'])])
@@ -60,6 +62,8 @@ class Manufacturing_Connect(http.Controller):
res = {'Succeed': True, 'Datas': []}
datas = request.httprequest.data
ret = json.loads(datas)
+ request.env['center_control.interface.log'].sudo().create(
+ {'content': ret, 'name': 'AutoDeviceApi/GetShiftPlan'})
if 'ProductionLine' in ret:
workorder_ids = request.env['mrp.workorder'].sudo().get_plan_workorder(ret['ProductionLine'])
else:
@@ -113,6 +117,8 @@ class Manufacturing_Connect(http.Controller):
res = {'Succeed': True, 'Datas': []}
datas = request.httprequest.data
ret = json.loads(datas)
+ request.env['center_control.interface.log'].sudo().create(
+ {'content': ret, 'name': 'AutoDeviceApi/QcCheck'})
logging.info('RfidCode:%s' % ret['RfidCode'])
if 'RfidCode' in ret:
workorder = request.env['mrp.workorder'].sudo().search(
@@ -150,6 +156,8 @@ class Manufacturing_Connect(http.Controller):
res = {'Succeed': True, 'Datas': ['工单已开始']}
datas = request.httprequest.data
ret = json.loads(datas)
+ request.env['center_control.interface.log'].sudo().create(
+ {'content': ret, 'name': 'AutoDeviceApi/FeedBackStart'})
production_id = ret['BillId']
routing_type = ret['CraftId']
equipment_id = ret["DeviceId"]
@@ -193,6 +201,8 @@ class Manufacturing_Connect(http.Controller):
res = {'Succeed': True, 'Datas': ['工单已结束']}
datas = request.httprequest.data
ret = json.loads(datas)
+ request.env['center_control.interface.log'].sudo().create(
+ {'content': ret, 'name': 'AutoDeviceApi/FeedBackEnd'})
production_id = ret['BillId']
routing_type = ret['CraftId']
workorder = request.env['mrp.workorder'].sudo().search(
@@ -237,6 +247,8 @@ class Manufacturing_Connect(http.Controller):
res = {'Succeed': True}
datas = request.httprequest.data
ret = json.loads(datas)
+ request.env['center_control.interface.log'].sudo().create(
+ {'content': ret, 'name': 'AutoDeviceApi/PartQualityInspect'})
production_id = ret['BillId']
routing_type = ret['CraftId']
workorder = request.env['mrp.workorder'].sudo().search(
@@ -293,6 +305,8 @@ class Manufacturing_Connect(http.Controller):
res = {'Succeed': True, 'Datas': []}
datas = request.httprequest.data
ret = json.loads(datas)
+ request.env['center_control.interface.log'].sudo().create(
+ {'content': ret, 'name': 'AutoDeviceApi/CMMProgDolod'})
if 'RfidCode' in ret:
logging.info('RfidCode:%s' % ret['RfidCode'])
workorder = request.env['mrp.workorder'].sudo().search(
@@ -331,6 +345,8 @@ class Manufacturing_Connect(http.Controller):
res = {'Succeed': True, 'Datas': []}
datas = request.httprequest.data
ret = json.loads(datas)
+ request.env['center_control.interface.log'].sudo().create(
+ {'content': ret, 'name': 'AutoDeviceApi/NCProgDolod'})
if 'RfidCode' in ret:
logging.info('RfidCode:%s' % ret['RfidCode'])
workorder = request.env['mrp.workorder'].sudo().search(
@@ -370,6 +386,8 @@ class Manufacturing_Connect(http.Controller):
res = {'Succeed': True, 'Datas': []}
datas = request.httprequest.data
ret = json.loads(datas)
+ request.env['center_control.interface.log'].sudo().create(
+ {'content': ret, 'name': 'AutoDeviceApi/LocationChange'})
logging.info('LocationChange_ret===========:%s' % ret)
RfidCode = ret['RfidCode']
ChangeType = ret['ChangeType']
@@ -426,11 +444,13 @@ class Manufacturing_Connect(http.Controller):
res = {'Succeed': True}
datas = request.httprequest.data
ret = json.loads(datas)
+ request.env['center_control.interface.log'].sudo().create(
+ {'content': ret, 'name': 'AutoDeviceApi/AGVToProduct'})
logging.info('ret:%s' % ret)
if 'DeviceId' in ret:
logging.info('DeviceId:%s' % ret['DeviceId'])
if 'IsComplete' in ret:
- if ret['IsComplete'] is True:
+ if ret['IsComplete'] is True or ret['IsComplete'] is False:
for i in range(1, 5):
logging.info('F-RfidCode:%s' % i)
if f'RfidCode{i}' in ret:
@@ -477,12 +497,14 @@ class Manufacturing_Connect(http.Controller):
res = {'Succeed': True}
datas = request.httprequest.data
ret = json.loads(datas)
+ request.env['center_control.interface.log'].sudo().create(
+ {'content': ret, 'name': 'AutoDeviceApi/AGVDownProduct'})
logging.info('ret:%s' % ret)
if 'DeviceId' in ret:
logging.info('DeviceId:%s' % ret['DeviceId'])
delivery_Arr = []
if 'IsComplete' in ret:
- if ret['IsComplete'] is True:
+ if ret['IsComplete'] is True or ret['IsComplete'] is False:
for i in range(1, 5):
logging.info('F-RfidCode:%s' % i)
if f'RfidCode{i}' in ret:
@@ -538,6 +560,8 @@ class Manufacturing_Connect(http.Controller):
res = {'Succeed': True}
datas = request.httprequest.data
ret = json.loads(datas)
+ request.env['center_control.interface.log'].sudo().create(
+ {'content': ret, 'name': 'AutoDeviceApi/EquipmentBaseCoordinate'})
if 'DeviceId' in ret:
equipment = request.env['maintenance.equipment'].sudo().search('name', '=', ret['DeviceId'])
if equipment:
diff --git a/sf_manufacturing/controllers/workpiece.py b/sf_manufacturing/controllers/workpiece.py
index 8cda129b..5c5d6f22 100644
--- a/sf_manufacturing/controllers/workpiece.py
+++ b/sf_manufacturing/controllers/workpiece.py
@@ -28,7 +28,7 @@ class Workpiece(http.Controller):
req_codes = ret['reqCode'].split(',')
for req_code in req_codes:
workpiece_delivery = request.env['sf.workpiece.delivery'].sudo().search(
- [('delivery_num', '=', req_code.strip()), ('task_completion_time', '=', False)])
+ [('name', '=', req_code.strip()), ('task_completion_time', '=', False)])
if workpiece_delivery:
workpiece_delivery.write({'status': '已配送', 'task_completion_time': datetime.now()})
else:
diff --git a/sf_manufacturing/data/empty_racks_data.xml b/sf_manufacturing/data/empty_racks_data.xml
new file mode 100644
index 00000000..1a2b313b
--- /dev/null
+++ b/sf_manufacturing/data/empty_racks_data.xml
@@ -0,0 +1,48 @@
+
+
+
+
+ 工件配送
+ sf.workpiece.delivery
+ WDO%(year)s%(month)s%(day)s
+ 4
+
+
+
+
+ 运送空料架路线:C01-A01
+ 运送空料架
+
+
+
+
+
+
+ 运送空料架路线:B01-B02
+ 运送空料架
+
+
+
+
+
+
+ 运送空料架路线:B01-A01
+ 运送空料架
+
+
+
+
+
+
+ 运送空料架路线:C01-B02
+ 运送空料架
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sf_manufacturing/models/agv_setting.py b/sf_manufacturing/models/agv_setting.py
index dad6ec4f..d0104bc3 100644
--- a/sf_manufacturing/models/agv_setting.py
+++ b/sf_manufacturing/models/agv_setting.py
@@ -32,6 +32,8 @@ class AgvSetting(models.Model):
if da['DeviceId'] == item.name:
if da['AtHome'] is True:
item.state = '占用'
+ else:
+ item.state = '空闲'
class AgvTaskRoute(models.Model):
@@ -40,15 +42,20 @@ class AgvTaskRoute(models.Model):
name = fields.Char('名称')
type = fields.Selection([
- ('F01', '搬运'), ], string='类型', default="F01")
+ ('F01', '搬运'), ], string='任务类型', default="F01")
+ route_type = fields.Selection([
+ ('上产线', '上产线'), ('下产线', '下产线'), ('运送空料架', '运送空料架')], string='类型')
start_site_id = fields.Many2one('sf.agv.site', '起点接驳站位置编号')
end_site_id = fields.Many2one('sf.agv.site', '终点接驳站位置编号')
destination_production_line_id = fields.Many2one('sf.production.line', '目的生产线')
- priority = fields.Selection([
- ('0', '正常'),
- ('1', '低'),
- ('2', '中'),
- ('3', '高'),
- ('4', '紧急'),
- ], string='优先级', default='0')
+ active = fields.Boolean('有效', default=True)
+
+
+class Center_controlInterfaceLog(models.Model):
+ _name = 'center_control.interface.log'
+ _description = '中控接口调用日志'
+
+ name = fields.Char('接口名称')
+ content = fields.Char('接口内容')
+ interface_call_date = fields.Datetime("调用时间", default=fields.Datetime.now, readonly=True)
active = fields.Boolean('有效', default=True)
diff --git a/sf_manufacturing/models/mrp_production.py b/sf_manufacturing/models/mrp_production.py
index 82588ffe..e2d6616a 100644
--- a/sf_manufacturing/models/mrp_production.py
+++ b/sf_manufacturing/models/mrp_production.py
@@ -56,8 +56,9 @@ class MrpProduction(models.Model):
glb_file = fields.Binary("glb模型文件")
production_line_id = fields.Many2one('sf.production.line', string='生产线')
plan_start_processing_time = fields.Datetime('计划开始加工时间')
- production_line_state = fields.Selection([('待上产线', '待上产线'), ('已上产线', '已上产线'), ('已下产线', '已下产线')],
- string='上/下产线', default='待上产线')
+ production_line_state = fields.Selection(
+ [('待上产线', '待上产线'), ('已上产线', '已上产线'), ('已下产线', '已下产线')],
+ string='上/下产线', default='待上产线')
# 工序状态
# Todo 研究下用法
process_state = fields.Selection([
@@ -114,7 +115,7 @@ class MrpProduction(models.Model):
# production.state = 'pending_processing'
production.state = 'pending_cam'
if production.state == 'progress' and production.schedule_state == '已排' and production.process_state == '待加工':
- # if production.state == 'pending_cam' and production.process_state == '待加工':
+ # if production.state == 'pending_cam' and production.process_state == '待加工':
production.state = 'pending_processing'
elif production.state == 'progress' and production.process_state == '待解除装夹':
production.state = 'pending_era_cam'
@@ -262,10 +263,14 @@ class MrpProduction(models.Model):
# 其他规则限制: 默认只分配给工作中心状态为非故障的工作中心;
def _create_workorder3(self):
+ programming_no = None
+ product_id_new = None
for production in self:
if not production.bom_id or not production.product_id:
continue
workorders_values = []
+ if product_id_new is None:
+ product_id_new = production.product_id
product_qty = production.product_uom_id._compute_quantity(production.product_qty,
production.bom_id.product_uom_id)
@@ -290,7 +295,14 @@ class MrpProduction(models.Model):
'state': 'pending',
}]
if production.product_id.categ_id.type == '成品':
- production.fetchCNC()
+ if programming_no is None:
+ production.fetchCNC()
+ programming_no = production.programming_no
+ else:
+ if production.product_id == product_id_new:
+ if not production.programming_no:
+ production.write({'programming_no': programming_no, 'programming_state': '编程中'})
+
# 根据加工面板的面数及对应的工序模板生成工单
i = 0
processing_panel_len = len(production.product_id.model_processing_panel.split(','))
diff --git a/sf_manufacturing/models/mrp_workorder.py b/sf_manufacturing/models/mrp_workorder.py
index b32ffdce..817e6421 100644
--- a/sf_manufacturing/models/mrp_workorder.py
+++ b/sf_manufacturing/models/mrp_workorder.py
@@ -180,6 +180,11 @@ class ResMrpWorkOrder(models.Model):
detection_report = fields.Binary('检测报告', readonly=True)
is_remanufacture = fields.Boolean(string='是否重新生成制造订单', default=True)
+ @api.onchange('rfid_code')
+ def _onchange(self):
+ if self.rfid_code and self.state == 'progress':
+ self.workpiece_delivery_ids[0].write({'rfid_code': self.rfid_code})
+
def get_plan_workorder(self, production_line):
tomorrow = (date.today() + timedelta(days=+1)).strftime("%Y-%m-%d")
tomorrow_start = tomorrow + ' 00:00:00'
@@ -422,19 +427,22 @@ class ResMrpWorkOrder(models.Model):
if not item.route_id:
raise UserError('【工件配送】明细中请选择【任务路线】')
else:
- if item.is_cnc_program_down is True:
- if item.status == '待下发':
- return {
- 'name': _('确认'),
- 'type': 'ir.actions.act_window',
- 'view_mode': 'form',
- 'res_model': 'sf.workpiece.delivery.wizard',
- 'target': 'new',
- 'context': {
- 'default_workorder_id': self.id,
- }}
+ if self.state == 'done':
+ if item.is_cnc_program_down is True:
+ if item.status == '待下发':
+ return {
+ 'name': _('确认'),
+ 'type': 'ir.actions.act_window',
+ 'view_mode': 'form',
+ 'res_model': 'sf.workpiece.delivery.wizard',
+ 'target': 'new',
+ 'context': {
+ 'default_workorder_id': self.id,
+ }}
+ else:
+ raise UserError(_("该制造订单还未下发CNC程序单,无法进行工件配送"))
else:
- raise UserError(_("该制造订单还未下发CNC程序单,无法进行工件配送"))
+ raise UserError(_("该工单暂未完成,无法进行工件配送"))
# 拼接工单对象属性值
def json_workorder_str(self, k, production, route):
@@ -485,9 +493,8 @@ class ResMrpWorkOrder(models.Model):
def _json_workpiece_delivery_list(self, production):
return [
- [0, '', {'production_id': production.id, 'type': '上产线', 'delivery_num': '%s-%s' % (production.name, 1)}],
- [0, '',
- {'production_id': production.id, 'type': '下产线', 'delivery_num': '%s-%s' % (production.name, 2)}]]
+ [0, '', {'production_id': production.id, 'type': '上产线'}],
+ [0, '', {'production_id': production.id, 'type': '下产线'}]]
# 拼接工单对象属性值(表面工艺)
def _json_workorder_surface_process_str(self, production, route, process_parameter, supplier_id):
@@ -632,6 +639,33 @@ class ResMrpWorkOrder(models.Model):
# 'domain': [('production_id', '=', self.id)],
# 'target':'new'
# }
+ @api.depends('production_availability', 'blocked_by_workorder_ids', 'blocked_by_workorder_ids.state')
+ def _compute_state(self):
+ for workorder in self:
+ if workorder.routing_type == '装夹预调':
+ if not workorder.cnc_ids:
+ workorder.state = 'waiting'
+ else:
+ for item in workorder.cnc_ids:
+ functional_cutting_tool = self.env['sf.functional.cutting.tool.entity'].search(
+ [('tool_name_id.name', '=', item.cutting_tool_name)])
+ if not functional_cutting_tool:
+ workorder.state = 'waiting'
+ if workorder.state == 'pending':
+ if all([wo.state in ('done', 'cancel') for wo in workorder.blocked_by_workorder_ids]):
+ workorder.state = 'ready' if workorder.production_id.reservation_state == 'assigned' else 'waiting'
+ continue
+ if workorder.state not in ('waiting', 'ready'):
+ continue
+ if not all([wo.state in ('done', 'cancel') for wo in workorder.blocked_by_workorder_ids]):
+ workorder.state = 'pending'
+ continue
+ if workorder.production_id.reservation_state not in ('waiting', 'confirmed', 'assigned'):
+ continue
+ if workorder.production_id.reservation_state == 'assigned' and workorder.state == 'waiting':
+ workorder.state = 'ready'
+ elif workorder.production_id.reservation_state != 'assigned' and workorder.state == 'ready':
+ workorder.state = 'waiting'
def recreateManufacturingOrWorkerOrder(self):
"""
@@ -701,7 +735,18 @@ class ResMrpWorkOrder(models.Model):
def button_start(self):
if self.routing_type == '装夹预调' and self.production_id.move_raw_ids[0].move_line_ids[0].lot_id.name:
self.pro_code = self.production_id.move_raw_ids[0].move_line_ids[0].lot_id.name
-
+ if self.routing_type == '装夹预调':
+ cnc_workorder = self.search(
+ [('production_id', '=', self.production_id.id), ('routing_type', '=', 'CNC加工')],
+ limit=1, order='id asc')
+ if not cnc_workorder:
+ raise UserError(_('该制造订单还未下发CNC程序,请稍后再试'))
+ else:
+ for item in cnc_workorder.cnc_ids:
+ functional_cutting_tool = self.env['sf.functional.cutting.tool.entity'].search(
+ [('tool_name_id.name', '=', item.cutting_tool_name)])
+ if not functional_cutting_tool:
+ raise UserError(_('该制造订单的CNC程序为%s没有对应的功能刀具' % item.cutting_tool_name))
if self.routing_type == '解除装夹':
'''
记录开始时间
@@ -919,6 +964,9 @@ class CNCprocessing(models.Model):
if workpiece_delivery:
for item in workpiece_delivery:
item.is_cnc_program_down = True
+ if item.workorder_id.state == 'waiting':
+ item.workorder_id.state = 'ready'
+
# cnc_workorder.time_ids.date_end = datetime.now()
# cnc_workorder.button_finish()
@@ -1005,9 +1053,13 @@ class SfWorkOrderBarcodes(models.Model):
workorder = self.env['mrp.workorder'].browse(self.ids)
# workorder_preset = self.env['mrp.workorder'].search(
# [('routing_type', '=', '装夹预调'), ('rfid_code', '=', barcode)])
- workorder_old = self.env['mrp.workorder'].search([('rfid_code', '=', barcode)])
- if workorder_old:
- raise UserError('该托盘已绑定【%s】制造订单,请先解除绑定!!!' % workorder_old.production_id.name)
+ workorder_olds = self.env['mrp.workorder'].search(
+ [('routing_type', '=', '装夹预调'), ('rfid_code', '=', barcode)])
+ if workorder_olds:
+ name = ''
+ for workorder in workorder_olds:
+ name = '%s %s' % (name, workorder.production_id.name)
+ raise UserError('该托盘已绑定【%s】制造订单,请先解除绑定!!!' % name)
if workorder:
if workorder.routing_type == '装夹预调':
if workorder.state in ['done']:
@@ -1035,6 +1087,8 @@ class SfWorkOrderBarcodes(models.Model):
for item in workorder_rfid:
item.write({'rfid_code': barcode})
logging.info("Rfid绑定成功!!!")
+ else:
+ raise UserError('该Rfid【%s】绑定的是【%s】, 不是托盘!!!' % (barcode, lot.product_id.name))
self.process_state = '待检测'
self.date_start = datetime.now()
else:
@@ -1094,12 +1148,12 @@ class WorkPieceDelivery(models.Model):
_name = "sf.workpiece.delivery"
_description = '工件配送'
- delivery_num = fields.Char('工件配送编码')
+ name = fields.Char('单据编号')
workorder_id = fields.Many2one('mrp.workorder', string='工单', readonly=True)
+ workorder_state = fields.Selection(related='workorder_id.state', string='工单状态')
+ rfid_code = fields.Char(related='workorder_id.rfid_code', string='rfid码', store=True)
production_id = fields.Many2one('mrp.production', string='制造订单号', readonly=True)
- production_line_id = fields.Many2one('sf.production.line', compute='_compute_production_line_id',
- string='目的生产线', readonly=True,
- store=True)
+ production_line_id = fields.Many2one('sf.production.line', string='目的生产线')
plan_start_processing_time = fields.Datetime('计划开始加工时间', readonly=True)
route_id = fields.Many2one('sf.agv.task.route', '任务路线')
@@ -1113,9 +1167,24 @@ class WorkPieceDelivery(models.Model):
status = fields.Selection(
[('待下发', '待下发'), ('待配送', '待配送'), ('已配送', '已配送')], string='状态', default='待下发')
is_cnc_program_down = fields.Boolean('程序是否下发', default=False)
+ active = fields.Boolean(string="有效", default=True)
- # @api.model
- # def create(self, vals):
+ @api.model
+ def create(self, vals):
+ if vals.get('name', '/') == '/' or vals.get('name', '/') is False:
+ vals['name'] = self.env['ir.sequence'].next_by_code('sf.workpiece.delivery') or '/'
+ obj = super(WorkPieceDelivery, self).create(vals)
+ return obj
+
+ def action_delivery_history(self):
+ return {
+ 'name': _('配送历史'),
+ 'type': 'ir.actions.act_window',
+ 'view_mode': 'tree',
+ 'res_model': 'sf.workpiece.delivery',
+ 'view_id': self.env.ref('sf_manufacturing.sf_workpiece_delivery_empty_racks_tree').id,
+ 'domain': [('type', '=', '运送空料架'), ('route_id', '=', self.route_id.id), ('name', 'ilike', 'WDO')]
+ }
@api.onchange('route_id')
def onchange_route(self):
@@ -1126,6 +1195,7 @@ class WorkPieceDelivery(models.Model):
# 工件配送
def button_delivery(self):
delivery_ids = []
+ production_ids = []
is_cnc_down = 0
is_not_production_line = 0
is_not_route = 0
@@ -1136,29 +1206,44 @@ class WorkPieceDelivery(models.Model):
num = 0
for item in self:
num += 1
- if num > 4:
- raise UserError('仅限于配送1-4个制造订单,请重新选择')
- if item.route_id:
- if same_route_id is None:
- same_route_id = item.route_id.id
- if item.route_id.id != same_route_id:
- is_not_route += 1
- else:
- raise UserError('请选择【任务路线】再进行配送')
if production_type is None:
production_type = item.type
- if production_type != item.type:
- raise UserError('请选择类型为%s的制造订单进行配送' % production_type)
- if down_status != item.status:
- raise UserError('请选择状态为【待下发】的制造订单进行配送')
- if same_production_line_id is None:
- same_production_line_id = item.production_line_id.id
- if item.production_line_id.id != same_production_line_id:
- is_not_production_line += 1
- if item.is_cnc_program_down is False:
- is_cnc_down += 1
- if is_cnc_down == 0 and is_not_production_line == 0 and is_not_route == 0:
- delivery_ids.append(item.id)
+ if item.type == "运送空料架":
+ if num >= 2:
+ raise UserError('仅选择一条路线进行配送,请重新选择')
+ else:
+ delivery_ids.append(item.id)
+ else:
+ if num > 4:
+ raise UserError('仅限于配送1-4个制造订单,请重新选择')
+ if item.status in ['待配送', '已配送']:
+ raise UserError('请选择状态为【待下发】的制造订单进行配送')
+ if item.route_id:
+ if same_route_id is None:
+ same_route_id = item.route_id.id
+ if item.route_id.id != same_route_id:
+ is_not_route += 1
+ else:
+ raise UserError('请选择【任务路线】再进行配送')
+ if production_type != item.type:
+ raise UserError('请选择类型为%s的制造订单进行配送' % production_type)
+ if down_status != item.status:
+ up_workpiece = self.search([('type', '=', '上产线'), ('production_id', '=', item.production_id),
+ ('status', '=', '待下发')])
+ if up_workpiece:
+ raise UserError('您所选择的制造订单暂未上产线,请在上产线后再进行配送')
+ else:
+ raise UserError('请选择状态为【待下发】的制造订单进行配送')
+
+ if same_production_line_id is None:
+ same_production_line_id = item.production_line_id.id
+ if item.production_line_id.id != same_production_line_id:
+ is_not_production_line += 1
+ if item.is_cnc_program_down is False:
+ is_cnc_down += 1
+ if is_cnc_down == 0 and is_not_production_line == 0 and is_not_route == 0:
+ delivery_ids.append(item.id)
+ production_ids.append(item.production_id.id)
if is_cnc_down >= 1:
raise UserError('您所选择制造订单的【CNC程序】暂未下发,请在程序下发后再进行配送')
if is_not_production_line >= 1:
@@ -1176,9 +1261,17 @@ class WorkPieceDelivery(models.Model):
'target': 'new',
'context': {
'default_delivery_ids': [(6, 0, delivery_ids)],
+ 'default_production_ids': [(6, 0, production_ids)],
+ 'default_destination_production_line_id': same_production_line_id,
+ 'default_route_id': same_route_id,
+ 'default_type': production_type,
}}
else:
- raise UserError("您所选择制造订单的【任务路线】的【终点接驳站】已占用,请在该接驳站空闲时进行配送")
+ if self.type == '运送空料架':
+ raise UserError("您所选择的【任务路线】的【终点接驳站】已占用,请在该接驳站空闲时进行配送")
+ else:
+ raise UserError(
+ "您所选择制造订单的【任务路线】的【终点接驳站】已占用,请在该接驳站空闲时或选择其他路线进行配送")
# 验证agv站点是否可用
def _check_avgsite_state(self):
@@ -1187,15 +1280,18 @@ class WorkPieceDelivery(models.Model):
if agv_site:
agv_site.update_site_state()
for item in self:
- if item.type in ["上产线", "下产线"]:
- logging.info('工件配送-起点状态:%s-%s' % (
- item.feeder_station_start_id.name, item.feeder_station_start_id.state))
- logging.info('工件配送-终点状态:%s-%s' % (
- item.feeder_station_destination_id.name, item.feeder_station_destination_id.state))
+ logging.info('工件配送-起点状态:%s-%s' % (
+ item.feeder_station_start_id.name, item.feeder_station_start_id.state))
+ logging.info('工件配送-终点状态:%s-%s' % (
+ item.feeder_station_destination_id.name, item.feeder_station_destination_id.state))
+ if item.type in ['上产线', '下产线']:
if (
item.feeder_station_start_id.state == '占用' and item.feeder_station_destination_id.state == '空闲') or (
item.feeder_station_start_id.state == '空闲' and item.feeder_station_destination_id.state == '空闲'):
is_free = True
+ else:
+ if item.feeder_station_destination_id.state == '空闲':
+ is_free = True
logging.info('is_free:%s' % is_free)
return is_free
@@ -1206,12 +1302,23 @@ class WorkPieceDelivery(models.Model):
delivery_Arr = []
feeder_station_start = None
feeder_station_destination = None
+ route_id = None
for item in self:
- if feeder_station_start is None:
- feeder_station_start = item.feeder_station_start_id.name
- if feeder_station_destination is None:
- feeder_station_destination = item.feeder_station_destination_id.name
- delivery_Arr.append(item.delivery_num)
+ delivery_Arr.append(item.name)
+ if item.type in ['上产线', '下产线']:
+ if route_id is None:
+ route_id = item.route_id.id
+ if feeder_station_start is None:
+ feeder_station_start = item.feeder_station_start_id.name
+ if feeder_station_destination is None:
+ feeder_station_destination = item.feeder_station_destination_id.name
+ item.route_id = route_id
+ else:
+ self = self.create(
+ {'name': self.env['ir.sequence'].next_by_code('sf.workpiece.delivery'),
+ 'route_id': self.route_id.id,
+ 'feeder_station_start_id': self.feeder_station_start_id.id,
+ 'feeder_station_destination_id': self.feeder_station_destination_id.id})
delivery_str = ','.join(map(str, delivery_Arr))
if feeder_station_start is not None:
positionCode_Arr.append({
@@ -1240,24 +1347,19 @@ class WorkPieceDelivery(models.Model):
req_codes = ret['reqCode'].split(',')
for delivery_item in self:
for req_code in req_codes:
- if delivery_item.delivery_num == req_code.strip():
- logging.info('delivery_num:%s' % delivery_item.delivery_num)
+ if delivery_item.name == req_code.strip():
+ logging.info('delivery_item-name:%s' % delivery_item.name)
delivery_item.write({
'task_delivery_time': fields.Datetime.now(),
'status': '待配送'
})
- delivery_item.workorder_id.write({'is_delivery': True})
+ if delivery_item == "上产线":
+ delivery_item.workorder_id.write({'is_delivery': True})
else:
raise UserError(ret['message'])
except Exception as e:
logging.info('config-e:%s' % e)
- raise UserError("工件配送请求agv失败")
-
- @api.onchange('production_id.production_line_id')
- def _compute_production_line_id(self):
- if self.production_id.production_line_id:
- self.production_line_id = self.production_id.production_line_id.id
- self.plan_start_processing_time = self.production_id.plan_start_processing_time
+ raise UserError("工件配送请求agv失败:%s" % e)
@api.depends('task_delivery_time', 'task_completion_time')
def _compute_delivery_duration(self):
diff --git a/sf_manufacturing/models/product_template.py b/sf_manufacturing/models/product_template.py
index 16ea429a..4d07ae1c 100644
--- a/sf_manufacturing/models/product_template.py
+++ b/sf_manufacturing/models/product_template.py
@@ -399,7 +399,7 @@ class ResProductMo(models.Model):
cutting_speed_ids = fields.One2many('sf.cutting.speed', 'product_template_id', string='切削速度Vc')
feed_per_tooth_ids = fields.One2many('sf.feed.per.tooth', 'product_template_id', string='每齿走刀量fz')
- cutting_tool_diameter = fields.Float('直径(mm)')
+ cutting_tool_diameter = fields.Float('刀具直径(mm)')
cutting_tool_rear_angle = fields.Integer('后角(°)')
cutting_tool_main_included_angle = fields.Integer('主偏角(°)')
# 适用夹头型号可以多选
diff --git a/sf_manufacturing/security/ir.model.access.csv b/sf_manufacturing/security/ir.model.access.csv
index e8c2449c..1798ba27 100644
--- a/sf_manufacturing/security/ir.model.access.csv
+++ b/sf_manufacturing/security/ir.model.access.csv
@@ -52,8 +52,8 @@ access_mrp_bom_manager_group_sf_mrp_user,mrp.bom.manager,mrp.model_mrp_bom,sf_ba
access_mrp_bom_line_manager_group_sf_mrp_user,mrp.bom.line.manager,mrp.model_mrp_bom_line,sf_base.group_sf_mrp_user,1,1,1,0
access_mrp_bom_line_group_plan_director,mrp_bom_line_group_plan_director,mrp.model_mrp_bom_line,sf_base.group_plan_director,1,1,1,0
access_mrp_bom_line_group_sale_director,mrp_bom_line_group_sale_director,mrp.model_mrp_bom_line,sf_base.group_sale_director,1,1,1,0
+access_mrp_bom_line_group_sale_salemanager,mrp_bom_line_group_sale_salemanager,mrp.model_mrp_bom_line,sf_base.group_sale_salemanager,1,0,1,0
access_mrp_bom_line_group_purchase_director,mrp_bom_line_group_purchase_director,mrp.model_mrp_bom_line,sf_base.group_purchase_director,1,1,1,0
-
access_mrp_bom_byproduct_manager_group_sf_mrp_user,mrp.bom.byproduct manager,mrp.model_mrp_bom_byproduct,sf_base.group_sf_mrp_user,1,1,1,0
access_mrp_production_stock_worker,mrp.production stock_worker,mrp.model_mrp_production,stock.group_stock_user,1,0,0,0
access_product_product_user_group_sf_mrp_user,product.product user,product.model_product_product,sf_base.group_sf_mrp_user,1,0,0,0
@@ -132,4 +132,5 @@ access_maintenance_equipment_tool_group_plan_dispatch,maintenance.equipment.tool
access_sf_workpiece_delivery_group_plan_dispatch,sf.workpiece.delivery,sf_manufacturing.model_sf_workpiece_delivery,sf_base.group_plan_dispatch,1,0,0,0
access_sf_agv_site_group_sf_order_user,sf_agv_site_group_sf_order_user,model_sf_agv_site,sf_base.group_sf_order_user,1,1,1,0
-access_sf_agv_task_route_group_sf_order_user,sf_agv_task_route_group_sf_order_user,model_sf_agv_task_route,sf_base.group_sf_order_user,1,1,1,0
\ No newline at end of file
+access_sf_agv_task_route_group_sf_order_user,sf_agv_task_route_group_sf_order_user,model_sf_agv_task_route,sf_base.group_sf_order_user,1,1,1,0
+access_center_control_interface_log_admin,center_control_interface_log_admin,model_center_control_interface_log,base.group_system,1,1,1,0
\ No newline at end of file
diff --git a/sf_manufacturing/views/agv_setting_views.xml b/sf_manufacturing/views/agv_setting_views.xml
index 61b25e10..e8bd9f61 100644
--- a/sf_manufacturing/views/agv_setting_views.xml
+++ b/sf_manufacturing/views/agv_setting_views.xml
@@ -34,7 +34,8 @@
-
+
+
@@ -54,4 +55,36 @@
sequence="13"
action="action_agv_task_route_form"/>
+
+
+
+ center_control.interface.log
+
+
+
+
+
+
+
+
+
+
+ center_control.interface.log
+
+
+
+
+
+
+
+
+
+
+
+ 中控调用日志
+ center_control.interface.log
+ tree
+
+
\ No newline at end of file
diff --git a/sf_manufacturing/views/mrp_production_addional_change.xml b/sf_manufacturing/views/mrp_production_addional_change.xml
index 0e1695ef..c5616242 100644
--- a/sf_manufacturing/views/mrp_production_addional_change.xml
+++ b/sf_manufacturing/views/mrp_production_addional_change.xml
@@ -78,13 +78,14 @@
+
-
+
@@ -258,6 +259,18 @@
+
+ product.template.search
+ product.template
+
+
+
+
+
+
+
+
+
sf.mrp.production.workorder.tree.editable
@@ -380,7 +393,7 @@
-
+
diff --git a/sf_manufacturing/views/mrp_workorder_view.xml b/sf_manufacturing/views/mrp_workorder_view.xml
index b3164055..7fdf390d 100644
--- a/sf_manufacturing/views/mrp_workorder_view.xml
+++ b/sf_manufacturing/views/mrp_workorder_view.xml
@@ -43,7 +43,7 @@
{'invisible':
['|',("user_permissions","=",False),("name","=","获取CNC加工程序")]}
-
+
1
@@ -111,36 +111,38 @@
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+ attrs="{'invisible': ['|', '|', '|', ('production_state','in', ('draft', 'done', 'cancel')), ('working_state', '=', 'blocked'), ('state', 'in', ('done', 'cancel')), ('is_user_working', '!=', False)]}"/>
-
+ attrs="{'invisible': ['|', '|', ('production_state', 'in', ('draft', 'done', 'cancel')), ('working_state', '=', 'blocked'), ('is_user_working', '=', False)]}"/>
+
-
-
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
@@ -196,6 +198,8 @@
attrs='{"invisible": [("routing_type","in",("获取CNC加工程序","切割"))]}'/>
+
-
-
+
+
@@ -621,9 +625,9 @@
sf.workpiece.delivery
-
-
-
+
+
+
@@ -644,9 +648,68 @@
工件配送
sf.workpiece.delivery
- {'search_default_on_down':1}
+ {'search_default_on_up':1}
tree,search
- [('type','in',['上产线','下产线'])]
+ [('type','in',['上产线','下产线']),('workorder_state','=','done')]
+
+
+
+
+
+ 空料架配送
+ sf.workpiece.delivery
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 空料架配送
+ sf.workpiece.delivery
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 空料架配送
+ sf.workpiece.delivery
+
+
+
+
+
+
+
+
+
+
+
+ 空料架配送
+ sf.workpiece.delivery
+
+
+ tree
+ [('type','in',['运送空料架']),('name','not ilike','WDO')]
diff --git a/sf_manufacturing/wizard/workpiece_delivery_views.xml b/sf_manufacturing/wizard/workpiece_delivery_views.xml
index a65593d7..a8d00d72 100644
--- a/sf_manufacturing/wizard/workpiece_delivery_views.xml
+++ b/sf_manufacturing/wizard/workpiece_delivery_views.xml
@@ -5,13 +5,32 @@
sf.workpiece.delivery.wizard
diff --git a/sf_manufacturing/wizard/workpiece_delivery_wizard.py b/sf_manufacturing/wizard/workpiece_delivery_wizard.py
index e8222a1b..bebe9e44 100644
--- a/sf_manufacturing/wizard/workpiece_delivery_wizard.py
+++ b/sf_manufacturing/wizard/workpiece_delivery_wizard.py
@@ -2,7 +2,7 @@
# Part of YiZuo. See LICENSE file for full copyright and licensing details.
from odoo.exceptions import UserError, ValidationError
from datetime import datetime
-from odoo import models, api, fields
+from odoo import models, api, fields, _
class WorkpieceDeliveryWizard(models.TransientModel):
@@ -10,10 +10,74 @@ class WorkpieceDeliveryWizard(models.TransientModel):
_description = '工件配送'
delivery_ids = fields.Many2many('sf.workpiece.delivery', string='配送')
+ rfid_code = fields.Char('rfid码')
workorder_id = fields.Many2one('mrp.workorder', string='工单')
+ production_ids = fields.Many2many('mrp.production', string='制造订单号')
+ destination_production_line_id = fields.Many2one('sf.production.line', '目的生产线')
+ route_id = fields.Many2one('sf.agv.task.route', '任务路线', domain=[('route_type', 'in', ['上产线', '下产线'])])
+ feeder_station_start_id = fields.Many2one('sf.agv.site', '起点接驳站')
+ feeder_station_destination_id = fields.Many2one('sf.agv.site', '目的接驳站')
+ type = fields.Selection(
+ [('上产线', '上产线'), ('下产线', '下产线'), ('运送空料架', '运送空料架')], string='类型')
def confirm(self):
+ if self.type != '运送空料架':
+ if not self.route_id:
+ raise UserError('请选择路线')
if self.workorder_id:
self.workorder_id.workpiece_delivery_ids[0]._delivery_avg()
else:
- self.delivery_ids._delivery_avg()
+ is_not_production_line = 0
+ same_production_line_id = None
+ notsame_production_line_arr = []
+ for item in self.production_ids:
+ if same_production_line_id is None:
+ same_production_line_id = item.production_line_id.id
+ if item.production_line_id.id != same_production_line_id:
+ notsame_production_line_arr.append(item.name)
+ notsame_production_line_str = ','.join(map(str, notsame_production_line_arr))
+ if is_not_production_line >= 1:
+ raise UserError('制造订单号为%s的目的生产线不一致' % notsame_production_line_str)
+ else:
+ self.delivery_ids._delivery_avg()
+
+ def recognize_production(self):
+ # production_ids = []
+ # delivery_ids = []
+ if len(self.production_ids) > 4:
+ raise UserError('只能配送四个制造订单')
+ else:
+ if self.rfid_code:
+ wd = self.env['sf.workpiece.delivery'].search(
+ [('type', '=', self.delivery_ids[0].type), ('rfid_code', '=', self.rfid_code),
+ ('status', '=', self.delivery_ids[0].status)])
+ if wd:
+ if wd.production_line_id.id == self.delivery_ids[0].production_line_id.id:
+ # production_ids.append(wd.production_id)
+ # delivery_ids.append(wd.id)
+ # 将对象添加到对应的同模型且是多对多类型里
+ self.production_ids |= wd.production_id
+ self.delivery_ids |= wd
+ self.rfid_code = False
+ # self.production_ids = [(6, 0, production_ids)]
+ # self.delivery_ids = [(6, 0, delivery_ids)]
+ else:
+ raise UserError('该rfid对应的制造订单号为%s的目的生产线不一致' % wd.production_id.name)
+ return {
+ 'name': _('确认'),
+ 'type': 'ir.actions.act_window',
+ 'view_mode': 'form',
+ 'res_model': 'sf.workpiece.delivery.wizard',
+ 'target': 'new',
+ 'context': {
+ 'default_delivery_ids': [(6, 0, self.delivery_ids.ids)],
+ 'default_production_ids': [(6, 0, self.production_ids.ids)],
+ 'default_route_id': self.delivery_ids[0].route_id.id,
+ 'default_type': self.delivery_ids[0].type
+ }}
+
+ @api.onchange('route_id')
+ def onchange_route(self):
+ if self.route_id:
+ self.feeder_station_start_id = self.route_id.start_site_id.id
+ self.feeder_station_destination_id = self.route_id.end_site_id.id
diff --git a/sf_plan/models/custom_plan.py b/sf_plan/models/custom_plan.py
index fc38bc11..bcb15d69 100644
--- a/sf_plan/models/custom_plan.py
+++ b/sf_plan/models/custom_plan.py
@@ -84,7 +84,8 @@ class sf_production_plan(models.Model):
item.sudo().production_id.production_line_id = item.production_line_id.id
item.sudo().production_id.workorder_ids.filtered(
lambda b: b.routing_type == "装夹预调").workpiece_delivery_ids.write(
- {'production_line_id': item.production_line_id.id})
+ {'production_line_id': item.production_line_id.id,
+ 'plan_start_processing_time': item.plan_start_processing_time})
# item.sudo().production_id.plan_start_processing_time = item.date_planned_start
# @api.onchange('state')
diff --git a/sf_plan/views/view.xml b/sf_plan/views/view.xml
index 68772142..c240bed0 100644
--- a/sf_plan/views/view.xml
+++ b/sf_plan/views/view.xml
@@ -306,6 +306,14 @@
action="sf_manufacturing.sf_workpiece_delivery_act"
parent="mrp.menu_mrp_manufacturing"
/>
+
+
diff --git a/sf_sale/models/quick_easy_order.py b/sf_sale/models/quick_easy_order.py
index 1ece6b86..259655ee 100644
--- a/sf_sale/models/quick_easy_order.py
+++ b/sf_sale/models/quick_easy_order.py
@@ -17,7 +17,6 @@ from odoo.addons.sf_base.commons.common import Common
from . import parser_and_calculate_work_time as pc
-
class QuickEasyOrder(models.Model):
_name = 'quick.easy.order'
_description = '简易下单'
@@ -55,6 +54,7 @@ class QuickEasyOrder(models.Model):
('success', '成功'),
('fail', '失败')], string='模型上色状态')
processing_time = fields.Integer('加工时长(min)')
+ sale_order_id = fields.Many2one('sale.order', '销售订单号')
@api.depends('unit_price', 'quantity')
def _compute_total_amount(self):
@@ -89,7 +89,8 @@ class QuickEasyOrder(models.Model):
obj = super(QuickEasyOrder, self).create(vals)
# self.model_coloring(obj)
logging.info('---------开始派单到工厂-------')
- self.distribute_to_factory(obj)
+ sale_order = self.distribute_to_factory(obj)
+ obj.sale_order_id = sale_order.id
obj.state = '待接单'
return obj
diff --git a/sf_sale/models/quick_easy_order_old.py b/sf_sale/models/quick_easy_order_old.py
index 724d4369..768c483c 100644
--- a/sf_sale/models/quick_easy_order_old.py
+++ b/sf_sale/models/quick_easy_order_old.py
@@ -51,6 +51,7 @@ class QuickEasyOrder(models.Model):
('success', '成功'),
('fail', '失败')], string='模型上色状态')
processing_time = fields.Integer('加工时长(min)')
+ sale_order_id = fields.Many2one('sale.order', '销售订单号')
@api.depends('unit_price', 'quantity')
def _compute_total_amount(self):
@@ -79,12 +80,12 @@ class QuickEasyOrder(models.Model):
base64_datas = base64_data.decode('utf-8')
model_code = hashlib.sha1(base64_datas.encode('utf-8')).hexdigest()
report_path = attachment._full_path(attachment.store_fname)
- vals['model_file'] = self.transition_glb_file(report_path, model_code)
obj = super(QuickEasyOrder, self).create(vals)
logging.info('---------向cloud生成模型库记录-------')
self.model_coloring(obj)
logging.info('---------开始派单到工厂-------')
- self.distribute_to_factory(obj)
+ sale_order = self.distribute_to_factory(obj)
+ obj.sale_order_id = sale_order.id
obj.state = '待接单'
return obj
@@ -264,6 +265,7 @@ class QuickEasyOrder(models.Model):
product_bom_purchase = self.env['mrp.bom'].bom_create(product, 'normal', False)
product_bom_purchase.bom_create_line_has(purchase_embryo)
order_id.with_user(self.env.ref("base.user_admin")).sale_order_create_line(product, item)
+ return order_id
except Exception as e:
# self.cr.rollback()
return UserError('工厂创建销售订单和产品失败,请联系管理员')
diff --git a/sf_sale/models/sale_order.py b/sf_sale/models/sale_order.py
index 379ff199..64c64b52 100644
--- a/sf_sale/models/sale_order.py
+++ b/sf_sale/models/sale_order.py
@@ -85,9 +85,9 @@ class ReSaleOrder(models.Model):
self.check_status = 'pending'
def get_customer(self):
- partner_tag = self.env['res.partner.category'].search([('name', '=', '业务平台')], limit=1, order='id asc')
+ partner_tag = self.env['res.partner.category'].sudo().search([('name', '=', '业务平台')], limit=1, order='id asc')
if not partner_tag:
- partner_tag = self.env['res.partner.category'].create({'name': '平台客户'})
+ partner_tag = self.env['res.partner.category'].sudo().create({'name': '平台客户'})
customer = self.env['res.partner'].search([('name', '=', '业务平台')], limit=1, order='id asc')
if customer:
if not customer.vat:
@@ -141,6 +141,19 @@ class ResaleOrderLine(models.Model):
model_glb_file = fields.Binary('模型的glb文件')
check_status = fields.Selection(related='order_id.check_status')
+ @api.onchange('product_id')
+ def _compute_model_glb_file(self):
+ for line in self:
+ if line.product_template_id:
+ if not line.model_glb_file:
+ line.update({
+ 'model_glb_file': line.product_id.product_tmpl_id.model_file,
+ })
+ if not line.price_unit:
+ line.update({
+ 'price_unit': line.product_id.product_tmpl_id.list_price,
+ })
+
class ProductTemplate(models.Model):
_inherit = 'product.template'
@@ -151,23 +164,18 @@ class ProductTemplate(models.Model):
class RePurchaseOrder(models.Model):
_inherit = 'purchase.order'
- check_status = fields.Selection([('pending', '待审核'), ('approved', '已审核'), ('fail', '不通过')], '审核状态')
remark = fields.Text('备注')
user_id = fields.Many2one(
'res.users', string='买家', index=True, tracking=True,
compute='_compute_user_id',
store=True)
- def button_confirming(self):
- self.write({'state': 'purchase', 'check_status': 'pending'})
-
@api.depends('partner_id')
def _compute_user_id(self):
if not self.user_id:
if self.partner_id:
self.user_id = self.partner_id.purchase_user_id.id
- self.check_status = 'pending'
- self.state = 'purchase'
+ # self.state = 'purchase'
else:
self.user_id = self.env.user.id
@@ -190,28 +198,6 @@ class RePurchaseOrder(models.Model):
if not line.taxes_id:
raise UserError('请对【产品】中的【税】进行选择')
- def write(self, vals):
- if self.env.user.has_group('sf_base.group_purchase_director'):
- if vals.get('check_status'):
- if vals['check_status'] in ('pending', False):
- vals['check_status'] = 'approved'
- return super().write(vals)
-
- def button_confirm(self):
- for order in self:
- if order.state not in ['draft', 'sent', 'purchase']:
- continue
- order.order_line._validate_analytic_distribution()
- order._add_supplier_to_product()
- # Deal with double validation process
- if order._approval_allowed():
- order.button_approve()
- else:
- order.write({'state': 'to approve'})
- if order.partner_id not in order.message_partner_ids:
- order.message_subscribe([order.partner_id.id])
- return True
-
@api.onchange('order_line')
def _onchange_order_line(self):
for order in self:
diff --git a/sf_sale/security/group_security.xml b/sf_sale/security/group_security.xml
index c6af4225..5f23b5bb 100644
--- a/sf_sale/security/group_security.xml
+++ b/sf_sale/security/group_security.xml
@@ -99,6 +99,26 @@
+
+
+
+ 销售经理只可以查看本人的快速订单
+
+ [('create_uid', '=',user.id)]
+
+
+
+
+
+
+
+ 销售总监查看所有快速订单
+
+ [(1,'=',1)]
+
+
+
+
\ No newline at end of file
diff --git a/sf_sale/security/ir.model.access.csv b/sf_sale/security/ir.model.access.csv
index 451396e3..0e2f0684 100644
--- a/sf_sale/security/ir.model.access.csv
+++ b/sf_sale/security/ir.model.access.csv
@@ -32,8 +32,8 @@ access_stock_picking_group_quality_director,stock_picking_group_quality_director
access_account_move_group_sale_salemanager,account_move_group_sale_salemanager,account.model_account_move,sf_base.group_sale_salemanager,1,0,0,0
access_account_move_group_sale_director,account_move_group_sale_director,account.model_account_move,sf_base.group_sale_director,1,0,0,0
access_resource_resource_group_sale_director,resource_resource_group_sale_director,resource.model_resource_resource,sf_base.group_sale_director,1,1,1,0
-access_mrp_bom_group_sale_salemanager,mrp_bom_group_sale_salemanager,mrp.model_mrp_bom,sf_base.group_sale_salemanager,1,0,0,0
-access_mrp_bom_group_sale_director,mrp_bom_group_sale_director,mrp.model_mrp_bom,sf_base.group_sale_director,1,0,0,0
+access_mrp_bom_group_sale_salemanager,mrp_bom_group_sale_salemanager,mrp.model_mrp_bom,sf_base.group_sale_salemanager,1,0,1,0
+access_mrp_bom_group_sale_director,mrp_bom_group_sale_director,mrp.model_mrp_bom,sf_base.group_sale_director,1,0,1,0
access_mrp_bom_group_purchase,mrp_bom_group_purchase,mrp.model_mrp_bom,sf_base.group_purchase,1,0,0,0
access_mrp_bom_group_purchase_director,mrp_bom_group_purchase_director,mrp.model_mrp_bom,sf_base.group_purchase_director,1,0,0,0
access_mrp_bom_group_quality,mrp_bom_group_quality,mrp.model_mrp_bom,sf_base.group_quality,1,0,0,0
diff --git a/sf_sale/views/purchase_order_view.xml b/sf_sale/views/purchase_order_view.xml
index 90c9bd4b..b0a4ed98 100644
--- a/sf_sale/views/purchase_order_view.xml
+++ b/sf_sale/views/purchase_order_view.xml
@@ -8,28 +8,15 @@
-
-
+
-
-
-
-
-
@@ -93,43 +80,35 @@
sf_base.group_purchase,sf_base.group_purchase_director
- {'readonly': ['&',('state', 'in', ['purchase']),('check_status','in',
- ['approved'])]}
+ {'readonly': [('state', 'in', ['purchase'])]}
- {'readonly': ['&',('state', 'in', ['purchase']),('check_status','in',
- ['pending','approved'])]}
+ {'readonly': [('state', 'in', ['purchase'])]}
- {'readonly': ['&',('state', 'in', ['purchase']),('check_status','in',
- ['pending','approved'])]}
+ {'readonly': [('state', 'in', ['purchase'])]}
- {'readonly': ['&',('state', 'in', ['purchase']),('check_status','in',
- ['pending','approved'])]}
+ {'readonly': [('state', 'in', ['purchase'])]}
- {'readonly': ['&',('state', 'in', ['purchase']),('check_status','in',
- ['pending','approved'])]}
+ {'readonly': [('state', 'in', ['purchase'])]}
- {'readonly': ['&',('state', 'in', ['purchase']),('check_status','in',
- ['pending','approved'])]}
+ {'readonly': [('state', 'in', ['purchase'])]}
- {'readonly': ['&',('state', 'in', ['purchase']),('check_status','in',
- ['pending','approved'])]}
+ {'readonly': [('state', 'in', ['purchase'])]}
- {'readonly': ['&',('state', 'in', ['purchase']),('check_status','in',
- ['pending','approved'])]}
+ {'readonly': [('state', 'in', ['purchase'])]}
@@ -153,13 +132,10 @@
- {'readonly': ['&',('state', 'in', ['purchase']),('check_status','in',
- ['pending','approved'])]}
-
+ {'readonly': [('state', 'in', ['purchase'])]}
- {'readonly': ['&',('state', 'in', ['purchase']),('check_status','in',
- ['pending','approved'])]}
+ {'readonly': [('state', 'in', ['purchase'])]}
@@ -171,14 +147,8 @@
- check_status desc,date_approve asc
+ date_approve asc
-
-
-
diff --git a/sf_sale/views/quick_easy_order_view.xml b/sf_sale/views/quick_easy_order_view.xml
index 3038b987..896d3704 100644
--- a/sf_sale/views/quick_easy_order_view.xml
+++ b/sf_sale/views/quick_easy_order_view.xml
@@ -26,6 +26,7 @@
+
@@ -78,6 +79,8 @@
+
diff --git a/sf_tool_management/__manifest__.py b/sf_tool_management/__manifest__.py
index fc556283..64d08d05 100644
--- a/sf_tool_management/__manifest__.py
+++ b/sf_tool_management/__manifest__.py
@@ -19,9 +19,9 @@
'views/functional_tool_views.xml',
'views/mrp_workcenter_views.xml',
'views/sf_maintenance_equipment.xml',
- 'views/menu_view.xml',
'views/tool_material_search.xml',
'views/fixture_material_search_views.xml',
+ 'views/menu_view.xml',
'data/tool_data.xml',
],
'demo': [
diff --git a/sf_tool_management/models/base.py b/sf_tool_management/models/base.py
index ed1cf129..b00ab209 100644
--- a/sf_tool_management/models/base.py
+++ b/sf_tool_management/models/base.py
@@ -345,7 +345,7 @@ class CAMWorkOrderProgramKnifePlan(models.Model):
# 创建装刀请求
knife_plan.apply_for_tooling()
else:
- logging.info('功能刀具【%s】满足CNC用刀需求!!!')
+ logging.info('功能刀具【%s】满足CNC用刀需求!!!' % cnc_processing.cutting_tool_name)
class FunctionalToolAssembly(models.Model):
@@ -769,6 +769,15 @@ class FunctionalToolDismantle(models.Model):
chuck_freight_id = fields.Many2one('sf.shelf.location', '夹头目标货位',
domain="[('product_id', 'in', (chuck_product_id, False))]")
+ @api.onchange('functional_tool_id')
+ def _onchange_freight(self):
+ for item in self:
+ item.integral_freight_id = False
+ item.blade_freight_id = False
+ item.bar_freight_id = False
+ item.pad_freight_id = False
+ item.chuck_freight_id = False
+
@api.depends('functional_tool_id')
def _compute_functional_tool_num(self):
for item in self:
@@ -807,10 +816,12 @@ class FunctionalToolDismantle(models.Model):
raise ValidationError('Rfid为【%s】的功能刀具已经拆解,请勿重复操作!' % self.functional_tool_id.rfid_dismantle)
location = self.env['stock.location'].search([('name', '=', '刀具组装位置')])
location_dest = self.env['stock.location'].search([('name', '=', '刀具房')])
- # =================刀柄是否报废拆解=======
+ # =================刀柄是否[报废]拆解=======
location_dest_scrap = self.env['stock.location'].search([('name', '=', 'Scrap')])
if self.handle_rfid:
lot = self.env['stock.lot'].sudo().search([('rfid', '=', self.handle_rfid)])
+ if not lot:
+ raise ValidationError('Rfid为【%s】的功能刀具序列号不存在!' % self.handle_rfid)
functional_tool_assembly = self.functional_tool_id.functional_tool_name_id
if self.scrap_boolean:
# 刀柄报废 入库到Scrap
@@ -820,34 +831,34 @@ class FunctionalToolDismantle(models.Model):
# 刀柄不报废 入库到刀具房
lot.create_stock_quant(location, location_dest, functional_tool_assembly.id, '功能刀具拆解',
functional_tool_assembly, functional_tool_assembly.tool_groups_id)
- # ==============功能刀具报废拆解================
+ # ==============功能刀具[报废]拆解================
if self.dismantle_cause in ['寿命到期报废', '崩刀报废']:
# 除刀柄外物料报废 入库到Scrap
- if self.integral_freight_id:
+ if self.integral_product_id:
self.integral_product_id.dismantle_stock_moves(False, location, location_dest_scrap)
- elif self.blade_freight_id:
+ elif self.blade_product_id:
self.blade_product_id.dismantle_stock_moves(False, location, location_dest_scrap)
- if self.bar_freight_id:
+ if self.bar_product_id:
self.bar_product_id.dismantle_stock_moves(False, location, location_dest_scrap)
- elif self.pad_freight_id:
+ elif self.pad_product_id:
self.pad_product_id.dismantle_stock_moves(False, location, location_dest_scrap)
- if self.chuck_freight_id:
+ if self.chuck_product_id:
self.chuck_product_id.dismantle_stock_moves(False, location, location_dest_scrap)
- # ===========功能刀具磨削拆解==============
+ # ===========功能刀具[磨削]拆解==============
elif self.dismantle_cause in ['刀具需磨削']:
location_dest = self.env['stock.location'].search([('name', '=', '磨削房')])
# 除刀柄外物料拆解 入库到具体库位
- if self.integral_freight_id:
+ if self.integral_product_id:
self.integral_product_id.dismantle_stock_moves(False, location, location_dest)
- elif self.blade_freight_id:
+ elif self.blade_product_id:
self.blade_product_id.dismantle_stock_moves(False, location, location_dest)
- if self.bar_freight_id:
+ if self.bar_product_id:
self.bar_product_id.dismantle_stock_moves(False, location, location_dest)
- elif self.pad_freight_id:
+ elif self.pad_product_id:
self.pad_product_id.dismantle_stock_moves(False, location, location_dest)
- if self.chuck_freight_id:
+ if self.chuck_product_id:
self.chuck_product_id.dismantle_stock_moves(False, location, location_dest)
- # ==============功能刀具更换拆解==============
+ # ==============功能刀具[更换]拆解==============
elif self.dismantle_cause in ['更换为其他刀具']:
# 除刀柄外物料拆解 入库到具体库位
if self.integral_freight_id:
@@ -875,7 +886,7 @@ class FunctionalToolDismantle(models.Model):
'rfid': '',
'state': '已拆解'
})
- logging.info('刀具拆解成功!')
+ logging.info('【%s】刀具拆解成功!' % self.name)
class ProductProduct(models.Model):
@@ -895,12 +906,12 @@ class ProductProduct(models.Model):
location = self.env['sf.shelf.location'].sudo().search([('barcode', '=', shelf_location_barcode)])
location.product_num = location.product_num + 1
else:
- location = False
+ location = self.env['sf.shelf.location']
# 创建移动历史记录
stock_move_line_id = self.env['stock.move.line'].sudo().create({
'product_id': self.id,
'move_id': stock_move_id.id,
- 'current_location_id': location.id or False,
+ 'current_location_id': location.id,
'install_tool_time': fields.Datetime.now(),
'qty_done': 1.0,
'state': 'done',
diff --git a/sf_tool_management/views/fixture_material_search_views.xml b/sf_tool_management/views/fixture_material_search_views.xml
index c8f6592f..429be323 100644
--- a/sf_tool_management/views/fixture_material_search_views.xml
+++ b/sf_tool_management/views/fixture_material_search_views.xml
@@ -61,6 +61,7 @@
+
@@ -94,12 +95,4 @@
sf.fixture.material.search
tree,form
-
-
\ No newline at end of file
diff --git a/sf_tool_management/views/menu_view.xml b/sf_tool_management/views/menu_view.xml
index c964bb35..1228fe07 100644
--- a/sf_tool_management/views/menu_view.xml
+++ b/sf_tool_management/views/menu_view.xml
@@ -120,5 +120,13 @@
parent="menu_sf_tool_material"
/>
+
+
diff --git a/sf_tool_management/views/tool_base_views.xml b/sf_tool_management/views/tool_base_views.xml
index 274d3bcc..06a60b1c 100644
--- a/sf_tool_management/views/tool_base_views.xml
+++ b/sf_tool_management/views/tool_base_views.xml
@@ -846,7 +846,8 @@
attrs="{'invisible': [('dismantle_cause', 'not in', ['寿命到期报废','崩刀报废'])], 'readonly': [('state', '=', '已拆解')]}"/>
-
+
@@ -855,7 +856,8 @@
+ attrs="{'invisible': [('dismantle_cause', 'not in', ['更换为其他刀具'])], 'readonly': [('state', '=', '已拆解')],
+ 'required': [('chuck_product_id', '!=', False),('dismantle_cause', 'in', ['更换为其他刀具'])]}"/>
@@ -869,7 +871,8 @@
+ attrs="{'invisible': [('dismantle_cause', 'not in', ['更换为其他刀具'])], 'readonly': [('state', '=', '已拆解')],
+ 'required': [('integral_product_id', '!=', False),('dismantle_cause', 'in', ['更换为其他刀具'])]}"/>
@@ -884,7 +887,8 @@
+ attrs="{'invisible': [('dismantle_cause', 'not in', ['更换为其他刀具'])], 'readonly': [('state', '=', '已拆解')],
+ 'required': [('blade_product_id', '!=', False),('dismantle_cause', 'in', ['更换为其他刀具'])]}"/>
@@ -896,7 +900,8 @@
+ attrs="{'invisible': [('dismantle_cause', 'not in', ['更换为其他刀具'])], 'readonly': [('state', '=', '已拆解')],
+ 'required': [('bar_product_id', '!=', False),('dismantle_cause', 'in', ['更换为其他刀具'])]}"/>
@@ -908,7 +913,8 @@
+ attrs="{'invisible': [('dismantle_cause', 'not in', ['更换为其他刀具'])], 'readonly': [('state', '=', '已拆解')],
+ 'required': [('pad_product_id', '!=', False), ('dismantle_cause', 'in', ['更换为其他刀具'])]}"/>
@@ -944,7 +950,5 @@
{'search_default_no_dismantle_state':1}
-
-
diff --git a/sf_tool_management/views/tool_material_search.xml b/sf_tool_management/views/tool_material_search.xml
index d4772244..eb7a2418 100644
--- a/sf_tool_management/views/tool_material_search.xml
+++ b/sf_tool_management/views/tool_material_search.xml
@@ -61,6 +61,7 @@
+
diff --git a/sf_tool_management/wizard/wizard.py b/sf_tool_management/wizard/wizard.py
index 7a4f1553..a77c9010 100644
--- a/sf_tool_management/wizard/wizard.py
+++ b/sf_tool_management/wizard/wizard.py
@@ -246,9 +246,20 @@ class FunctionalToolAssemblyOrder(models.TransientModel):
if self.integral_freight_barcode:
location = self.env['sf.shelf.location'].sudo().search([('barcode', '=', self.integral_freight_barcode)])
if location:
- self.integral_product_id = location.product_id.id
+ if not location.product_id:
+ raise ValidationError('编码为【%s】的货位为空货位!' % location.barcode)
+ else:
+ material_name_id = location.product_id.cutting_tool_material_id
+ if material_name_id and material_name_id.name == '整体式刀具':
+ if location.product_num == 0:
+ raise ValidationError('编码为【%s】的货位的产品库存数量为0,请重新选择!' % location.barcode)
+ self.integral_product_id = location.product_id.id
+ else:
+ raise ValidationError(
+ '编码为【%s】的货位存放的产品为【%s】,不是整体式刀具,请重新选择!' % (
+ location.barcode, location.product_id.name))
else:
- self.integral_product_id = False
+ raise ValidationError('编码为【%s】的货位不存在!' % self.integral_freight_barcode)
# ===============刀片型号====================
blade_freight_barcode = fields.Char('刀片货位')
@@ -265,9 +276,20 @@ class FunctionalToolAssemblyOrder(models.TransientModel):
if self.blade_freight_barcode:
location = self.env['sf.shelf.location'].sudo().search([('barcode', '=', self.blade_freight_barcode)])
if location:
- self.blade_product_id = location.product_id.id
+ if not location.product_id:
+ raise ValidationError('编码为【%s】的货位为空货位!' % location.barcode)
+ else:
+ material_name_id = location.product_id.cutting_tool_material_id
+ if material_name_id and material_name_id.name == '刀片':
+ if location.product_num == 0:
+ raise ValidationError('编码为【%s】的货位的产品库存数量为0,请重新选择!' % location.barcode)
+ self.blade_product_id = location.product_id.id
+ else:
+ raise ValidationError(
+ '编码为【%s】的货位存放的产品为【%s】,不是刀片,请重新选择!' % (
+ location.barcode, location.product_id.name))
else:
- self.blade_product_id = False
+ raise ValidationError('编码为【%s】的货位不存在!' % self.blade_freight_barcode)
# ====================刀杆型号==================
bar_freight_barcode = fields.Char('刀杆货位')
@@ -284,9 +306,20 @@ class FunctionalToolAssemblyOrder(models.TransientModel):
if self.bar_freight_barcode:
location = self.env['sf.shelf.location'].sudo().search([('barcode', '=', self.bar_freight_barcode)])
if location:
- self.bar_product_id = location.product_id.id
+ if not location.product_id:
+ raise ValidationError('编码为【%s】的货位为空货位!' % location.barcode)
+ else:
+ material_name_id = location.product_id.cutting_tool_material_id
+ if material_name_id and material_name_id.name == '刀杆':
+ if location.product_num == 0:
+ raise ValidationError('编码为【%s】的货位的产品库存数量为0,请重新选择!' % location.barcode)
+ self.bar_product_id = location.product_id.id
+ else:
+ raise ValidationError(
+ '编码为【%s】的货位存放的产品为【%s】,不是刀杆,请重新选择!' % (
+ location.barcode, location.product_id.name))
else:
- self.bar_product_id = False
+ raise ValidationError('编码为【%s】的货位不存在!' % self.bar_freight_barcode)
# ===============刀盘型号===================
pad_freight_barcode = fields.Char('刀盘货位')
@@ -303,9 +336,20 @@ class FunctionalToolAssemblyOrder(models.TransientModel):
if self.pad_freight_barcode:
location = self.env['sf.shelf.location'].sudo().search([('barcode', '=', self.pad_freight_barcode)])
if location:
- self.pad_product_id = location.product_id.id
+ if not location.product_id:
+ raise ValidationError('编码为【%s】的货位为空货位!' % location.barcode)
+ else:
+ material_name_id = location.product_id.cutting_tool_material_id
+ if material_name_id and material_name_id.name == '刀盘':
+ if location.product_num == 0:
+ raise ValidationError('编码为【%s】的货位的产品库存数量为0,请重新选择!' % location.barcode)
+ self.pad_product_id = location.product_id.id
+ else:
+ raise ValidationError(
+ '编码为【%s】的货位存放的产品为【%s】,不是刀盘,请重新选择!' % (
+ location.barcode, location.product_id.name))
else:
- self.pad_product_id = False
+ raise ValidationError('编码为【%s】的货位不存在!' % self.pad_freight_barcode)
# ================刀柄型号===============
handle_freight_rfid = fields.Char('刀柄Rfid', compute='_compute_rfid')
@@ -342,12 +386,22 @@ class FunctionalToolAssemblyOrder(models.TransientModel):
if self.chuck_freight_barcode:
location = self.env['sf.shelf.location'].sudo().search([('barcode', '=', self.chuck_freight_barcode)])
if location:
- self.chuck_product_id = location.product_id.id
+ if not location.product_id:
+ raise ValidationError('编码为【%s】的货位为空货位!' % location.barcode)
+ else:
+ material_name_id = location.product_id.cutting_tool_material_id
+ if material_name_id and material_name_id.name == '夹头':
+ if location.product_num == 0:
+ raise ValidationError('编码为【%s】的货位的产品库存数量为0,请重新选择!' % location.barcode)
+ self.chuck_product_id = location.product_id.id
+ else:
+ raise ValidationError(
+ '编码为【%s】的货位存放的产品为【%s】,不是夹头,请重新选择!' % (
+ location.barcode, location.product_id.name))
else:
- self.chuck_product_id = False
+ raise ValidationError('编码为【%s】的货位不存在!' % self.chuck_freight_barcode)
# ========================================
-
def on_barcode_scanned(self, barcode):
"""
智能工厂组装单处扫码绑定刀具物料
diff --git a/sf_warehouse/models/model.py b/sf_warehouse/models/model.py
index fdbd0c33..e526d5b9 100644
--- a/sf_warehouse/models/model.py
+++ b/sf_warehouse/models/model.py
@@ -838,7 +838,7 @@ class Sf_stock_move_line(models.Model):
if obj:
obj.product_id = record.product_id.id
# obj.location_status = '占用'
- obj.product_num += record.reserved_uom_qty
+ obj.product_num += record.qty_done
@api.onchange('destination_location_id')
def _check_destination_location_id(self):
diff --git a/sf_warehouse/security/ir.model.access.csv b/sf_warehouse/security/ir.model.access.csv
index a5b3b2fa..8755d7a1 100644
--- a/sf_warehouse/security/ir.model.access.csv
+++ b/sf_warehouse/security/ir.model.access.csv
@@ -136,6 +136,8 @@ access_sf_shelf_location_wizard_group_plan_dispatch,sf_shelf_location_wizard_gro
access_sf_shelf_location_wizard_group_sf_stock_user_group_sf_stock_user,sf_shelf_location_wizard_group_sf_stock_user_group_sf_stock_user,model_sf_shelf_location_wizard,sf_warehouse.group_sf_stock_user,1,0,0,0
access_sf_shelf_location_wizard_group_sf_stock_manager,sf_shelf_location_wizard_group_sf_stock_manager,model_sf_shelf_location_wizard,sf_warehouse.group_sf_stock_manager,1,1,1,0
+access_sf_shelf_location_group_sf_tool_user,sf.shelf.location.group_sf_tool_user,model_sf_shelf_location,sf_base.group_sf_tool_user,1,1,0,0
+access_sf_shelf_group_user,sf.shelf.location.group_user,model_sf_shelf_location,base.group_user,1,1,0,0
diff --git a/sf_warehouse/views/shelf_location.xml b/sf_warehouse/views/shelf_location.xml
index 90783fca..2f6317b9 100644
--- a/sf_warehouse/views/shelf_location.xml
+++ b/sf_warehouse/views/shelf_location.xml
@@ -246,6 +246,7 @@
+