This commit is contained in:
jinling.yang
2024-08-20 14:34:34 +08:00
48 changed files with 1856 additions and 6420 deletions

View File

@@ -53,6 +53,23 @@ const tableRequiredList = [
]
patch(FormStatusIndicator.prototype, 'jikimo_frontend.FormStatusIndicator', {
setup() {
owl.onMounted(() => {
try {
const dom = this.__owl__.bdom.el
const buttonsDom = $(dom).find('.o_form_status_indicator_buttons ')
if (buttonsDom) {
const dom1 = buttonsDom.children('.o_form_button_save')
const dom2 = buttonsDom.children('.o_form_button_cancel')
dom1.append('保存')
dom2.append('取消')
}
} catch (e) {
console.log(e)
}
});
},
// 你可以重写或者添加一些方法和属性
async _onDiscardChanges() {
// var self = this;
@@ -183,17 +200,6 @@ patch(ListRenderer.prototype, 'jikimo_frontend.ListRenderer', {
// })
$(function () {
document.addEventListener('click', function () {
const dom = $('.o_form_status_indicator_buttons ')
if (dom) {
const dom1 = dom.children().eq(0)
const dom2 = dom.children().eq(1)
if (!dom1.text()) {
dom1.append('保存')
dom2.append('取消')
}
}
})
function customRequired() {
let timer = null

View File

@@ -30,6 +30,7 @@
'views/machine_info_present.xml',
'views/delivery_record.xml',
'views/res_config_settings_views.xml',
'views/maintenance_views.xml',
],
'assets': {

View File

@@ -1,11 +1,45 @@
# -*- coding: utf-8 -*-
import re
import ast
import json
import logging
from datetime import datetime
from odoo import http
from odoo.http import request
def convert_to_seconds(time_str):
# 修改正则表达式,使 H、M、S 部分可选
pattern = r"(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?"
match = re.match(pattern, time_str)
if match:
# 提取各时间单位如果某个单位缺失则默认设为0
hours = int(match.group(1)) if match.group(1) else 0
minutes = int(match.group(2)) if match.group(2) else 0
seconds = int(match.group(3)) if match.group(3) else 0
# 计算总秒数
total_seconds = hours * 3600 + minutes * 60 + seconds
if total_seconds == 0:
# return None
pattern = r"(?:(\d+)小时)?(?:(\d+)分钟)?(?:(\d+)秒)?"
match = re.match(pattern, time_str)
if match:
# 提取各时间单位如果某个单位缺失则默认设为0
hours = int(match.group(1)) if match.group(1) else 0
minutes = int(match.group(2)) if match.group(2) else 0
seconds = int(match.group(3)) if match.group(3) else 0
# 计算总秒数
total_seconds = hours * 3600 + minutes * 60 + seconds
return total_seconds
else:
return None
return total_seconds
class Sf_Dashboard_Connect(http.Controller):
@http.route('/api/get_machine_datas/list', type='http', auth='public', methods=['GET', 'POST'], csrf=False,
@@ -18,6 +52,11 @@ class Sf_Dashboard_Connect(http.Controller):
"""
res = {'status': 1, 'message': '成功', 'data': []}
logging.info('前端请求机床数据的参数为:%s' % kw)
# 获取当前时间的时间戳
current_timestamp = datetime.now().timestamp()
print(current_timestamp)
# tem_list = [
# "XT-GNJC-WZZX-X800-Y550-Z550-T24-A5-1", "XT-GNJC-LSZX-X800-Y550-Z550-T24-A3-3",
# "XT-GNJC-LSZX-X800-Y550-Z550-T24-A3-4", "XT-GNJC-LSZX-X800-Y550-Z550-T24-A3-5",
@@ -33,10 +72,24 @@ class Sf_Dashboard_Connect(http.Controller):
machine_list = ast.literal_eval(kw['machine_list'])
for item in machine_list:
machine_data = equipment_obj.search([('code', '=', item)])
# 机床上线时间段
first_online_duration = current_timestamp - int(machine_data.first_online_time.timestamp())
power_off_time = None
power_off_rate = None
if machine_data.machine_power_on_time:
power_off_time = first_online_duration - convert_to_seconds(machine_data.machine_power_on_time)
power_off_rate = round((power_off_time / first_online_duration), 3)
else:
power_off_time = False
power_off_rate = False
if machine_data:
res['data'].append({
'active': machine_data.status,
'id': machine_data.id,
'name': machine_data.name,
'brand': machine_data.type_id.name,
'code': machine_data.code,
'status': machine_data.status,
'run_status': machine_data.run_status,
@@ -88,6 +141,16 @@ class Sf_Dashboard_Connect(http.Controller):
'alarm_time': machine_data.alarm_time,
'alarm_msg': machine_data.alarm_msg,
'clear_time': machine_data.clear_time,
# 计算出来的数据
# 开动率:运行时间/通电时间
'run_rate': machine_data.run_rate,
# 关机时长:初次上线时间 - 通电时间
'power_off_time': power_off_time,
# 关机率:关机时长/初次上线时间
'power_off_rate': power_off_rate,
'first_online_duration': first_online_duration,
# 停机时间:关机时间 - 运行时间
# 停机时长:关机时间 - 初次上线时间
})
return json.JSONEncoder().encode(res)
@@ -96,3 +159,126 @@ class Sf_Dashboard_Connect(http.Controller):
res['status'] = -1
res['message'] = '前端请求机床数据失败,原因:%s' % e
return json.JSONEncoder().encode(res)
# @http.route('/api/logs/list', type='http', auth='public', methods=['GET', 'POST'], csrf=False,
# cors="*")
# def logs_list(self, **kw):
# """
# 拿到日志数据返回给大屏展示
# :param kw:
# :return:
# """
# res = {'status': 1, 'message': '成功', 'data': []}
# logging.info('前端请求日志数据的参数为:%s' % kw)
#
# try:
# # 获取请求的日志数据
# logs_obj = request.env['maintenance.equipment.oee.log.detail'].sudo()
# # 获取请求的机床数据
# machine_list = ast.literal_eval(kw['machine_list'])
# begin_time_str = kw['begin_time'].strip('"')
# end_time_str = kw['end_time'].strip('"')
#
# begin_time = datetime.strptime(begin_time_str, '%Y-%m-%d %H:%M:%S')
# end_time = datetime.strptime(end_time_str, '%Y-%m-%d %H:%M:%S')
#
# print('begin_time: %s' % begin_time)
# for item in machine_list:
# log_datas = logs_obj.search(
# [('equipment_code', '=', item), ('time', '>=', begin_time), ('time', '<=', end_time)])
# print('log_datas: %s' % log_datas)
# for log_data in log_datas:
# res['data'].append({
# 'equipment_code': log_data.equipment_code,
# 'time': log_data.time.strftime('%Y-%m-%d %H:%M:%S'),
# 'state': log_data.state
#
# })
#
# return json.JSONEncoder().encode(res)
#
# except Exception as e:
# logging.info('前端请求日志数据失败,原因:%s' % e)
# res['status'] = -1
# res['message'] = '前端请求日志数据失败,原因:%s' % e
# return json.JSONEncoder().encode(res)
@http.route('/api/logs/list', type='http', auth='public', methods=['GET', 'POST'], csrf=False, cors="*")
def logs_list(self, **kw):
"""
拿到日志数据返回给大屏展示
:param kw:
:return:
"""
res = {'status': 1, 'message': '成功', 'data': {}}
logging.info('前端请求日志数据的参数为:%s' % kw)
try:
# 获取请求的日志数据
logs_obj = request.env['maintenance.equipment.oee.log.detail'].sudo()
# 获取请求的机床数据
machine_list = ast.literal_eval(kw['machine_list'])
begin_time_str = kw['begin_time'].strip('"')
end_time_str = kw['end_time'].strip('"')
begin_time = datetime.strptime(begin_time_str, '%Y-%m-%d %H:%M:%S')
end_time = datetime.strptime(end_time_str, '%Y-%m-%d %H:%M:%S')
print('begin_time: %s' % begin_time)
for item in machine_list:
log_datas = logs_obj.search(
[('equipment_code', '=', item), ('time', '>=', begin_time), ('time', '<=', end_time)])
print('log_datas: %s' % log_datas)
# 将数据按照 equipment_code 进行分组
if item not in res['data']:
res['data'][item] = []
for log_data in log_datas:
res['data'][item].append({
'time': log_data.time.strftime('%Y-%m-%d %H:%M:%S'),
'state': log_data.state,
'production_name': log_data.production_name,
})
return json.dumps(res) # 注意使用 json.dumps 而不是直接用 json.JSONEncoder().encode()
except Exception as e:
logging.info('前端请求日志数据失败,原因:%s' % e)
res['status'] = -1
res['message'] = '前端请求日志数据失败,原因:%s' % e
return json.dumps(res)
# 返回CNC机床列表
@http.route('/api/CNCList', type='http', auth='public', methods=['GET', 'POST'], csrf=False,
cors="*")
def CNCList(self, **kw):
"""
获取CNC机床列表
:param kw:
:return:
"""
# logging.info('CNCList:%s' % kw)
try:
res = {'Succeed': True}
# cnc_list = request.env['sf.cnc.equipment'].sudo().search([])
# cnc_list = ["XT-GNJC-WZZX-X800-Y550-Z550-T24-A5-1", "XT-GNJC-LSZX-X800-Y550-Z550-T24-A3-3",
# "XT-GNJC-LSZX-X800-Y550-Z550-T24-A3-4", "XT-GNJC-LSZX-X800-Y550-Z550-T24-A3-5",
# "XT-GNJC-LSZX-X800-Y550-Z550-T24-A3-6", "XT-GNJC-LSZX-X800-Y550-Z550-T24-A3-7",
# "XT-GNJC-LSZX-X800-Y550-Z550-T24-A3-8", "XT-GNJC-WZZX-X800-Y550-Z550-T24-A5-2",
# "XT-GNJC-GSZG-X600-Y400-Z350-T21-A3-9", "XT-GNJC-GSZG-X600-Y400-Z350-T21-A3-10",
# "XT-GNJC-GSZG-X600-Y400-Z350-T21-A3-11", "XT-GNJC-GSZG-X600-Y400-Z350-T21-A3-12",
# "XT-GNJC-GSZG-X600-Y400-Z350-T21-A3-13", "XT-GNJC-GSZG-X600-Y400-Z350-T21-A3-14"]
cnc_list_obj = request.env['maintenance.equipment'].sudo().search(
[('function_type', '!=', False), ('active', '=', True)])
cnc_list = list(map(lambda x: x.code, cnc_list_obj))
print('cnc_list: %s' % cnc_list)
res['CNCList'] = cnc_list
except Exception as e:
res = {'Succeed': False, 'ErrorCode': 202, 'Error': e}
logging.info('CNCList error:%s' % e)
return json.JSONEncoder().encode(res)

View File

@@ -2,3 +2,4 @@ from . import ftp_client
from . import ftp_operate
from . import py2opcua
from . import res_config_setting
from . import mrp_workorder

View File

@@ -121,6 +121,13 @@ class Machine_ftp(models.Model):
"""
_inherit = 'maintenance.equipment'
# 机床首次上线时间(默认取值2024年08月01日零点)
def _get_default_online_time(self):
return datetime(2024, 1, 1, 0, 0, 0)
first_online_time = fields.Datetime(string='首次上线时间', default=_get_default_online_time)
# workorder_ids = fields.One2many('mrp.workorder', 'machine_tool_id', string='工单')
# # 机床配置项目
@@ -275,7 +282,28 @@ class Machine_ftp(models.Model):
alarm_msg = fields.Char('故障报警信息', readonly=True)
clear_time = fields.Char('故障消除时间(复原时间)', readonly=True)
# 当前程序名, 机床累计运行时间, 机床系统日期, 机床系统时间, 当前刀具号, 机床循环时间
# # 开动率
run_rate = fields.Char('开动率', readonly=True)
# 同步CNC设备到oee
def sync_oee(self):
"""
同步CNC设备到oee
:return:
"""
for record in self:
record.ensure_one()
cnc_oee_dict = {
'equipment_id': record.id,
'type_id': record.type_id.id,
'machine_tool_picture': record.machine_tool_picture,
'equipment_code': record.code,
'function_type': record.function_type,
}
if self.env['maintenance.equipment.oee.logs'].search([('equipment_id', '=', record.id)]):
self.env['maintenance.equipment.oee.logs'].write(cnc_oee_dict)
else:
self.env['maintenance.equipment.oee.logs'].create(cnc_oee_dict)
class WorkCenterBarcode(models.Model):

View File

@@ -0,0 +1,38 @@
import re
from odoo import fields, models, api
class ResMrpWorkOrder(models.Model):
_inherit = 'mrp.workorder'
mixed_search_field = fields.Char(string='坯料产品名称/RFID')
@api.model
def web_read_group(self, domain, fields, groupby, limit=None, offset=0, orderby=False,
lazy=True, expand=False, expand_limit=None, expand_orderby=False):
domain = domain or []
for index, item in enumerate(domain):
if isinstance(item, list):
if item[0] == 'mixed_search_field':
if self._is_rfid_code(item[2]):
domain[index] = ['rfid_code', item[1], item[2]]
else:
domain[index] = ['product_tmpl_name', item[1], item[2]]
return super(ResMrpWorkOrder, self).web_read_group(domain, fields, groupby, limit=limit, offset=offset, orderby=orderby,
lazy=lazy, expand=expand, expand_limit=expand_limit, expand_orderby=expand_orderby)
def _is_rfid_code(self, tag):
"""
判断是否是rfid_code
"""
# 基于长度判断假设RFID标签长度为10到16个字符
if not 10 <= len(tag) <= 16:
return False
# 基于字符集判断(仅包含数字和字母)
if not re.match("^[0-9]*$", tag):
return False
return True

View File

@@ -26,6 +26,7 @@
<filter string="自动编程" name="no_manual_quotation" domain="[('manual_quotation', '=', False)]"/>
</xpath>
<xpath expr="//field[@name='production_id']" position="before">
<field name="mixed_search_field"/>
<field name="product_tmpl_name"/>
<field name="rfid_code"/>
</xpath>

View File

@@ -18,6 +18,7 @@
<field name="run_status"/>
<field name="run_time"/>
<field name="system_date"/>
<field name="first_online_time"/>
</group>
<group>
<field name="cut_status"/>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0"?>
<odoo>
<!-- 修改设备列表视图-->
<record id="sf_machine_hr_equipment_view_tree_inherit" model="ir.ui.view">
<field name="name">sf.machine.hr.equipment.view.tree.inherit</field>
<field name="model">maintenance.equipment</field>
<field name="inherit_id" ref="maintenance.hr_equipment_view_tree"/>
<field name="arch" type="xml">
<xpath expr="//tree" position="inside">
<header>
<button name="sync_oee" type="object" string="同步设备至OEE"/>
</header>
</xpath>
</field>
</record>
</odoo>

View File

@@ -41,29 +41,32 @@ class SfMaintenanceEquipmentOEELog(models.Model):
_name = 'maintenance.equipment.oee.logs'
_description = '设备运行日志'
equipment_id = fields.Many2one('maintenance.equipment', '机台号')
equipment_code = fields.Char('设备编码')
equipment_id = fields.Many2one('maintenance.equipment', '机台号', readonly='True')
equipment_code = fields.Char('设备编码', readonly='True')
name = fields.Char('设备名称', readonly='True')
function_type = fields.Selection(
[("ZXJGZX", "钻铣加工中心"), ("CXJGZX", "车削加工中心"), ("FHJGZX", "复合加工中心")],
default="", string="功能类型")
machine_tool_picture = fields.Binary('设备图片')
type_id = fields.Many2one('sf.machine_tool.type', '品牌型号')
type_id = fields.Many2one('sf.machine_tool.type', '品牌型号', reaonly='True')
state = fields.Selection([("加工", "加工"), ("关机", "关机"), ("待机", "待机"), ("故障", "故障"),
("检修", "检修"), ("保养", "保养")], default="", string="实时状态")
online_time = fields.Char('开机时长')
online_time = fields.Char('开机时长', reaonly='True')
offline_time = fields.Char('关机时长')
offline_nums = fields.Integer('关机次数')
offline_time = fields.Char('关机时长', reaonly='True')
offline_nums = fields.Integer('关机次数', reaonly='True')
# 待机时长
idle_time = fields.Char('待机时长')
idle_time = fields.Char('待机时长', reaonly='True')
# 待机率
idle_rate = fields.Char('待机率')
idle_rate = fields.Char('待机率', reaonly='True')
work_time = fields.Char('加工时长')
work_rate = fields.Char('可用率')
fault_time = fields.Char('故障时长')
fault_rate = fields.Char('故障率')
fault_nums = fields.Integer('故障次数')
work_time = fields.Char('加工时长', reaonly='True')
work_rate = fields.Char('可用率', reaonly='True')
fault_time = fields.Char('故障时长', reaonly='True')
fault_rate = fields.Char('故障率', reaonly='True')
fault_nums = fields.Integer('故障次数', reaonly='True')
detail_ids = fields.One2many('maintenance.equipment.oee.log.detail', 'log_id', string='日志详情')
@@ -81,12 +84,15 @@ class SfMaintenanceEquipmentOEELog(models.Model):
class SfMaintenanceEquipmentOEELogDetail(models.Model):
_name = 'maintenance.equipment.oee.log.detail'
_description = '设备运行日志详情'
_order = 'time desc'
sequence = fields.Integer('序号')
# sequence = fields.Integer('序号', related='id')
time = fields.Datetime('时间')
state = fields.Selection([("加工", "加工"), ("关机", "关机"), ("待机", "待机"), ("故障", "故障"),
("检修", "检修"), ("保养", "保养")], default="", string="事件/状态")
production_id = fields.Many2one('mrp.production', '加工工单')
production_name = fields.Char('加工工单')
log_id = fields.Many2one('maintenance.equipment.oee.logs', '日志')
# equipment_code = fields.Char('设备编码', related='log_id.equipment_code')
equipment_code = fields.Char('设备编码', readonly='True')

View File

@@ -159,6 +159,8 @@
<field name="equipment_id" domain="[('name','ilike','加工中心')]"/>
<field name="type_id"/>
<field name="state"/>
<field name="equipment_code"/>
<field name="function_type"/>
</group>
</group>
<group>
@@ -202,10 +204,10 @@
<!-- <field name="detail_ids" domain="[('time','&lt;',(datetime.datetime.now() - datetime.timedelta(days=1)).strftime('%Y-%m-%d %H:%M:%S'))]"> -->
<field name="detail_ids" domain="[('state','ilike','加工')]">
<tree>
<field name="sequence"/>
<!-- <field name="sequence"/> -->
<field name="time"/>
<field name="state"/>
<field name="production_id"/>
<field name="production_name"/>
</tree>
<!-- <form> -->
<!-- <field name="sequence"/> -->
@@ -219,10 +221,10 @@
<page string="历史日志详情">
<field name="detail_ids">
<tree>
<field name="sequence"/>
<!-- <field name="sequence"/> -->
<field name="time"/>
<field name="state"/>
<field name="production_id"/>
<field name="production_name"/>
</tree>
<!-- <form> -->
<!-- <field name="sequence"/> -->
@@ -263,10 +265,10 @@
<field name="model">maintenance.equipment.oee.log.detail</field>
<field name="arch" type="xml">
<tree>
<field name="sequence"/>
<!-- <field name="sequence"/> -->
<field name="time"/>
<field name="state"/>
<field name="production_id"/>
<field name="production_name"/>
</tree>
</field>
</record>
@@ -280,10 +282,10 @@
<group>
<group>
<field name="state"/>
<field name="production_id"/>
<!-- <field name="production_id"/> -->
</group>
<group>
<field name="sequence"/>
<!-- <field name="sequence"/> -->
<field name="time"/>
</group>
</group>

View File

@@ -15,12 +15,14 @@
'data/stock_data.xml',
'data/empty_racks_data.xml',
'data/panel_data.xml',
'data/agv_scheduling_data.xml',
'security/group_security.xml',
'security/ir.model.access.csv',
'wizard/workpiece_delivery_views.xml',
'wizard/rework_wizard_views.xml',
'wizard/production_wizard_views.xml',
'views/mrp_views_menus.xml',
'views/agv_scheduling_views.xml',
'views/stock_lot_views.xml',
'views/mrp_production_addional_change.xml',
'views/mrp_routing_workcenter_view.xml',
@@ -30,7 +32,6 @@
'views/model_type_view.xml',
'views/agv_setting_views.xml',
'views/sf_maintenance_equipment.xml',
],
'assets': {
@@ -40,7 +41,9 @@
'web.assets_backend': [
'sf_manufacturing/static/src/xml/kanban_change.xml',
'sf_manufacturing/static/src/js/kanban_change.js',
'sf_manufacturing/static/src/scss/kanban_change.scss'
'sf_manufacturing/static/src/scss/kanban_change.scss',
'sf_manufacturing/static/src/xml/button_show_on_tree.xml',
'sf_manufacturing/static/src/js/workpiece_delivery_wizard_confirm.js',
]
},

View File

@@ -2,6 +2,8 @@
import logging
import json
from datetime import datetime
from odoo.addons.sf_manufacturing.models.agv_scheduling import RepeatTaskException
from odoo import http
from odoo.http import request
@@ -386,7 +388,7 @@ class Manufacturing_Connect(http.Controller):
ret = json.loads(datas)
request.env['center_control.interface.log'].sudo().create(
{'content': ret, 'name': 'AutoDeviceApi/LocationChange'})
logging.info('LocationChange_ret===========:%s' % ret)
logging.info('库位变更LocationChange_ret:%s' % ret)
RfidCode = ret['RfidCode']
ChangeType = ret['ChangeType']
OldDeciveId = ret['OldDeciveId']
@@ -396,34 +398,79 @@ class Manufacturing_Connect(http.Controller):
OldDeciveStart = ret['OldDeciveStart']
OldDeciveEnd = ret['OldDeciveEnd']
temp_val_sn_id = None
old_localtion = None
# if ChangeType == 'Part' or ChangeType == 'Tool':
stock_lot_obj = request.env['stock.lot'].sudo().search(
[('rfid', '=', RfidCode)], limit=1)
logging.info('stock_lot_obj===========:%s' % stock_lot_obj)
if not stock_lot_obj:
res = {'Succeed': False, 'ErrorCode': 202, 'Error': '未根据RfidCode找到该产品'}
return json.JSONEncoder().encode(res)
if OldPosition:
old_localtion = request.env['sf.shelf.location'].sudo().search(
[('barcode', '=', OldPosition)], limit=1)
logging.info('old_localtion===========:%s' % old_localtion)
new_localtion = request.env['sf.shelf.location'].sudo().search(
[('barcode', '=', NewPosition)], limit=1)
logging.info('new_localtion===========:%s' % new_localtion)
if not new_localtion:
res = {'Succeed': False, 'ErrorCode': 202, 'Error': '没有该目标位置'}
return json.JSONEncoder().encode(res)
if old_localtion:
temp_val_sn_id = old_localtion.product_sn_id
logging.info('temp_val_sn_id===========:%s' % temp_val_sn_id)
old_localtion.product_sn_id = None
new_localtion.product_sn_id = temp_val_sn_id
logging.info('====1======')
else:
new_localtion.product_sn_id = stock_lot_obj.id
logging.info('=====2======')
if ChangeType == 'Part':
temp_val_sn_id = None
old_localtion = None
# if ChangeType == 'Part' or ChangeType == 'Tool':
stock_lot_obj = request.env['stock.lot'].sudo().search(
[('rfid', '=', RfidCode)], limit=1)
logging.info('stock_lot_obj===========:%s' % stock_lot_obj)
if not stock_lot_obj:
res = {'Succeed': False, 'ErrorCode': 202, 'Error': '未根据RfidCode找到该产品'}
return json.JSONEncoder().encode(res)
if OldPosition:
old_localtion = request.env['sf.shelf.location'].sudo().search(
[('barcode', '=', OldPosition)], limit=1)
logging.info('old_localtion===========:%s' % old_localtion)
new_localtion = request.env['sf.shelf.location'].sudo().search(
[('barcode', '=', NewPosition)], limit=1)
logging.info('new_localtion===========:%s' % new_localtion)
if not new_localtion:
res = {'Succeed': False, 'ErrorCode': 202, 'Error': '没有该目标位置'}
return json.JSONEncoder().encode(res)
if old_localtion:
temp_val_sn_id = old_localtion.product_sn_id
logging.info('temp_val_sn_id===========:%s' % temp_val_sn_id)
old_localtion.product_sn_id = None
new_localtion.product_sn_id = temp_val_sn_id
logging.info('====1======')
else:
new_localtion.product_sn_id = stock_lot_obj.id
logging.info('=====2======')
elif ChangeType == 'Tool':
# 对功能刀具库位变更信息进行更改
def write_tool(DeciveId):
if 'Tool' in DeciveId:
shelfinfo = list(filter(lambda x: x.get('DeviceId') == DeciveId,
request.env['sf.shelf.location'].sudo().get_sf_shelf_location_info(
DeciveId)))
total_data = request.env['sf.shelf.location.datasync'].sudo().get_total_data()
for item in shelfinfo:
shelf_barcode = request.env['sf.shelf.location.datasync'].sudo().find_our_code(
total_data, item['Postion'])
location_id = request.env['sf.shelf.location'].sudo().search(
[('barcode', '=', shelf_barcode)],
limit=1)
if location_id:
# 如果是线边刀库信息,则对功能刀具移动生成记录
if 'Tool' in item['Postion']:
tool = request.env['sf.functional.cutting.tool.entity'].sudo().search(
[('rfid', '=', item['RfidCode']), ('functional_tool_status', '!=', '已拆除')])
tool.sudo().tool_in_out_stock_location(location_id)
if tool:
location_id.product_sn_id = tool.barcode_id.id
# 修改功能刀具状态
tool_state = {'Nomal': '正常', 'Warning': '报警'}
if tool_state.get(item.get('State')):
if tool_state.get(item.get('State')) != tool.functional_tool_status:
tool.write({
'functional_tool_status': tool_state.get(item['State'])
})
else:
location_id.product_sn_id = False
logging.info('货架已获取信息:%s' % item)
else:
equipment_id = request.env['maintenance.equipment'].sudo().search([('name', '=', DeciveId)])
if equipment_id:
equipment_id.sudo().register_equipment_tool()
else:
res_1 = {'Succeed': False, 'ErrorCode': 202, 'Error': f'设备【{DeciveId}】不存在'}
return json.JSONEncoder().encode(res_1)
if OldDeciveId:
write_tool(OldDeciveId)
elif NewDeciveId:
write_tool(NewDeciveId)
except Exception as e:
res = {'Succeed': False, 'ErrorCode': 202, 'Error': e}
logging.info('LocationChange error:%s' % e)
@@ -448,13 +495,16 @@ class Manufacturing_Connect(http.Controller):
if 'DeviceId' in ret:
logging.info('DeviceId:%s' % ret['DeviceId'])
if 'IsComplete' in ret:
rfid_codes = []
workorder_ids = []
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:
rfid_code = ret[f'RfidCode{i}']
logging.info('RfidCode:%s' % rfid_code)
if rfid_code is not None:
if rfid_code is not None and rfid_code != '':
rfid_codes.append(rfid_code)
domain = [
('rfid_code', '=', rfid_code),
('routing_type', '=', 'CNC加工'), ('state', '!=', 'rework')
@@ -462,6 +512,7 @@ class Manufacturing_Connect(http.Controller):
workorder = request.env['mrp.workorder'].sudo().search(domain, order='id asc')
if workorder:
for order in workorder:
workorder_ids.append(order.id)
if order.production_line_state == '待上产线':
logging.info(
'工单产线状态:%s' % order.production_line_state)
@@ -470,23 +521,30 @@ class Manufacturing_Connect(http.Controller):
('processing_panel', '=', order.processing_panel)])
if panel_workorder:
panel_workorder.write({'production_line_state': '已上产线'})
workpiece_delivery = request.env['sf.workpiece.delivery'].sudo().search(
[
('rfid_code', '=', rfid_code), ('type', '=', '上产线'),
('production_id', '=', order.production_id.id),
('workorder_id', '=', order.id),
('workorder_state', '=', 'done')])
if workpiece_delivery.status == '待下发':
workpiece_delivery.write({'is_manual_work': True})
# workpiece_delivery = request.env['sf.workpiece.delivery'].sudo().search(
# [
# ('rfid_code', '=', rfid_code), ('type', '=', '上产线'),
# ('production_id', '=', order.production_id.id),
# ('workorder_id', '=', order.id),
# ('workorder_state', '=', 'done')])
# if workpiece_delivery.status == '待下发':
# workpiece_delivery.write({'is_manual_work': True})
# 下发
else:
res = {'Succeed': False, 'ErrorCode': 204,
'Error': 'DeviceId为%s没有对应的已配送工件数据' % ret['DeviceId']}
if ret['IsComplete'] is True:
# 向AGV任务调度下发运送空料架任务
workorders = request.env['mrp.workorder'].browse(workorder_ids)
request.env['sf.agv.scheduling'].add_scheduling(ret['DeviceId'], '运送空料架', workorders)
else:
res = {'Succeed': False, 'ErrorCode': 203, 'Error': '未传IsComplete字段'}
else:
res = {'Succeed': False, 'ErrorCode': 201, 'Error': '未传DeviceId字段'}
except RepeatTaskException as e:
logging.info('AGVToProduct error:%s' % e)
except Exception as e:
res = {'Succeed': False, 'ErrorCode': 202, 'Error': e}
res = {'Succeed': False, 'ErrorCode': 202, 'Error': str(e)}
logging.info('AGVToProduct error:%s' % e)
return json.JSONEncoder().encode(res)
@@ -509,7 +567,8 @@ class Manufacturing_Connect(http.Controller):
logging.info('ret:%s' % ret)
if 'DeviceId' in ret:
logging.info('DeviceId:%s' % ret['DeviceId'])
delivery_Arr = []
# delivery_Arr = []
workorder_ids = []
if 'IsComplete' in ret:
if ret['IsComplete'] is True or ret['IsComplete'] is False:
for i in range(1, 5):
@@ -517,7 +576,7 @@ class Manufacturing_Connect(http.Controller):
if f'RfidCode{i}' in ret:
rfid_code = ret[f'RfidCode{i}']
logging.info('RfidCode:%s' % rfid_code)
if rfid_code is not None:
if rfid_code is not None and rfid_code != '':
domain = [
('rfid_code', '=', rfid_code),
('routing_type', '=', 'CNC加工'), ('state', '!=', 'rework')
@@ -525,6 +584,7 @@ class Manufacturing_Connect(http.Controller):
workorder = request.env['mrp.workorder'].sudo().search(domain, order='id asc')
if workorder:
for order in workorder:
workorder_ids.append(order.id)
if order.production_line_state == '已上产线':
logging.info(
'工单产线状态:%s' % order.production_line_state)
@@ -534,35 +594,41 @@ class Manufacturing_Connect(http.Controller):
if panel_workorder:
panel_workorder.write({'production_line_state': '已下产线'})
workorder.write({'state': 'to be detected'})
workpiece_delivery = request.env['sf.workpiece.delivery'].sudo().search(
[
('rfid_code', '=', rfid_code), ('type', '=', '下产线'),
('production_id', '=', order.production_id.id),
('workorder_id', '=', order.id),
('workorder_state', '=', 'done')])
if workpiece_delivery:
delivery_Arr.append(workpiece_delivery.id)
# workpiece_delivery = request.env['sf.workpiece.delivery'].sudo().search(
# [
# ('rfid_code', '=', rfid_code), ('type', '=', '下产线'),
# ('production_id', '=', order.production_id.id),
# ('workorder_id', '=', order.id),
# ('workorder_state', '=', 'done')])
# if workpiece_delivery:
# delivery_Arr.append(workpiece_delivery.id)
else:
res = {'Succeed': False, 'ErrorCode': 204,
'Error': 'DeviceId为%s没有对应的已配送工件数据' % ret['DeviceId']}
if delivery_Arr:
logging.info('delivery_Arr:%s' % delivery_Arr)
delivery_workpiece = request.env['sf.workpiece.delivery'].sudo().search(
[('id', 'in', delivery_Arr)])
if delivery_workpiece:
logging.info('开始向agv下发下产线任务')
agv_site = request.env['sf.agv.site'].sudo().search([])
if agv_site:
has_site = agv_site.update_site_state()
if has_site is True:
is_free = delivery_workpiece._check_avgsite_state()
if is_free is True:
delivery_workpiece._delivery_avg()
logging.info('agv下发下产线任务下发完成')
# if delivery_Arr:
# logging.info('delivery_Arr:%s' % delivery_Arr)
# delivery_workpiece = request.env['sf.workpiece.delivery'].sudo().search(
# [('id', 'in', delivery_Arr)])
# if delivery_workpiece:
# logging.info('开始向agv下发下产线任务')
# agv_site = request.env['sf.agv.site'].sudo().search([])
# if agv_site:
# has_site = agv_site.update_site_state()
# if has_site is True:
# is_free = delivery_workpiece._check_avgsite_state()
# if is_free is True:
# delivery_workpiece._delivery_avg()
# logging.info('agv下发下产线任务下发完成')
if ret['IsComplete'] is True:
# 向AGV任务调度下发下产线任务
workorders = request.env['mrp.workorder'].browse(workorder_ids)
request.env['sf.agv.scheduling'].add_scheduling(ret['DeviceId'], '下产线', workorders)
else:
res = {'Succeed': False, 'ErrorCode': 203, 'Error': '未传IsComplete字段'}
except RepeatTaskException as e:
logging.info('AGVToProduct error:%s' % e)
except Exception as e:
res = {'Succeed': False, 'ErrorCode': 202, 'Error': e}
res = {'Succeed': False, 'ErrorCode': 202, 'Error': str(e)}
logging.info('AGVDownProduct error:%s' % e)
return json.JSONEncoder().encode(res)
@@ -600,3 +666,27 @@ class Manufacturing_Connect(http.Controller):
res = {'Succeed': False, 'ErrorCode': 202, 'Error': e}
logging.info('AGVDownProduct error:%s' % e)
return json.JSONEncoder().encode(res)
@http.route('/AutoDeviceApi/GetAgvStationState', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
cors="*")
def AGVStationState(self, **kw):
"""
中控推送接驳站状态
:param kw:
:return:
"""
logging.info('AGVStationState:%s' % kw)
try:
res = {'Succeed': True}
datas = request.httprequest.data
ret = json.loads(datas)
request.env['center_control.interface.log'].sudo().create(
{'content': ret, 'name': 'AutoDeviceApi/AGVStationState'})
logging.info('ret:%s' % ret)
if 'DeviceId' in ret and 'AtHome' in ret:
logging.info('DeviceId:%s, AtHome:%s' % (ret['DeviceId'], ret['AtHome']))
request.env['sf.agv.site'].update_site_state({ret['DeviceId']: '占用' if ret['AtHome'] else '空闲'})
except Exception as e:
res = {'Succeed': False, 'ErrorCode': 202, 'Error': str(e)}
logging.info('AGVDownProduct error:%s' % e)
return json.JSONEncoder().encode(res)

View File

@@ -25,15 +25,14 @@ class Workpiece(http.Controller):
if 'reqCode' in ret:
if 'method' in ret:
if ret['method'] == 'end':
req_codes = ret['reqCode'].split(',')
for req_code in req_codes:
workpiece_delivery = request.env['sf.workpiece.delivery'].sudo().search(
[('name', '=', req_code.strip()), ('task_completion_time', '=', False)])
if workpiece_delivery:
workpiece_delivery.write({'status': '已配送', 'task_completion_time': datetime.now()})
else:
res = {'Succeed': False, 'ErrorCode': 203,
'Error': '该reqCode暂未查到对应的工件配送记录'}
# 找到对应的AGV调度任务
agv_scheduling = request.env['sf.agv.scheduling'].sudo().search(
[('name', '=', ret['reqCode']), ('state', '=', '配送中')])
if agv_scheduling:
agv_scheduling.finish_scheduling()
else:
res = {'Succeed': False, 'ErrorCode': 203,
'Error': '该reqCode暂未查到对应的AGV任务记录'}
else:
res = {'Succeed': False, 'ErrorCode': 204, 'Error': '未传method字段'}
else:

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="0">
<record id="sequence_agv_scheduling" model="ir.sequence">
<field name="name">AGV调度</field>
<field name="code">sf.agv.scheduling</field>
<field name="prefix">B%(year)s%(month)s%(day)s</field>
<field name="padding">4</field>
<field name="number_next">1</field>
<field name="implementation">standard</field>
<field name="use_date_range">True</field>
<field name="company_id" eval="False"/>
</record>
</data>
</odoo>

View File

@@ -9,3 +9,4 @@ from . import stock
from . import res_user
from . import production_line_base
from . import agv_setting
from . import agv_scheduling

View File

@@ -0,0 +1,253 @@
import requests
from odoo import models, fields, api, _
from odoo.exceptions import UserError
import logging
_logger = logging.getLogger(__name__)
class RepeatTaskException(UserError):
pass
class AgvScheduling(models.Model):
_name = 'sf.agv.scheduling'
_description = 'agv调度'
_order = 'id desc'
name = fields.Char('任务单号', index=True, copy=False)
def _get_agv_route_type_selection(self):
return self.env['sf.agv.task.route'].fields_get(['route_type'])['route_type']['selection']
agv_route_type = fields.Selection(selection=_get_agv_route_type_selection, string='任务类型', required=True)
agv_route_name = fields.Char('任务路线名称')
start_site_id = fields.Many2one('sf.agv.site', '起点接驳站', required=True)
end_site_id = fields.Many2one('sf.agv.site', '终点接驳站', tracking=True)
site_state = fields.Selection([
('占用', '占用'),
('空闲', '空闲')], string='终点接驳站状态', default='占用')
state = fields.Selection([
('待下发', '待下发'),
('配送中', '配送中'),
('已配送', '已配送'),
('已取消', '已取消')], string='状态', default='待下发', tracking=True)
workorder_ids = fields.Many2many('mrp.workorder', 'sf_agv_scheduling_mrp_workorder_ref', string='关联工单')
task_create_time = fields.Datetime('任务创建时间')
task_delivery_time = fields.Datetime('任务下发时间')
task_completion_time = fields.Datetime('任务完成时间')
task_duration = fields.Char('任务时长', compute='_compute_task_duration')
@api.depends('agv_route_type')
def _compute_delivery_workpieces(self):
for record in self:
if record.agv_route_type == '运送空料架':
record.delivery_workpieces = '/'
else:
record.delivery_workpieces = ''.join(record.workorder_ids.mapped('production_id.name'))
delivery_workpieces = fields.Char('配送工件', compute=_compute_delivery_workpieces)
@api.model
def web_search_read(self, domain=None, fields=None, offset=0, limit=None, order=None, count_limit=None):
domain = domain or []
new_domain = []
for index, item in enumerate(domain):
if isinstance(item, list):
if item[0] == 'delivery_workpieces':
new_domain.append('&')
new_domain.append(['workorder_ids.production_id.name', item[1], item[2]])
new_domain.append(['agv_route_type', '!=', '运送空料架'])
continue
new_domain.append(item)
return super(AgvScheduling, self).web_search_read(new_domain, fields, limit=limit, offset=offset)
@api.depends('task_completion_time', 'task_delivery_time')
def _compute_task_duration(self):
for rec in self:
if rec.task_completion_time and rec.task_delivery_time:
rec.task_duration = str(rec.task_completion_time - rec.task_delivery_time)
else:
rec.task_duration = ''
@api.model_create_multi
def create(self, vals_list):
# We generate a standard reference
for vals in vals_list:
vals['name'] = self.env['ir.sequence'].next_by_code('sf.agv.scheduling') or _('New')
return super().create(vals_list)
def add_scheduling(self, agv_start_site_name, agv_route_type, workorders):
""" add_scheduling(agv_start_site_id, agv_route_type, workorders) -> agv_scheduling
新增AGV调度
params:
agv_start_site_name: AGV起点接驳站名称
agv_route_type: AGV任务类型
workorders: 工单
"""
_logger.info('创建AGV调度任务\r\n起点为【%s】,任务类型为【%s】,工单为【%s' % (agv_start_site_name, agv_route_type, workorders))
if not workorders:
raise UserError(_('工单不能为空'))
agv_start_site = self.env['sf.agv.site'].sudo().search([('name', '=', agv_start_site_name)], limit=1)
if not agv_start_site:
raise UserError(_('不存在名称为【%s】的接驳站,请先创建!' % agv_start_site_name))
# 如果存在相同任务类型工单的AGV调度任务则提示错误
agv_scheduling = self.sudo().search([
('workorder_ids', 'in', workorders.ids),
('agv_route_type', '=', agv_route_type),
('state', 'in', ['待下发', '配送中'])
], limit=1)
if agv_scheduling:
# 计算agv_scheduling.workorder_ids与workorders的交集
repetitive_workorders = agv_scheduling.workorder_ids & workorders
raise RepeatTaskException(
'制造订单号【%s】已存在于【%s】AGV调度任务请勿重复下发' %
(','.join(repetitive_workorders.mapped('production_id.name')), agv_scheduling.name)
)
vals = {
'start_site_id': agv_start_site.id,
'agv_route_type': agv_route_type,
'workorder_ids': workorders.ids,
# 'workpiece_delivery_ids': deliveries.mapped('id') if deliveries else [],
'task_create_time': fields.Datetime.now()
}
# 如果只有唯一任务路线,则自动赋予终点接驳站跟任务名称
agv_routes = self.env['sf.agv.task.route'].sudo().search([
('route_type', '=', agv_route_type),
('start_site_id', '=', agv_start_site.id)
])
if not agv_routes:
raise UserError(_('不存在起点为【%s】的【%s】任务路线,请先创建!' % (agv_start_site_name, agv_route_type)))
idle_route = None
if len(agv_routes) == 1:
idle_route = agv_routes[0]
vals.update({'end_site_id': idle_route.end_site_id.id, 'agv_route_name': idle_route.name})
else:
# 判断终点接驳站是否为空闲
idle_routes = agv_routes.filtered(lambda r: r.end_site_id.state == '空闲')
if idle_routes:
# 将空闲的路线按照终点接驳站名称排序
idle_routes = sorted(idle_routes, key=lambda r: r.end_site_id.name)
idle_route = idle_routes[0]
vals.update({'end_site_id': idle_route.end_site_id.id, 'agv_route_name': idle_route.name})
try:
scheduling = self.env['sf.agv.scheduling'].sudo().create(vals)
# 触发空闲接驳站状态更新,触发新任务下发
if idle_route and idle_route.end_site_id.state == '空闲':
scheduling.dispatch_scheduling(idle_route)
except Exception as e:
_logger.error('添加AGV调度任务失败: %s', e)
raise UserError(_('添加AGV调度任务失败: %s', e))
return scheduling
def on_site_state_change(self, agv_site_id, agv_site_state):
"""
响应AGV接驳站站点状态变化
params:
agv_site_id: 接驳站ID
agv_site_state: 站点状态('空闲', '占用'
"""
if agv_site_state == '空闲':
# 查询终点接驳站为agv_site_id的AGV路线
task_routes = self.env['sf.agv.task.route'].sudo().search([('end_site_id', '=', agv_site_id)])
agv_scheduling = self.env['sf.agv.scheduling'].sudo().search(
[('state', '=', '待下发'), ('agv_route_type', 'in', task_routes.mapped('route_type'))],
order='id asc',
limit=1
)
task_route = task_routes.filtered(
lambda r: r.start_site_id == agv_scheduling.start_site_id and r.start_site_id == agv_scheduling.start_site_id
)
# 下发AGV调度任务并修改接驳站状态为占用
agv_scheduling.dispatch_scheduling(task_route)
def _delivery_avg(self):
config = self.env['res.config.settings'].get_values()
position_code_arr = [{
'positionCode': self.start_site_id.name,
'code': '00'
}, {
'positionCode': self.end_site_id.name,
'code': '00'
}]
res = {'reqCode': self.name, 'reqTime': '', 'clientCode': '', 'tokenCode': '',
'taskTyp': 'F01', 'ctnrTyp': '', 'ctnrCode': '', 'wbCode': config['wbcode'],
'positionCodePath': position_code_arr,
'podCode': '',
'podDir': '', 'materialLot': '', 'priority': '', 'taskCode': '', 'agvCode': '', 'materialLot': '',
'data': ''}
try:
logging.info('AGV请求路径:%s' % config['agv_rcs_url'])
logging.info('AGV-json:%s' % res)
headers = {'Content-Type': 'application/json'}
ret = requests.post((config['agv_rcs_url']), json=res, headers=headers)
ret = ret.json()
logging.info('config-ret:%s' % ret)
if ret['code'] == 0:
return True
else:
raise UserError(ret['message'])
except Exception as e:
logging.info('config-e:%s' % e)
raise UserError("工件配送请求agv失败:%s" % e)
def button_cancel(self):
# 弹出二次确认窗口后执行
for rec in self:
if rec.state != '待下发':
raise UserError('只有待下发状态的AGV调度任务才能取消')
rec.state = '已取消'
def finish_scheduling(self):
"""
完成调度任务
"""
for rec in self:
if rec.state != '配送中':
return False
_logger.info('AGV任务调度完成任务%s' % rec)
rec.state = '已配送'
rec.task_completion_time = fields.Datetime.now()
def dispatch_scheduling(self, agv_task_route):
"""
下发调度任务
params:
agv_route sf.agv.task.route对象
"""
for rec in self:
if rec.state != '待下发':
return False
_logger.info('AGV任务调度下发调度任务路线为%s' % agv_task_route)
# rec._delivery_avg()
rec.state = '配送中'
rec.task_delivery_time = fields.Datetime.now()
rec.site_state = '空闲'
rec.end_site_id = agv_task_route.end_site_id.id
rec.agv_route_name = agv_task_route.name
# 更新接驳站状态
rec.env['sf.agv.site'].update_site_state({rec.end_site_id.name: '占用'}, False)
def write(self, vals):
if vals.get('state', False):
if vals['state'] == '已取消':
self.env['sf.workpiece.delivery'].search([('agv_scheduling_id', '=', self.id)]).write({'status': '待下发'})
elif vals['state'] == '已配送':
self.env['sf.workpiece.delivery'].search([('agv_scheduling_id', '=', self.id)]).write({'status': '已配送'})
return super().write(vals)
class ResMrpWorkOrder(models.Model):
_inherit = 'mrp.workorder'
agv_scheduling_ids = fields.Many2many(
'sf.agv.scheduling',
'sf_agv_scheduling_mrp_workorder_ref',
string='AGV调度',
domain=[('state', '!=', '已取消')])

View File

@@ -5,50 +5,77 @@ import time
from odoo import fields, models, api
from odoo.exceptions import UserError
_logger = logging.getLogger(__name__)
class AgvSetting(models.Model):
_name = 'sf.agv.site'
_description = 'agv站点'
name = fields.Char('位置编号')
owning_region = fields.Char('所属区域')
# owning_region = fields.Char('所属区域')
state = fields.Selection([
('占用', '占用'),
('空闲', '空闲')], string='状态')
divide_the_work = fields.Char('主要分工')
active = fields.Boolean('有效', default=True)
workcenter_id = fields.Many2one(string='所属区域', comodel_name='mrp.workcenter', tracking=True,
domain=[('is_agv_scheduling', '=', True)])
def update_site_state(self):
# 调取中控的接驳站接口并修改对应站点的状态
config = self.env['res.config.settings'].get_values()
# token = sf_sync_config['token'Ba F2CF5DCC-1A00-4234-9E95-65603F70CC8A]
headers = {'Authorization': config['center_control_Authorization']}
center_control_url = config['center_control_url'] + "/AutoDeviceApi/GetAgvStationState?date="
timestamp = int(time.time())
center_control_url += str(timestamp)
logging.info('工件配送-请求中控地址:%s' % center_control_url)
try:
center_control_r = requests.get(center_control_url, headers=headers, timeout=10) # 设置超时为60秒
ret = center_control_r.json()
logging.info('工件配送-请求中控站点信息:%s' % ret)
self.env['center_control.interface.log'].sudo().create(
{'content': ret, 'name': 'AutoDeviceApi/GetAgvStationState?date=%s' % str(timestamp)})
if ret['Succeed'] is True:
datas = ret['Datas']
for item in self:
for da in datas:
if da['DeviceId'] == item.name:
if da['AtHome'] is True:
item.state = '占用'
else:
item.state = '空闲'
return True
except requests.exceptions.Timeout:
logging.error('工件配送-请求中控接口超时')
return False
except requests.exceptions.RequestException as e:
logging.error('工件配送-请求中控接口错误: %s', e)
return False
# name必须唯一
_sql_constraints = [
('name_uniq', 'unique (name)', '站点编号必须唯一!'),
]
# def update_site_state(self):
# # 调取中控的接驳站接口并修改对应站点的状态
# config = self.env['res.config.settings'].get_values()
# # token = sf_sync_config['token'Ba F2CF5DCC-1A00-4234-9E95-65603F70CC8A]
# headers = {'Authorization': config['center_control_Authorization']}
# center_control_url = config['center_control_url'] + "/AutoDeviceApi/GetAgvStationState?date="
# timestamp = int(time.time())
# center_control_url += str(timestamp)
# logging.info('工件配送-请求中控地址:%s' % center_control_url)
# try:
# center_control_r = requests.get(center_control_url, headers=headers, timeout=10) # 设置超时为60秒
# ret = center_control_r.json()
# logging.info('工件配送-请求中控站点信息:%s' % ret)
# self.env['center_control.interface.log'].sudo().create(
# {'content': ret, 'name': 'AutoDeviceApi/GetAgvStationState?date=%s' % str(timestamp)})
# if ret['Succeed'] is True:
# datas = ret['Datas']
# for item in self:
# for da in datas:
# if da['DeviceId'] == item.name:
# if da['AtHome'] is True:
# item.state = '占用'
# else:
# item.state = '空闲'
# return True
# except requests.exceptions.Timeout:
# logging.error('工件配送-请求中控接口超时')
# return False
# except requests.exceptions.RequestException as e:
# logging.error('工件配送-请求中控接口错误: %s', e)
# return False
def update_site_state(self, agv_site_state_arr, notify=True):
"""
更新接驳站状态
params:
agv_site_state_arr: {'A01': '空闲', 'B01': '占用'}
notify: 是否通知调度(非中控发起的状态改变不触发调度任务)
"""
if isinstance(agv_site_state_arr, dict):
for agv_site_name, is_occupy in agv_site_state_arr.items():
agv_site = self.env['sf.agv.site'].sudo().search([('name', '=', agv_site_name)])
if agv_site:
agv_site.state = is_occupy
if notify:
self.env['sf.agv.scheduling'].on_site_state_change(agv_site.id, agv_site.state)
else:
_logger.error("更新失败:接驳站站点错误!%s" % agv_site_name)
raise UserError("更新失败:接驳站站点错误!")
class AgvTaskRoute(models.Model):
@@ -71,6 +98,17 @@ class AgvTaskRoute(models.Model):
if self.end_site_id == self.start_site_id:
raise UserError("您选择的终点接驳站与起点接驳站重复,请重新选择")
workcenter_id = fields.Many2one(string='所属区域', comodel_name='mrp.workcenter', domain=[('is_agv_scheduling', '=', True)],
compute="_compute_region")
@api.depends('end_site_id')
def _compute_region(self):
for record in self:
if record.end_site_id:
record.workcenter_id = record.end_site_id.workcenter_id
else:
record.workcenter_id = None
class Center_controlInterfaceLog(models.Model):
_name = 'center_control.interface.log'

View File

@@ -124,6 +124,8 @@ class ResWorkcenter(models.Model):
res[wc_id] = [(datetime.fromtimestamp(s), datetime.fromtimestamp(e)) for s, e, _ in final_intervals_wc]
return res
# AGV是否可配送
is_agv_scheduling = fields.Boolean(string="AGV所属区域", tracking=True)
class ResWorkcenterProductivity(models.Model):
_inherit = 'mrp.workcenter.productivity'

View File

@@ -135,6 +135,9 @@ class ResMrpWorkOrder(models.Model):
surface_technics_picking_count = fields.Integer("外协出入库", compute='_compute_surface_technics_picking_ids')
surface_technics_purchase_count = fields.Integer("外协采购", compute='_compute_surface_technics_purchase_ids')
# 是否绑定托盘
is_trayed = fields.Boolean(string='是否绑定托盘', default=False)
@api.depends('name', 'production_id.name')
def _compute_surface_technics_picking_ids(self):
for workorder in self:
@@ -371,10 +374,10 @@ class ResMrpWorkOrder(models.Model):
vals['leave_id'] = leave.id
self.write(vals)
@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})
# @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")
@@ -687,25 +690,35 @@ class ResMrpWorkOrder(models.Model):
k, item),
'cmm_ids': False if route.routing_type != 'CNC加工' else self.env['sf.cmm.program']._json_cmm_program(k,
item),
'workpiece_delivery_ids': False if not route.routing_type == '装夹预调' else self._json_workpiece_delivery_list(
production)
# 'workpiece_delivery_ids': False if not route.routing_type == '装夹预调' else self._json_workpiece_delivery_list(
# production)
}]
return workorders_values_str
def _json_workpiece_delivery_list(self, production):
up_route = self.env['sf.agv.task.route'].search([('route_type', '=', '上产线')], limit=1, order='id asc')
down_route = self.env['sf.agv.task.route'].search([('route_type', '=', '下产线')], limit=1, order='id asc')
def _json_workpiece_delivery_list(self):
# 修改在装夹工单完成后,生成上产线的工件配送单
# up_route = self.env['sf.agv.task.route'].search([('route_type', '=', '上产线')], limit=1, order='id asc')
# down_route = self.env['sf.agv.task.route'].search([('route_type', '=', '下产线')], limit=1, order='id asc')
return [
[0, '',
{'production_id': production.id, 'production_line_id': production.production_line_id.id, 'type': '上产线',
'route_id': up_route.id,
'feeder_station_start_id': up_route.start_site_id.id,
'feeder_station_destination_id': up_route.end_site_id.id}],
[0, '',
{'production_id': production.id, 'production_line_id': production.production_line_id.id, 'type': '下产线',
'route_id': down_route.id,
'feeder_station_start_id': down_route.start_site_id.id,
'feeder_station_destination_id': down_route.end_site_id.id}]]
{
'production_id': self.production_id.id,
'production_line_id': self.production_id.production_line_id.id,
'type': '上产线',
'is_cnc_program_down': True,
'rfid_code': self.rfid_code
# 'route_id': up_route.id,
# 'feeder_station_start_id': agv_start_site_id,
# 'feeder_station_destination_id': up_route.end_site_id.id
}
],
# [0, '',
# {'production_id': production.id, 'production_line_id': production.production_line_id.id, 'type': '下产线',
# 'route_id': down_route.id,
# 'feeder_station_start_id': down_route.start_site_id.id,
# 'feeder_station_destination_id': down_route.end_site_id.id}]
]
# 拼接工单对象属性值(表面工艺)
def _json_workorder_surface_process_str(self, production, route, process_parameter, supplier_id):
@@ -1149,6 +1162,8 @@ class ResMrpWorkOrder(models.Model):
record.process_state = '待加工'
# record.write({'process_state': '待加工'})
record.production_id.process_state = '待加工'
# 生成工件配送单
record.workpiece_delivery_ids = record._json_workpiece_delivery_list()
if record.routing_type == 'CNC加工':
record.process_state = '待解除装夹'
# record.write({'process_state': '待加工'})
@@ -1236,6 +1251,19 @@ class ResMrpWorkOrder(models.Model):
record.production_id.button_mark_done1()
# record.production_id.state = 'done'
# 解绑托盘
def unbind_tray(self):
self.write({
'rfid_code': False,
'tray_serial_number': False,
'tray_product_id': False,
'tray_brand_id': False,
'tray_type_id': False,
'tray_model_id': False,
'is_trayed': False
})
# 将FTP的检测报告文件下载到临时目录
def download_reportfile_tmp(self, workorder, reportpath):
logging.info('reportpath/ftp地址:%s' % reportpath)
@@ -1275,6 +1303,66 @@ class ResMrpWorkOrder(models.Model):
else:
raise UserError("无关联制造订单或关联序列号,无法打印。请检查!")
@api.model
def get_views(self, views, options=None):
res = super().get_views(views, options)
if res['views'].get('list', {}) and self.env.context.get('search_default_workcenter_id'):
workcenter = self.env['mrp.workcenter'].browse(self.env.context.get('search_default_workcenter_id'))
tree_view = res['views']['list']
if workcenter.name == '工件拆卸中心':
arch = etree.fromstring(tree_view['arch'])
# 查找 tree 标签
tree_element = arch.xpath("//tree")[0]
# 查找或创建 header 标签
header_element = tree_element.find('header')
if header_element is None:
header_element = etree.Element('header')
tree_element.insert(0, header_element)
# 创建并添加按钮元素
button_element = etree.Element('button', {
'name': 'button_delivery',
'type': 'object',
'string': '解除装夹',
'class': 'btn-primary',
# 'className': 'btn-primary',
'modifiers': '{"force_show": 1}'
})
header_element.append(button_element)
# 更新 tree_view 的 arch
tree_view['arch'] = etree.tostring(arch, encoding='unicode')
return res
def button_delivery(self):
production_ids = []
workorder_ids = []
delivery_type = '运送空料架'
max_num = 4 # 最大配送数量
if len(self) > max_num:
raise UserError('仅限于拆卸1-4个制造订单请重新选择')
for item in self:
if item.state != 'ready':
raise UserError('请选择状态为【就绪】的工单进行解除装夹')
production_ids.append(item.production_id.id)
workorder_ids.append(item.id)
return {
'name': _('确认'),
'type': 'ir.actions.act_window',
'view_mode': 'form',
'res_model': 'sf.workpiece.delivery.wizard',
'target': 'new',
'context': {
# 'default_delivery_ids': [(6, 0, delivery_ids)],
'default_production_ids': [(6, 0, production_ids)],
'default_delivery_type': delivery_type,
'default_workorder_ids': [(6, 0, workorder_ids)],
'default_workcenter_id': self.env.context.get('default_workcenter_id'),
'default_confirm_button': '确认解除'
}}
class CNCprocessing(models.Model):
_name = 'sf.cnc.processing'
@@ -1483,6 +1571,7 @@ class SfWorkOrderBarcodes(models.Model):
raise UserError('该Rfid【%s】绑定的是【%s】, 不是托盘!!!' % (barcode, lot.product_id.name))
self.process_state = '待检测'
self.date_start = datetime.now()
self.is_trayed = True
else:
raise UserError('没有找到Rfid为【%s】的托盘信息!!!' % barcode)
# stock_move_line = self.env['stock.move.line'].search([('lot_name', '=', barcode)])
@@ -1554,20 +1643,25 @@ class WorkPieceDelivery(models.Model):
feeder_station_destination_id = fields.Many2one('sf.agv.site', '目的接驳站')
task_delivery_time = fields.Datetime('任务下发时间')
task_completion_time = fields.Datetime('任务完成时间')
type = fields.Selection(
[('上产线', '上产线'), ('下产线', '下产线'), ('运送空料架', '运送空料架')], string='类型')
def _get_agv_route_type_selection(self):
return self.env['sf.agv.task.route'].fields_get(['route_type'])['route_type']['selection']
type = fields.Selection(selection=_get_agv_route_type_selection, string='类型')
delivery_duration = fields.Float('配送时长', compute='_compute_delivery_duration')
status = fields.Selection(
[('待下发', '待下发'), ('待配送', '待配送'), ('已配送', '已配送'), ('已取消', '已取消')], string='状态',
default='待下发',
tracking=True)
[('待下发', '待下发'), ('已下发', '已下发'), ('已配送', '已配送'), ('已取消', '已取消')], string='状态',
default='待下发', tracking=True)
is_cnc_program_down = fields.Boolean('程序是否下发', default=False, tracking=True)
is_manual_work = fields.Boolean('人工操作', default=False)
active = fields.Boolean(string="有效", default=True)
agv_scheduling_id = fields.Many2one('sf.agv.scheduling', 'AGV任务调度')
@api.model
def create(self, vals):
if vals['route_id'] and vals.get('type') is None:
if vals.get('route_id') and vals.get('type') is None:
vals['type'] = '运送空料架'
else:
if vals.get('name', '/') == '/' or vals.get('name', '/') is False:
@@ -1615,84 +1709,45 @@ class WorkPieceDelivery(models.Model):
def button_delivery(self):
delivery_ids = []
production_ids = []
workorder_ids = []
is_cnc_down = 0
is_not_production_line = 0
is_not_route = 0
same_production_line_id = None
same_route_id = None
down_status = '待下发'
production_type = None
num = 0
delivery_type = '上产线'
max_num = 4 # 最大配送数量
if len(self) > max_num:
raise UserError('仅限于配送1-4个制造订单请重新选择')
for item in self:
num += 1
if production_type is None:
production_type = item.type
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 item.production_id.production_line_state == '已下产线' and item.state == '待下发' and item.type == '下产线':
# 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 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:
delivery_ids.append(item.id)
production_ids.append(item.production_id.id)
workorder_ids.append(item.workorder_id.id)
if is_cnc_down >= 1:
raise UserError('您所选择制造订单的【CNC程序】暂未下发请在程序下发后再进行配送')
if is_not_production_line >= 1:
raise UserError('您所选择制造订单的【目的生产线】不一致,请重新确认')
if is_not_route >= 1:
raise UserError('您所选择制造订单的【任务路线】不一致,请重新确认')
is_free = self._check_avgsite_state()
if is_free is True:
if delivery_ids:
return {
'name': _('确认'),
'type': 'ir.actions.act_window',
'view_mode': 'form',
'res_model': 'sf.workpiece.delivery.wizard',
'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:
if production_type == '运送空料架':
raise UserError("您所选择的【任务路线】的【终点接驳站】已占用,请在该接驳站空闲时进行配送")
else:
raise UserError(
"您所选择制造订单的【任务路线】的【终点接驳站】已占用,请在该接驳站空闲时或选择其他路线进行配送")
return {
'name': _('确认'),
'type': 'ir.actions.act_window',
'view_mode': 'form',
'res_model': 'sf.workpiece.delivery.wizard',
'target': 'new',
'context': {
'default_delivery_ids': [(6, 0, delivery_ids)],
'default_production_ids': [(6, 0, production_ids)],
'default_delivery_type': delivery_type,
'default_workorder_ids': [(6, 0, workorder_ids)],
'default_confirm_button': '确认配送'
}}
# 验证agv站点是否可用
def _check_avgsite_state(self):
@@ -1798,6 +1853,7 @@ class WorkPieceDelivery(models.Model):
obj.delivery_duration = 0.0
class CMMprogram(models.Model):
_name = 'sf.cmm.program'
_description = "CMM程序"

View File

@@ -150,5 +150,12 @@ access_sf_processing_panel_group_sf_order_user,sf_processing_panel_group_sf_orde
access_sf_production_wizard_group_sf_order_user,sf_production_wizard_group_sf_order_user,model_sf_production_wizard,sf_base.group_sf_order_user,1,1,1,0
access_sf_processing_panel_group_plan_dispatch,sf_processing_panel_group_plan_dispatch,model_sf_processing_panel,sf_base.group_plan_dispatch,1,1,1,0
access_sf_agv_scheduling_admin,sf_agv_scheduling_admin,model_sf_agv_scheduling,base.group_system,1,1,1,1
access_sf_agv_scheduling_group_sf_order_user,sf_agv_scheduling_group_sf_order_user,model_sf_agv_scheduling,sf_base.group_sf_order_user,1,1,1,0
access_sf_agv_scheduling_group_sf_mrp_manager,sf_agv_scheduling_group_sf_mrp_manager,model_sf_agv_scheduling,sf_base.group_sf_mrp_manager,1,1,1,0
access_sf_agv_scheduling_group_sf_equipment_user,sf_agv_scheduling_group_sf_equipment_user,model_sf_agv_scheduling,sf_base.group_sf_equipment_user,1,1,1,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
150
151
152
153
154
155
156
157
158
159
160
161

View File

@@ -1,15 +1,17 @@
var RFID = ''
$(document).off('keydown')
console.log(2222)
$(document).on('keydown', '.modal.d-block.o_technical_modal,body.o_web_client', function (e) {
const dom = $('.customRFID')
if(!dom.length) return
$(document).on('keydown', 'body.o_web_client', function (e) {
setTimeout(() => {
RFID = ''
}, 200)
if(e.key == 'Enter' && e.keyCode == 13 || e.key == 'Tab' && e.keyCode == 9){
console.log(RFID)
if(!RFID || RFID.length <= 3) return;
dom.children('span').text(RFID)
$('[name="button_start"]').trigger('click')
setTimeout(() => {
$('.o_dialog .modal-footer .btn-primary').trigger('click')
}, 50)
RFID = ''
return;
}

View File

@@ -0,0 +1,53 @@
odoo.define('sf_manufacturing.action_dispatch_confirm', function (require) {
const core = require('web.core');
const ajax = require('web.ajax');
const Dialog = require('web.Dialog');
var rpc = require('web.rpc');
var _t = core._t;
async function dispatch_confirm(parent, {params}) {
console.log(params, 'params')
console.log("<div>本次下发的工件数量为:" + params.workorder_count + ",是否确认?</div>", 'content')
const dialog = new Dialog(parent, {
title: "确认",
$content: $('<div>').append("请确认是否仅配送" + params.workorder_count + "个工件?"),
buttons: [
{ text: "确认", classes: 'btn-primary', close: true, click: () => dispatchConfirmed(parent, params) },
{ text: "取消", close: true },
],
});
dialog.open();
async function dispatchConfirmed(parent, params) {
console.log(parent, 'parent')
rpc.query({
model: 'sf.workpiece.delivery.wizard',
method: 'confirm',
args: [params.active_id]
,
kwargs: {
context: params.context,
}
}).then(res => {
console.log(res, 'res')
console.log(res.name, 'res')
parent.services.action.doAction({
'type': 'ir.actions.client',
'tag': 'display_notification',
'target': 'new',
'params': {
'message': '任务下发成功AGV任务调度编号为【' + res.name + '】',
'type': 'success',
'sticky': false,
'next': {'type': 'ir.actions.act_window_close'},
}
});
})
}
}
core.action_registry.add('dispatch_confirm', dispatch_confirm);
return dispatch_confirm;
});

View File

@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<t t-name="sf_manufacturing.button_show" t-inherit="web.ListView.Buttons" t-inherit-mode="extension" owl="1">
<xpath expr="//div/t[@t-if='nbSelected']" position="after">
<t t-elif="!nbSelected">
<t t-foreach="archInfo.headerButtons" t-as="button" t-key="button.id">
<t t-if="button.modifiers.force_show">
<ListViewHeaderButton
list="model.root"
clickParams="button.clickParams"
defaultRank="button.defaultRank"
domain="props.domain"
icon="button.icon"
string="button.string"
title="button.title"
className="button.className+' ms-2'"
/>
</t>
</t>
</t>
</xpath>
<xpath expr="//div/t[@t-if='nbSelected']" position="replace">
<t t-if="nbSelected">
<t t-foreach="archInfo.headerButtons" t-as="button" t-key="button.id">
<t t-if="!button.modifiers.force_show">
<ListViewHeaderButton
list="model.root"
clickParams="button.clickParams"
defaultRank="button.defaultRank"
domain="props.domain"
icon="button.icon"
string="button.string"
title="button.title"
/>
</t>
</t>
<t t-if="!env.isSmall">
<t t-call="web.ListView.Selection"/>
</t>
<t t-foreach="archInfo.headerButtons" t-as="button" t-key="button.id">
<t t-if="button.modifiers.force_show == 1">
<ListViewHeaderButton
list="model.root"
clickParams="button.clickParams"
defaultRank="button.defaultRank"
domain="props.domain"
icon="button.icon"
string="button.string"
title="button.title"
className="button.className+' ms-2'"
/>
</t>
</t>
</t>
</xpath>
</t>
</templates>

View File

@@ -0,0 +1,79 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<!-- agv站点 -->
<record id="view_agv_scheduling_tree" model="ir.ui.view">
<field name="name">agv调度</field>
<field name="model">sf.agv.scheduling</field>
<field name="arch" type="xml">
<tree editable="bottom" delete="0" create="0">
<field name="state" widget="badge"
decoration-success="state == '已配送'"
decoration-warning="state == '待下发'"
decoration-danger="state == '配送中'"
decoration-info="state == '已取消'"
/>
<field name="agv_route_type" invisible="1"/>
<field name="name"/>
<field name="agv_route_name"/>
<field name="start_site_id"/>
<field name="end_site_id"/>
<field name="site_state"/>
<field name="delivery_workpieces"/>
<field name="task_create_time" readonly="1"/>
<field name="task_delivery_time" readonly="1"/>
<field name="task_completion_time" readonly="1"/>
<field name="task_duration" readonly="1"/>
<button
name="button_cancel"
string="取消" type="object"
attrs="{'invisible': ['|', ('state', '!=', '待下发'), ('agv_route_type', '=', '运送空料架')]}"
icon="fa-times"
class="btn-danger"
confirm="你确定要取消这条记录吗?"
/>
</tree>
</field>
</record>
<record id="view_agv_scheduling_search" model="ir.ui.view">
<field name="name">sf.agv.scheduling.search</field>
<field name="model">sf.agv.scheduling</field>
<field name="arch" type="xml">
<search string="AGV调度">
<field name="name"/>
<field name="agv_route_name"/>
<field name="start_site_id"/>
<field name="end_site_id"/>
<field name="delivery_workpieces"/>
<field name="state" string="状态"/>
<filter name="filter_to_be_issued" string="待下发" domain="[('state', 'in', ['待下发'])]"/>
<filter name="filter_delivering" string="配送中" domain="[('state', 'in', ['配送中'])]"/>
<filter name="filter_delivered" string="已配送" domain="[('state', 'in', ['已配送'])]"/>
</search>
</field>
</record>
<record id="action_agv_scheduling_tree" model="ir.actions.act_window">
<field name="name">AGV调度</field>
<field name="res_model">sf.agv.scheduling</field>
<field name="view_mode">tree</field>
<field name="context">
{
"search_default_filter_to_be_issued": 1,
"search_default_filter_delivering": 1,
}
</field>
</record>
<menuitem
id="menu_action_agv_scheduling"
name="AGV调度"
sequence="28"
action="action_agv_scheduling_tree"
parent="mrp.menu_mrp_manufacturing"
groups="sf_base.group_sf_order_user,sf_base.group_sf_mrp_manager,sf_base.group_sf_equipment_user"
/>
</data>
</odoo>

View File

@@ -8,7 +8,7 @@
<field name="arch" type="xml">
<tree editable="bottom">
<field name="name" required="1" attrs="{'readonly': [('id', '!=', False)]}"/>
<field name="owning_region" required="1" attrs="{'readonly': [('id', '!=', False)]}"/>
<field name="workcenter_id" required="1" options="{'no_create': True}"/>
<field name="state" required="1" attrs="{'readonly': [('id', '!=', False)]}"/>
<field name="divide_the_work" required="1"/>
</tree>
@@ -40,8 +40,9 @@
<field name="start_site_id" required="1" options="{'no_create': True}" string="起点接驳站"
attrs="{'readonly': [('id', '!=', False)]}"/>
<field name="end_site_id" required="1" options="{'no_create': True}" string="终点接驳站"/>
<field name="destination_production_line_id" required="1" options="{'no_create': True}"
attrs="{'readonly': [('id', '!=', False)]}"/>
<!-- <field name="destination_production_line_id" required="1" options="{'no_create': True}"-->
<!-- attrs="{'readonly': [('id', '!=', False)]}"/>-->
<field name="workcenter_id"/>
</tree>
</field>
</record>

View File

@@ -122,7 +122,7 @@
groups="sf_base.group_sf_mrp_user"/>
</xpath>
<xpath expr="(//header//button[@name='button_scrap'])" position="replace">
<button name="button_scrap" invisible="0"/>
<button name="button_scrap" invisible="1"/>
<button name="do_update_program" string="更新程序" type="object" groups="sf_base.group_sf_mrp_user"
confirm="是否确认更新程序"
attrs="{'invisible': ['|',('state', '!=', 'rework'),('programming_state', '!=', '已编程未下发')]}"/>

View File

@@ -182,6 +182,7 @@
</xpath>
<xpath expr="//field[@name='resource_calendar_id']" position="after">
<field name="is_process_outsourcing"/>
<field name="is_agv_scheduling"/>
</xpath>
</field>
</record>

View File

@@ -40,10 +40,15 @@
<field name="date_planned_finished" string="计划结束日期" optional="hide"/>
</xpath>
<xpath expr="//button[@name='button_start']" position="attributes">
<!-- <attribute name="attrs">{'invisible': ['|', '|', '|','|','|', ('production_state','in', ('draft',-->
<!-- 'done',-->
<!-- 'cancel')), ('working_state', '=', 'blocked'), ('state', 'in', ('done', 'cancel')),-->
<!-- ('is_user_working', '!=', False),("user_permissions","=",False),("name","=","CNC加工")]}-->
<!-- </attribute>-->
<attribute name="attrs">{'invisible': ['|', '|', '|','|','|', ('production_state','in', ('draft',
'done',
'cancel')), ('working_state', '=', 'blocked'), ('state', 'in', ('done', 'cancel')),
('is_user_working', '!=', False),("user_permissions","=",False),("name","=","CNC加工")]}
('is_user_working', '!=', False),("user_permissions","=",False),("name","in",("CNC加工","解除装夹"))]}
</attribute>
</xpath>
<xpath expr="//button[@name='%(mrp.act_mrp_block_workcenter_wo)d']" position="attributes">
@@ -113,6 +118,9 @@
<field name="model">mrp.workorder</field>
<field name="inherit_id" ref="mrp.mrp_production_workorder_form_view_inherit"/>
<field name="arch" type="xml">
<xpath expr="//form" position="inside">
<script src="sf_manufacturing/static/src/js/customRFID.js"/>
</xpath>
<xpath expr="//header/field[@name='state']" position="replace">
<field name="state" widget="statusbar"
statusbar_visible="pending,waiting,ready,progress,to be detected,done,rework"/>
@@ -141,6 +149,7 @@
<field name='name' invisible="1"/>
<field name='is_rework' invisible="1"/>
<field name='is_delivery' invisible="1"/>
<field name="is_trayed" invisible="1"/>
<!-- <field name='is_send_program_again' invisible="1"/>-->
<!-- 工单form页面的开始停工按钮等 -->
<!-- <button name="button_start" type="object" string="开始" class="btn-success" -->
@@ -153,8 +162,12 @@
<!-- attrs="{'invisible': ['|', '|', ('production_state', 'not in', ('pending_processing', 'pending_cam', 'pending_era_cam')), ('state','!=','progress'), ('routing_type', 'not in', ('装夹预调', 'CNC加工', '解除装夹'))]}" -->
<!-- groups="sf_base.group_sf_mrp_user" confirm="是否确认完工"/> -->
<!-- <button name="button_start" type="object" string="开始" class="btn-success" confirm="是否确认开始"-->
<!-- attrs="{'invisible': ['|', '|', '|', ('production_state','in', ('draft', 'done', 'cancel')), ('working_state', '=', 'blocked'), ('state', 'in', ('done', 'cancel','to be detected')), ('is_user_working', '!=', False)]}"/>-->
<button name="button_start" type="object" string="开始" class="btn-success" confirm="是否确认开始"
attrs="{'invisible': ['|', '|', '|', ('production_state','in', ('draft', 'done', 'cancel')), ('working_state', '=', 'blocked'), ('state', 'in', ('done', 'cancel','to be detected')), ('is_user_working', '!=', False)]}"/>
attrs="{'invisible': ['|', '|', '|', '|', '|', ('routing_type', '=', '装夹预调'), ('routing_type', '=', '解除装夹'), ('production_state','in', ('draft', 'done', 'cancel')), ('working_state', '=', 'blocked'), ('state', 'in', ('done', 'cancel','to be detected')), ('is_user_working', '!=', False)]}"/>
<button name="button_start" type="object" string="开始" class="btn-success"
attrs="{'invisible': ['|', '|', '|', '|', ('routing_type', '!=', '装夹预调'), ('production_state','in', ('draft', 'done', 'cancel')), ('working_state', '=', 'blocked'), ('state', 'in', ('done', 'cancel','to be detected')), ('is_user_working', '!=', False)]}"/>
<button name="button_pending" type="object" string="暂停" class="btn-warning"
attrs="{'invisible': ['|', '|', ('production_state', 'in', ('draft', 'done', 'cancel')), ('working_state', '=', 'blocked'), ('is_user_working', '=', False)]}"/>
<button name="button_finish" type="object" string="完成" class="btn-success" confirm="是否确认完工"
@@ -175,11 +188,14 @@
<!-- context="{'default_workcenter_id': workcenter_id}" class="btn-danger" -->
<!-- groups="sf_base.group_sf_mrp_user" -->
<!-- attrs="{'invisible': ['|', '|', ('production_state', 'in', ('draft', 'done', 'cancel')), ('working_state', '!=', 'blocked'),('state','=','done')]}"/> -->
<button name="button_workpiece_delivery" type="object" string="工件配送" class="btn-primary"
attrs="{'invisible': ['|','|','|','|',('routing_type','!=','装夹预调'),('is_delivery','=',True),('state','!=','done'),('is_rework','=',True),'&amp;',('rfid_code','in',['',False]),('state','=','done')]}"/>
<!-- <button name="button_workpiece_delivery" type="object" string="工件配送" class="btn-primary"-->
<!-- attrs="{'invisible': ['|','|','|','|',('routing_type','!=','装夹预调'),('is_delivery','=',True),('state','!=','done'),('is_rework','=',True),'&amp;',('rfid_code','in',['',False]),('state','=','done')]}"/>-->
<button name="button_rework_pre" type="object" string="返工"
class="btn-primary"
attrs="{'invisible': ['|','|',('routing_type','!=','装夹预调'),('state','!=','progress'),('is_rework','=',True)]}"/>
<button name="unbind_tray" type="object" string="解绑托盘"
class="btn-primary"
attrs="{'invisible': ['|', '|', ('routing_type','!=','装夹预调'),('state','!=','progress'), ('is_trayed', '=', False)]}"/>
<button name="print_method" type="object" string="打印二维码" class="btn-primary"
attrs="{'invisible': ['|',('routing_type','!=','解除装夹'),('state','!=','done')]}"/>
</xpath>
@@ -642,27 +658,27 @@
<field name="arch" type="xml">
<tree string="工件配送" class="center" create="0" delete="0">
<header>
<button name="button_delivery" type="object" string="配送" class="oe_highlight"/>
<button name="button_delivery" type="object" string="工件配送" class="btn-primary" attrs="{'force_show':1}"/>
</header>
<field name="status" widget="badge"
decoration-success="status == '已配送'"
decoration-warning="status == '待下发'"
decoration-danger="status == '待配送'"
decoration-danger="status == '已下发'"
decoration-info="status == '已取消'"
/>
<field name="name"/>
<field name="production_id"/>
<field name="type" readonly="1"/>
<field name="production_line_id" options="{'no_create': True}" readonly="1"/>
<field name="route_id" options="{'no_create': True}"
domain="[('route_type','in',['上产线','下产线'])]"/>
<!-- <field name="route_id" options="{'no_create': True}"-->
<!-- domain="[('route_type','in',['上产线','下产线'])]"/>-->
<field name="feeder_station_start_id" readonly="1" force_save="1"/>
<field name="feeder_station_destination_id" readonly="1" force_save="1"/>
<!-- <field name="feeder_station_destination_id" readonly="1" force_save="1"/>-->
<field name="is_cnc_program_down" readonly="1"/>
<!-- <field name="rfid_code"/>-->
<field name="task_delivery_time" readonly="1"/>
<field name="task_completion_time" readonly="1"/>
<field name="delivery_duration" widget="float_time"/>
<!-- <field name="task_delivery_time" readonly="1"/>-->
<!-- <field name="task_completion_time" readonly="1"/>-->
<!-- <field name="delivery_duration" widget="float_time"/>-->
</tree>
</field>
</record>
@@ -717,7 +733,7 @@
<field name="arch" type="xml">
<search string="工件配送">
<filter name="filter_to_be_issued" string="待下发" domain="[('status', 'in', ['待下发'])]"/>
<filter name="filter_waiting_delivery" string="待配送" domain="[('status', 'in', ['待配送'])]"/>
<filter name="filter_issued" string="已下发" domain="[('status', 'in', ['已下发'])]"/>
<filter name="filter_delivered" string="已配送" domain="[('status', 'in', ['已配送'])]"/>
<field name="rfid_code"/>
<field name="production_id"/>
@@ -741,7 +757,7 @@
<field name="res_model">sf.workpiece.delivery</field>
<field name="search_view_id" ref="sf_workpiece_delivery_search"/>
<field name="context">{'search_default_filter_to_be_issued': 1,
'search_default_filter_waiting_delivery': 1}
'search_default_filter_issued': 1}
</field>
<field name="view_mode">tree,form</field>
<field name="domain">
@@ -828,5 +844,11 @@
<field name="view_mode">tree</field>
<field name="domain">[('type','in',['运送空料架']),('name','not ilike','WDO')]</field>
</record>
<menuitem id="mrp.menu_mrp_manufacturing"
name="Operations"
sequence="10"
parent="mrp.menu_mrp_root"
groups="sf_base.group_sf_order_user,sf_base.group_sf_mrp_manager,sf_base.group_sf_equipment_user"/>
</odoo>

View File

@@ -4,31 +4,28 @@
<field name="name">sf.workpiece.delivery.wizard.form.view</field>
<field name="model">sf.workpiece.delivery.wizard</field>
<field name="arch" type="xml">
<form>
<form class="no_auto_focus">
<sheet>
<field name="delivery_ids" invisible="True"/>
<field name="workorder_id" invisible="True"/>
<field name="type" invisible="True"/>
<group attrs="{'invisible': [('type', 'in', ['运送空料架'])]}">
<field name="workorder_ids" invisible="True"/>
<field name="delivery_type" invisible="True"/>
<field name="confirm_button" invisible="1"/>
<field name="_barcode_scanned" widget="barcode_handler"/>
<group col="1">
<field name="production_ids" readonly="1" widget="many2many_tags" string="制造订单号"/>
<div class="o_address_format">
<lable for="rfid_code"></lable>
<field name="rfid_code" class="o_address_zip"/>
<button name="recognize_production" string="识别" type="object" class="oe_highlight"/>
</div>
<field name="destination_production_line_id" readonly="1"/>
<field name="route_id"/>
<field name="delivery_type" readonly="1"/>
<field name="feeder_station_start_id" options="{'no_create': True}" required="1"/>
<field name="workcenter_id" options="{'no_create': True}"/>
</group>
<group attrs="{'invisible': [('type', 'in', ['运送空料架'])]}">
<field name="feeder_station_start_id" focesave="1" readonly="1"/>
<field name="feeder_station_destination_id" focesave="1" readonly="1"/>
</group>
<div attrs="{'invisible': [('type', 'in', ['上产线','下产线'])]}">
是否确定配送
</div>
<footer>
<button string="配送" name="confirm" type="object" class="oe_highlight"/>
<button string="确认配送" name="dispatch_confirm" type="object" class="oe_highlight o_wizard_confirm_button" attrs="{'invisible': [('confirm_button', '!=', '确认配送')]}"/>
<button string="确认解除" name="dispatch_confirm" type="object" class="oe_highlight o_wizard_confirm_button" attrs="{'invisible': [('confirm_button', '!=', '确认解除')]}"/>
<button string="取消" class="btn btn-secondary" special="cancel"/>
<script>
setTimeout(function(){
$('#feeder_station_start_id').blur()
}, 200)
</script>
</footer>
</sheet>
</form>

View File

@@ -1,88 +1,200 @@
# -*- coding: utf-8 -*-
# Part of YiZuo. See LICENSE file for full copyright and licensing details.
import json
import logging
from odoo.exceptions import UserError, ValidationError
from datetime import datetime
from odoo import models, api, fields, _
from datetime import datetime, date
from odoo import models, api, fields
def convert_datetime(obj):
if isinstance(obj, (datetime, date)):
return obj.isoformat() # 将 datetime 或 date 对象转换为 ISO 8601 字符串格式
raise TypeError(f"Type {type(obj)} not serializable")
class WorkpieceDeliveryWizard(models.TransientModel):
_name = 'sf.workpiece.delivery.wizard'
_inherit = ["barcodes.barcode_events_mixin"]
_description = '工件配送'
delivery_ids = fields.Many2many('sf.workpiece.delivery', string='配送')
rfid_code = fields.Char('rfid码')
workorder_id = fields.Many2one('mrp.workorder', string='')
delivery_ids = fields.Many2many('sf.workpiece.delivery', string='配送')
workorder_ids = fields.Many2many('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='类型')
workcenter_id = fields.Many2one(string='所属区域', comodel_name='mrp.workcenter', tracking=True)
confirm_button = fields.Char('按钮名称')
@api.onchange('delivery_type')
def _onchange_type(self):
if self.delivery_type:
routes = self.env['sf.agv.task.route'].search([('route_type', '=', self.delivery_type)])
if self.workcenter_id:
routes = routes.filtered(lambda a: a.start_site_id.workcenter_id.id == self.workcenter_id.id)
start_site_ids = routes.mapped('start_site_id.id')
workcenter_ids = routes.mapped('end_site_id.workcenter_id.id')
if workcenter_ids:
self.workcenter_id = workcenter_ids[0]
return {
'domain':
{
'feeder_station_start_id': [('id', 'in', start_site_ids)],
'workcenter_id': [('id', 'in', workcenter_ids)],
}
}
else:
return {
'domain':
{
'feeder_station_start_id': [],
'workcenter_id': [],
}
}
def _get_agv_route_type_selection(self):
return self.env['sf.agv.task.route'].fields_get(['route_type'])['route_type']['selection']
delivery_type = fields.Selection(selection=_get_agv_route_type_selection, string='类型')
def dispatch_confirm(self):
if len(self.workorder_ids) < 4:
return {
'type': 'ir.actions.client',
'tag': 'dispatch_confirm',
'params': {
'workorder_count': len(self.workorder_ids),
'active_id': self.id,
'context': self.env.context
}
}
else:
scheduling = self.confirm()
# 显示通知
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'target': 'new',
'params': {
'message': '任务下发成功AGV任务调度编号为【%s' % scheduling['name'],
'type': 'success',
'sticky': False,
'next': {'type': 'ir.actions.act_window_close'},
}
}
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:
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()
try:
# if self.workorder_id:
# self.workorder_id.workpiece_delivery_ids[0].agv_scheduling_id()
# else:
# 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:
scheduling = self.env['sf.agv.scheduling'].add_scheduling(
agv_start_site_name=self.feeder_station_start_id.name,
agv_route_type=self.delivery_type,
workorders=self.workorder_ids,
)
# 如果关联了工件配送单,则修改状态为已下发
if self.delivery_ids:
self.delivery_ids.write({
'status': '已下发',
'agv_scheduling_id': scheduling.id,
'feeder_station_start_id': scheduling.start_site_id.id,
})
def recognize_production(self):
# production_ids = []
# delivery_ids = []
# aa = self.production_ids.workorder_ids.filtered(
# lambda b: b.routing_type == "装夹预调").workpiece_delivery_ids.filtered(
# lambda c: c.rfid_code == self.rfid_code)
# logging.info('aa:%s' % aa)
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
}}
# 如果是解除装夹工单,则需要处理工单逻辑
for item in self.workorder_ids:
if item.routing_type == '解除装夹' and item.state == 'ready':
item.button_start()
item.button_finish()
return scheduling.read()[0]
except Exception as e:
logging.info('%s任务下发失败:%s' % (self.delivery_type, e))
raise UserError('%s任务下发失败:%s' % (self.delivery_type, e))
# def recognize_production(self):
# # production_ids = []
# # delivery_ids = []
# # aa = self.production_ids.workorder_ids.filtered(
# # lambda b: b.routing_type == "装夹预调").workpiece_delivery_ids.filtered(
# # lambda c: c.rfid_code == self.rfid_code)
# # logging.info('aa:%s' % aa)
# 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
def on_barcode_scanned(self, barcode):
delivery_type = self.env.context.get('default_delivery_type')
if delivery_type == '上产线':
workorder = self.env['mrp.workorder'].search(
[('production_line_state', '=', '待上产线'), ('rfid_code', '=', barcode),
('state', '=', 'done')])
# 找到对应的配送单
delivery = self.env['sf.workpiece.delivery'].search(
[('type', '=', '上产线'), ('rfid_code', '=', barcode),
('status', '=', '待下发')])
if delivery:
self.delivery_ids |= delivery
elif delivery_type == '运送空料架':
workorder = self.env['mrp.workorder'].search(
[('routing_type', '=', '解除装夹'), ('rfid_code', '=', barcode),
('state', '=', 'ready')])
if workorder:
if (len(self.production_ids) > 0 and
workorder.production_line_id.id != self.production_ids[0].production_line_id.id):
raise UserError('该rfid对应的制造订单号为%s的目的生产线不一致' % workorder.production_id.name)
# 将对象添加到对应的同模型且是多对多类型里
self.production_ids |= workorder.production_id
self.workorder_ids |= workorder
else:
raise UserError('该rfid码对应的工单不存在')
return

File diff suppressed because it is too large Load Diff

View File

@@ -337,6 +337,7 @@
name="空料架配送"
sequence="11"
action="sf_manufacturing.sf_workpiece_delivery_empty_racks_act"
groups="base.group_system"
parent="mrp.menu_mrp_manufacturing"
/>
<!-- <menuitem -->

View File

@@ -20,7 +20,8 @@
'views/sale_order_view.xml',
'views/res_partner_view.xml',
'views/purchase_order_view.xml',
'views/quick_easy_order_view.xml'
'views/quick_easy_order_view.xml',
'views/purchase_menu.xml'
],
'assets': {
'web.assets_backend': [

View File

@@ -13,6 +13,11 @@ READONLY_FIELD_STATES = {
class ReSaleOrder(models.Model):
_inherit = 'sale.order'
mrp_production_count = fields.Integer(
"Count of MO generated",
compute='_compute_mrp_production_ids',
groups='mrp.group_mrp_user,sf_base.group_sale_salemanager,sf_base.group_sale_director')
logistics_way = fields.Selection([('自提', '自提'), ('到付', '到付'), ('在线支付', '在线支付')], string='物流方式')
state = fields.Selection(
selection=[

View File

@@ -96,5 +96,28 @@ access_product_supplierinfo_group_plan_director,product.supplierinfo user,produc
access_product_category_group_plan_director,product.category user,product.model_product_category,sf_base.group_plan_director,1,1,1,0
access_purchase_report_sf_base_group_purchase,purchase_report_sf_base_group_purchase,purchase.model_purchase_report,sf_base.group_purchase,1,0,0,0
access_purchase_report_sf_base_group_purchase_director,purchase_report_sf_base_group_purchase_director,purchase.model_purchase_report,sf_base.group_purchase_director,1,0,0,0
access_sale_order_sf_base_group_purchase,sale_order_sf_base_group_purchase,model_sale_order,sf_base.group_purchase,1,0,0,0
access_sale_order_sf_base_group_purchase_director,sale_order_sf_base_group_purchase_director,model_sale_order,sf_base.group_purchase_director,1,0,0,0
access_quality_check_group_sale_salemanager,quality_check_group_sale_salemanager,quality.model_quality_check,sf_base.group_sale_salemanager,1,0,0,0
access_quality_check_group_sale_director,quality_check_group_sale_director,quality.model_quality_check,sf_base.group_sale_director,1,0,0,0
access_stock_picking_group_sale_salemanager,stock_picking_group_sale_salemanager,stock.model_stock_picking,sf_base.group_sale_salemanager,1,0,0,0
access_stock_picking_group_sale_director,stock_picking_group_sale_director,stock.model_stock_picking,sf_base.group_sale_director,1,0,0,0
access_mrp_workorder_group_sale_salemanager,mrp_workorder_group_sale_salemanager,mrp.model_mrp_workorder,sf_base.group_sale_salemanager,1,0,0,0
access_mrp_workorder_group_sale_director,mrp_workorder_group_sale_director,mrp.model_mrp_workorder,sf_base.group_sale_director,1,0,0,0
access_mrp_unbuild_group_sale_salemanager,mrp_unbuild_group_sale_salemanager,mrp.model_mrp_unbuild,sf_base.group_sale_salemanager,1,0,0,0
access_mrp_unbuild_group_sale_director,mrp_unbuild_group_sale_director,mrp.model_mrp_unbuild,sf_base.group_sale_director,1,0,0,0
access_mrp_workcenter_productivity_group_sale_salemanager,mrp_workcenter_productivity_group_sale_salemanager,mrp.model_mrp_workcenter_productivity,sf_base.group_sale_salemanager,1,0,0,0
access_mrp_workcenter_productivity_group_sale_director,mrp_workcenter_productivity_group_sale_director,mrp.model_mrp_workcenter_productivity,sf_base.group_sale_director,1,0,0,0
access_sf_detection_result_group_sale_salemanager,sf_detection_result_group_sale_salemanager,sf_manufacturing.model_sf_detection_result,sf_base.group_sale_salemanager,1,0,0,0
access_sf_detection_result_group_sale_director,sf_detection_result_group_sale_director,sf_manufacturing.model_sf_detection_result,sf_base.group_sale_director,1,0,0,0
access_stock_scrap_group_sale_salemanager,stock_scrap_group_sale_salemanager,stock.model_stock_scrap,sf_base.group_sale_salemanager,1,0,0,0
access_stock_scrap_group_sale_director,stock_scrap_group_sale_director,stock.model_stock_scrap,sf_base.group_sale_director,1,0,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
96 access_purchase_report_sf_base_group_purchase_director purchase_report_sf_base_group_purchase_director purchase.model_purchase_report sf_base.group_purchase_director 1 0 0 0
97 access_sale_order_sf_base_group_purchase sale_order_sf_base_group_purchase model_sale_order sf_base.group_purchase 1 0 0 0
98 access_sale_order_sf_base_group_purchase_director sale_order_sf_base_group_purchase_director model_sale_order sf_base.group_purchase_director 1 0 0 0
99 access_quality_check_group_sale_salemanager quality_check_group_sale_salemanager quality.model_quality_check sf_base.group_sale_salemanager 1 0 0 0
100 access_quality_check_group_sale_director quality_check_group_sale_director quality.model_quality_check sf_base.group_sale_director 1 0 0 0
101 access_stock_picking_group_sale_salemanager stock_picking_group_sale_salemanager stock.model_stock_picking sf_base.group_sale_salemanager 1 0 0 0
102 access_stock_picking_group_sale_director stock_picking_group_sale_director stock.model_stock_picking sf_base.group_sale_director 1 0 0 0
103 access_mrp_workorder_group_sale_salemanager mrp_workorder_group_sale_salemanager mrp.model_mrp_workorder sf_base.group_sale_salemanager 1 0 0 0
104 access_mrp_workorder_group_sale_director mrp_workorder_group_sale_director mrp.model_mrp_workorder sf_base.group_sale_director 1 0 0 0
105 access_mrp_unbuild_group_sale_salemanager mrp_unbuild_group_sale_salemanager mrp.model_mrp_unbuild sf_base.group_sale_salemanager 1 0 0 0
106 access_mrp_unbuild_group_sale_director mrp_unbuild_group_sale_director mrp.model_mrp_unbuild sf_base.group_sale_director 1 0 0 0
107 access_mrp_workcenter_productivity_group_sale_salemanager mrp_workcenter_productivity_group_sale_salemanager mrp.model_mrp_workcenter_productivity sf_base.group_sale_salemanager 1 0 0 0
108 access_mrp_workcenter_productivity_group_sale_director mrp_workcenter_productivity_group_sale_director mrp.model_mrp_workcenter_productivity sf_base.group_sale_director 1 0 0 0
109 access_sf_detection_result_group_sale_salemanager sf_detection_result_group_sale_salemanager sf_manufacturing.model_sf_detection_result sf_base.group_sale_salemanager 1 0 0 0
110 access_sf_detection_result_group_sale_director sf_detection_result_group_sale_director sf_manufacturing.model_sf_detection_result sf_base.group_sale_director 1 0 0 0
111 access_stock_scrap_group_sale_salemanager stock_scrap_group_sale_salemanager stock.model_stock_scrap sf_base.group_sale_salemanager 1 0 0 0
112 access_stock_scrap_group_sale_director stock_scrap_group_sale_director stock.model_stock_scrap sf_base.group_sale_director 1 0 0 0
113
114
115
116
117
118
119
120
121
122
123

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<!-- 采购-产品 -->
<menuitem id="purchase.menu_purchase_products" name="Products" parent="purchase.menu_purchase_root"
groups="sf_base.group_purchase_director,sf_base.group_purchase"
sequence="5"/>
<!-- 采购-产品-产品 -->
<menuitem id="purchase.menu_procurement_partner_contact_form" name="Products"
action="purchase.product_normal_action_puchased" parent="purchase.menu_purchase_products"
groups="sf_base.group_purchase_director,sf_base.group_purchase"
sequence="20"/>
<!-- 采购-报表 -->
<menuitem id="purchase.purchase_report_main" name="Reporting" parent="purchase.menu_purchase_root" sequence="99"
groups="purchase.group_purchase_manager,sf_base.group_purchase_director,sf_base.group_purchase"/>
<!-- 采购-报表-采购 -->
<menuitem id="purchase.purchase_report" name="Purchase" parent="purchase.purchase_report_main" sequence="99"
groups="purchase.group_purchase_manager,sf_base.group_purchase_director,sf_base.group_purchase"
action="purchase.action_purchase_order_report_all"/>
</odoo>

View File

@@ -86,6 +86,18 @@
</attribute>
</xpath>
<xpath expr="//form/sheet/div[@name='button_box']/button[@name='action_view_picking']"
position="replace">
<button type="object"
name="action_view_picking"
class="oe_stat_button"
icon="fa-truck" attrs="{'invisible':[('incoming_picking_count','=', 0)]}"
groups="stock.group_stock_user,sf_base.group_purchase,sf_base.group_purchase_director">
<field name="incoming_picking_count" widget="statinfo" string="收货"
help="Incoming Shipments"/>
</button>
</xpath>
<xpath expr="//field[@name='order_line']" position="attributes">
<attribute name="attrs">{'readonly': [('state', 'in', ['purchase'])]}
</attribute>

View File

@@ -6,6 +6,13 @@
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale.view_order_form"/>
<field name="arch" type="xml">
<xpath expr="//button[@name='action_view_delivery']" position="attributes">
<attribute name="groups">sf_base.group_sale_salemanager,sf_base.group_sale_director</attribute>
</xpath>
<xpath expr="//button[@name='action_view_mrp_production']" position="attributes">
<attribute name="groups">mrp.group_mrp_user,sf_base.group_sale_salemanager,sf_base.group_sale_director
</attribute>
</xpath>
<xpath expr="//field[@name='user_id']" position="replace">
<field name="user_id" widget="many2one_avatar_user" context="{'is_sale': True }"/>
</xpath>
@@ -35,12 +42,12 @@
<xpath expr="//form/header/button[@name='action_confirm'][2]" position="replace">
<button name="action_confirm" data-hotkey="v"
string="确认接单" type="object" context="{'validate_analytic': True}"
attrs="{'invisible': ['|','&amp;',('check_status', '!=', 'approved'),('state', 'in', ['draft','cancel']),'&amp;','&amp;',('check_status', '=', 'approved'),('state', 'in', ['sale','cancel']),('delivery_status', '!=', False)]}"/>
attrs="{'invisible': ['|','&amp;','|',('check_status', '!=', 'approved'),('state', 'in', ['draft','cancel']),'&amp;','&amp;',('check_status', '=', 'approved'),('state', 'in', ['sale','cancel']),('delivery_status', '!=', False),('state', 'in', ['cancel'])]}"/>
</xpath>
<xpath expr="//form/header/button[@name='action_cancel']" position="attributes">
<attribute name="attrs">{'invisible': ['|','&amp;',('check_status', '!=', 'approved'),('state',
<attribute name="attrs">{'invisible': ['|','&amp;','|', ('check_status', '!=', 'approved'),('state',
'in', ['draft','cancel']),'&amp;','&amp;',('check_status', '=', 'approved'),('state', 'in',
['sale','cancel']),('delivery_status', '!=', False)]}
['sale','cancel']),('delivery_status', '!=', False), ('state', 'in', ['cancel'])]}
</attribute>
</xpath>
<xpath expr="//form/header/button[@name='action_draft']" position="attributes">

View File

@@ -94,47 +94,47 @@ class MachineTableToolChangingApply(models.Model):
if len(records) > 1:
raise ValidationError('该刀位号已存在,请重新选择!!!')
@api.constrains('functional_tool_status')
def automation_apply_for_tool_change(self):
"""
自动申请换刀
:return:
"""
# 更新数据到机台换刀申请界面
if self.functional_tool_status == '报警' and not self.sf_functional_tool_assembly_id:
machine_table_tool_changing_apply = self.env['sf.machine.table.tool.changing.apply'].search(
[('maintenance_equipment_id', '=', self.maintenance_equipment_id.id),
('cutter_spacing_code_id', '=', self.cutter_spacing_code_id.id)
])
# 创建功能刀具预警记录
self.env['sf.functional.tool.warning'].create_tool_warning_record({'tool_changing_apply_id': self})
# 新建组装任务
sf_functional_tool_assembly = self.env['sf.functional.tool.assembly'].create({
'functional_tool_name': self.functional_tool_name,
'functional_tool_type_id': self.functional_tool_type_id.id,
'functional_tool_diameter': self.diameter,
'knife_tip_r_angle': self.knife_tip_r_angle,
'coarse_middle_thin': '3',
'new_former': '0',
'functional_tool_length': self.extension_length,
'effective_length': self.effective_length,
'loading_task_source': '1',
'use_tool_time': fields.Datetime.now() + timedelta(hours=4),
'production_line_name_id': self.production_line_id.id,
'machine_tool_name_id': self.maintenance_equipment_id.id,
'applicant': '系统自动',
'apply_time': fields.Datetime.now(),
'cutter_spacing_code_id': self.cutter_spacing_code_id.id,
'whether_standard_knife': self.whether_standard_knife,
'reason_for_applying': '机台报警自动换刀',
'sf_machine_table_tool_changing_apply_id': self.id
})
machine_table_tool_changing_apply.write(
{'status': '1',
'sf_functional_tool_assembly_id': sf_functional_tool_assembly.id})
# @api.constrains('functional_tool_status')
# def automation_apply_for_tool_change(self):
# """
# 自动申请换刀
# :return:
# """
# # 更新数据到机台换刀申请界面
# if self.functional_tool_status == '报警' and not self.sf_functional_tool_assembly_id:
# machine_table_tool_changing_apply = self.env['sf.machine.table.tool.changing.apply'].search(
# [('maintenance_equipment_id', '=', self.maintenance_equipment_id.id),
# ('cutter_spacing_code_id', '=', self.cutter_spacing_code_id.id)
# ])
#
# # 创建功能刀具预警记录
# self.env['sf.functional.tool.warning'].create_tool_warning_record({'tool_changing_apply_id': self})
#
# # 新建组装任务
# sf_functional_tool_assembly = self.env['sf.functional.tool.assembly'].create({
# 'functional_tool_name': self.functional_tool_name,
# 'functional_tool_type_id': self.functional_tool_type_id.id,
# 'functional_tool_diameter': self.diameter,
# 'knife_tip_r_angle': self.knife_tip_r_angle,
# 'coarse_middle_thin': '3',
# 'new_former': '0',
# 'functional_tool_length': self.extension_length,
# 'effective_length': self.effective_length,
# 'loading_task_source': '1',
# 'use_tool_time': fields.Datetime.now() + timedelta(hours=4),
# 'production_line_name_id': self.production_line_id.id,
# 'machine_tool_name_id': self.maintenance_equipment_id.id,
# 'applicant': '系统自动',
# 'apply_time': fields.Datetime.now(),
# 'cutter_spacing_code_id': self.cutter_spacing_code_id.id,
# 'whether_standard_knife': self.whether_standard_knife,
# 'reason_for_applying': '机台报警自动换刀',
# 'sf_machine_table_tool_changing_apply_id': self.id
# })
#
# machine_table_tool_changing_apply.write(
# {'status': '1',
# 'sf_functional_tool_assembly_id': sf_functional_tool_assembly.id})
def revocation_1(self):
"""
@@ -760,6 +760,15 @@ class FunctionalToolDismantle(models.Model):
functional_tool_id = fields.Many2one('sf.functional.cutting.tool.entity', '功能刀具', required=True, tracking=True,
domain=[('functional_tool_status', '!=', '已拆除'),
('current_location', '=', '刀具房')])
@api.onchange('functional_tool_id')
def _onchange_functional_tool_id(self):
for item in self:
if item:
dismantle_id = self.search([('functional_tool_id', '=', item.functional_tool_id.id)])
if dismantle_id:
raise ValidationError(f'Rfid为【{item.rfid}】的功能刀具已经存在拆解单,单号是【{dismantle_id[0].code}')
tool_type_id = fields.Many2one('sf.functional.cutting.tool.model', string='功能刀具类型', store=True,
compute='_compute_functional_tool_num')
tool_groups_id = fields.Many2one('sf.tool.groups', '刀具组', compute='_compute_functional_tool_num', store=True)
@@ -938,15 +947,23 @@ class FunctionalToolDismantle(models.Model):
if self.chuck_freight_id == self.pad_freight_id:
raise ValidationError('【夹头】和【刀盘】的目标货位重复,请重新选择!')
def tool_scrap(self):
self.scrap_boolean = True
def tool_no_scrap(self):
self.scrap_boolean = False
def confirmation_disassembly(self):
logging.info('%s刀具确认开始拆解' % self.dismantle_cause)
code = self.code
if self.functional_tool_id.functional_tool_status == '已拆除':
raise ValidationError('Rfid为【%s】的功能刀具已经拆解,请勿重复操作!' % self.functional_tool_id.rfid_dismantle)
raise ValidationError('Rfid为【%s名称为【%s的功能刀具已经拆解,请勿重复操作!' % (
self.functional_tool_id.rfid_dismantle, self.name))
# 对拆解的功能刀具进行校验,只有在刀具房的功能刀具才能拆解
if self.functional_tool_id.tool_room_num == 0:
raise ValidationError('Rfid为【%s】的功能刀具当前位置为【%s】,不能进行拆解!' % (
self.rfid, self.functional_tool_id.current_location))
elif self.functional_tool_id.functional_tool_status != '报警':
if self.functional_tool_id.tool_room_num == 0:
raise ValidationError('Rfid为【%s】的功能刀具当前位置为【%s】,不能进行拆解!' % (
self.rfid, self.functional_tool_id.current_location))
# 目标重复校验
self.location_duplicate_check()
datas = {'scrap': [], 'picking': []}
@@ -1005,6 +1022,14 @@ class FunctionalToolDismantle(models.Model):
'rfid': '%s(已拆解)' % self.rfid,
'state': '已拆解'
})
# ==================修改刀具预警信息的值============
warning_id = self.env['sf.functional.tool.warning'].sudo().search(
[('functional_tool_id', '=', self.functional_tool_id.id)])
if warning_id:
warning_id.sudo().write({
'dispose_user': self.env.user.name,
'dispose_time': fields.Datetime.now()
})
logging.info('%s】刀具拆解成功!' % self.name)
def create_tool_picking_scrap(self, datas):

View File

@@ -11,6 +11,7 @@ from odoo.exceptions import ValidationError
class FunctionalCuttingToolEntity(models.Model):
_name = 'sf.functional.cutting.tool.entity'
_description = '功能刀具列表'
_order = 'functional_tool_status'
functional_tool_name_id = fields.Many2one('sf.functional.tool.assembly', string='功能刀具组装单', readonly=True)
@@ -53,6 +54,22 @@ class FunctionalCuttingToolEntity(models.Model):
safe_inventory_id = fields.Many2one('sf.real.time.distribution.of.functional.tools',
string='功能刀具安全库存', readonly=True)
@api.onchange('functional_tool_status')
def _onchange_functional_tool_status(self):
for item in self:
if item:
if item.functional_tool_status == '报警':
# 创建报警刀具拆解单
self.env['sf.functional.tool.dismantle'].sudo().create({
'functional_tool_id': item.ids[0],
'dismantle_cause': '寿命到期报废'
})
# 创建刀具报警记录
self.env['sf.functional.tool.warning'].sudo().create({
'rfid': item.rfid,
'functional_tool_id': item.ids[0]
})
@api.depends('barcode_id.quant_ids', 'barcode_id.quant_ids.location_id', 'functional_tool_status',
'current_shelf_location_id')
def _compute_current_location_id(self):
@@ -101,27 +118,28 @@ class FunctionalCuttingToolEntity(models.Model):
def tool_in_out_stock_location(self, location_id):
tool_room_id = self.env['stock.location'].search([('name', '=', '刀具房')])
pre_manufacturing_id = self.env['stock.location'].search([('name', '=', '制造前')])
for item in self:
# 中控反馈该位置有刀
if item:
# 系统该位置有刀
if location_id.product_sn_id:
# 中控反馈和系统中,该位置是同一把刀
if item.barcode_id == location_id.product_sn_id:
return True
# 中控反馈和系统中,该位置不是同一把刀
else:
# 原刀从线边出库
item.tool_in_out_stock_location_1(location_id, tool_room_id)
# 新刀入库到线边
item.create_stock_move(pre_manufacturing_id, location_id)
item.current_shelf_location_id = location_id.id
if self:
for item in self:
# 中控反馈该位置有刀
if item:
# 系统该位置有刀
if location_id.product_sn_id:
# 中控反馈和系统中,该位置是同一把刀
if item.barcode_id == location_id.product_sn_id:
return True
# 中控反馈和系统中,该位置不是同一把刀
else:
# 原刀从线边出库
item.tool_in_out_stock_location_1(location_id, tool_room_id)
# 新刀入库到线边
item.create_stock_move(pre_manufacturing_id, location_id)
item.current_shelf_location_id = location_id.id
# 中控反馈该位置没有刀
else:
# 系统该位置有刀
if location_id.product_sn_id:
item.tool_in_out_stock_location_1(location_id, tool_room_id)
# 中控反馈该位置没有刀
else:
# 系统该位置有刀
if location_id.product_sn_id:
self.tool_in_out_stock_location_1(location_id, tool_room_id)
def tool_in_out_stock_location_1(self, location_id, tool_room_id):
tool = self.env['sf.functional.cutting.tool.entity'].search(
@@ -238,10 +256,39 @@ class FunctionalCuttingToolEntity(models.Model):
functional_tool_model_ids.append(functional_tool_model.id)
return [(6, 0, functional_tool_model_ids)]
dismantle_num = fields.Integer('拆解单数量', compute='_compute_dismantle_num', store=True)
dismantle_ids = fields.One2many('sf.functional.tool.dismantle', 'functional_tool_id', '拆解单')
@api.depends('dismantle_ids')
def _compute_dismantle_num(self):
for item in self:
if item:
item.dismantle_num = len(item.dismantle_ids)
def open_functional_tool_dismantle_form(self):
self.ensure_one()
dismantle_ids = self.env['sf.functional.tool.dismantle'].sudo().search([('functional_tool_id', '=', self.id)])
action = {
'res_model': 'sf.functional.tool.dismantle',
'type': 'ir.actions.act_window',
'name': '拆解单',
}
if len(dismantle_ids) == 1:
action.update({
'view_mode': 'form',
'res_id': dismantle_ids[0].id,
})
else:
action.update({
'domain': [('id', 'in', dismantle_ids.ids)],
'view_mode': 'tree,form',
})
return action
def open_functional_tool_warning(self):
action = self.env.ref('sf_tool_management.action_sf_functional_tool_warning')
result = action.read()[0]
result['domain'] = [('functional_tool_name_id', '=', self.functional_tool_name_id.id)]
result['domain'] = [('functional_tool_id', '=', self.id)]
return result
def open_stock_move_line(self):
@@ -323,10 +370,10 @@ class FunctionalToolWarning(models.Model):
_name = 'sf.functional.tool.warning'
_description = '功能刀具预警'
code = fields.Char('编码', related='functional_tool_name_id.code')
rfid = fields.Char('Rfid', related='functional_tool_name_id.rfid')
tool_groups_id = fields.Many2one('sf.tool.groups', '刀具组', related='functional_tool_name_id.tool_groups_id')
name = fields.Char('名称', invisible=True, readonly=True, related='functional_tool_name_id.name')
code = fields.Char('编码', related='functional_tool_id.code')
rfid = fields.Char('Rfid', readonly=True)
tool_groups_id = fields.Many2one('sf.tool.groups', '刀具组', related='functional_tool_id.tool_groups_id')
name = fields.Char('名称', invisible=True, readonly=True, related='functional_tool_id.name')
# 机床信息
production_line_id = fields.Many2one('sf.production.line', string='生产线',
group_expand='_read_group_machine_table_name_ids')
@@ -337,52 +384,77 @@ class FunctionalToolWarning(models.Model):
cutter_spacing_code_id = fields.Many2one('maintenance.equipment.tool', string='刀位号',
domain="[('equipment_id', '=', maintenance_equipment_id)]")
# 功能刀具信息
functional_tool_name_id = fields.Many2one('sf.functional.tool.assembly', string='功能刀具名称')
barcode_id = fields.Many2one('stock.lot', string='功能刀具序列号', related='functional_tool_name_id.barcode_id')
mrs_cutting_tool_type_id = fields.Many2one('sf.functional.cutting.tool.model', string='功能刀具类型')
diameter = fields.Float(string='刀具直径(mm)')
knife_tip_r_angle = fields.Float(string='尖R角(mm)')
functional_tool_id = fields.Many2one('sf.functional.cutting.tool.entity', string='功能刀具', readonly=True)
barcode_id = fields.Many2one('stock.lot', string='序列号', related='functional_tool_id.barcode_id')
mrs_cutting_tool_type_id = fields.Many2one('sf.functional.cutting.tool.model', string='功能刀具类型',
related='functional_tool_id.sf_cutting_tool_type_id')
diameter = fields.Float(string='具直径(mm)', related='functional_tool_id.functional_tool_diameter')
knife_tip_r_angle = fields.Float(string='刀尖R角(mm)', related='functional_tool_id.knife_tip_r_angle')
# 其他信息
install_tool_time = fields.Datetime("刀具组装时间", related='functional_tool_name_id.tool_loading_time')
on_board_time = fields.Datetime('上机装刀时间')
max_lifetime_value = fields.Integer(string='最大寿命值(min)')
alarm_value = fields.Integer(string='报警值(min)')
used_value = fields.Integer(string='已使用值(min)')
max_lifetime_value = fields.Integer(string='最大寿命值(min)', related='functional_tool_id.max_lifetime_value')
alarm_value = fields.Integer(string='报警值(min)', related='functional_tool_id.alarm_value')
used_value = fields.Integer(string='已使用值(min)', related='functional_tool_id.used_value')
functional_tool_status = fields.Selection([('正常', '正常'), ('报警', '报警'), ('已拆除', '已拆除')], string='状态')
alarm_time = fields.Datetime('报警时间')
dispose_user = fields.Char('处理人')
dispose_time = fields.Char('处理时间')
dispose_func = fields.Char('处理方法/措施', readonly=False)
alarm_time = fields.Datetime('报警时间', default=lambda self: fields.Datetime.now(), readonly=True)
dispose_user = fields.Char('处理人', readonly=True)
dispose_time = fields.Char('处理时间', readonly=True)
dispose_func = fields.Char('处理方法/措施', readonly=True)
active = fields.Boolean(string='已归档', default=True)
functional_tool_name_id = fields.Many2one('sf.functional.tool.assembly', string='功能刀具名称')
@api.model
def _read_group_machine_table_name_ids(self, categories, domain, order):
machine_table_name_ids = categories._search([], order=order, access_rights_uid=SUPERUSER_ID)
return categories.browse(machine_table_name_ids)
def create_tool_warning_record(self, obj):
"""
机台换刀申请报警状态时,创建功能刀具预警记录
"""
if obj:
for tool in obj.get('tool_changing_apply_id'):
self.env['sf.functional.tool.warning'].create({
'production_line_id': tool.production_line_id.id,
'maintenance_equipment_id': tool.maintenance_equipment_id.id,
'machine_tool_code': tool.machine_tool_code,
'machine_table_type_id': tool.machine_table_type_id.id,
'cutter_spacing_code_id': tool.cutter_spacing_code_id.id,
'functional_tool_name_id': tool.functional_tool_name_id.id,
'barcode_id': tool.barcode_id.id,
'diameter': tool.diameter,
'knife_tip_r_angle': tool.knife_tip_r_angle,
'max_lifetime_value': tool.max_lifetime_value,
'alarm_value': tool.alarm_value,
'used_value': tool.used_value,
'functional_tool_status': tool.functional_tool_status,
'alarm_time': fields.Datetime.now(),
})
def action_open_dismantle(self):
self.ensure_one()
dismantle_ids = self.env['sf.functional.tool.dismantle'].sudo().search(
[('functional_tool_id', '=', self.functional_tool_id.id)])
action = {
'res_model': 'sf.functional.tool.dismantle',
'type': 'ir.actions.act_window',
'name': '拆解单'
}
if len(dismantle_ids) == 1:
action.update({
'view_mode': 'form',
'res_id': dismantle_ids.ids[0]
})
elif dismantle_ids:
action.update({
'view_mode': 'tree,form',
'domain': [('id', 'in', dismantle_ids.ids)],
})
else:
return False
return action
# def create_tool_warning_record(self, obj):
# """
# 机台换刀申请报警状态时,创建功能刀具预警记录
# """
# if obj:
# for tool in obj.get('tool_changing_apply_id'):
# self.env['sf.functional.tool.warning'].create({
# 'production_line_id': tool.production_line_id.id,
# 'maintenance_equipment_id': tool.maintenance_equipment_id.id,
# 'machine_tool_code': tool.machine_tool_code,
# 'machine_table_type_id': tool.machine_table_type_id.id,
# 'cutter_spacing_code_id': tool.cutter_spacing_code_id.id,
# 'functional_tool_name_id': tool.functional_tool_name_id.id,
# 'barcode_id': tool.barcode_id.id,
# 'diameter': tool.diameter,
# 'knife_tip_r_angle': tool.knife_tip_r_angle,
# 'max_lifetime_value': tool.max_lifetime_value,
# 'alarm_value': tool.alarm_value,
# 'used_value': tool.used_value,
# 'functional_tool_status': tool.functional_tool_status,
# 'alarm_time': fields.Datetime.now(),
# })
class StockMoveLine(models.Model):

View File

@@ -53,7 +53,7 @@ class SfMaintenanceEquipment(models.Model):
params = {"DeviceId": self.name}
r = requests.get(crea_url, params=params, headers=headers)
ret = r.json()
logging.info('register_equipment_tool:%s' % ret)
logging.info('机床刀库register_equipment_tool():%s' % ret)
datas = ret['Datas']
self.write_maintenance_equipment_tool(datas)
if ret['Succeed']:

View File

@@ -25,7 +25,10 @@
<field name="max_lifetime_value"/>
<field name="alarm_value"/>
<field name="used_value"/>
<field name="functional_tool_status"/>
<field name="functional_tool_status" widget='badge'
decoration-success="functional_tool_status == '正常'"
decoration-muted="functional_tool_status == '已拆除'"
decoration-danger="functional_tool_status == '报警'"/>
<field name="current_location" string="当前位置"/>
<field name="current_location_id" invisible="1"/>
@@ -48,6 +51,18 @@
<div class="oe_button_box" name="button_box">
<!-- <button name="button_safe_inventory_id" string="更新功能刀具关联的安全库存记录"-->
<!-- type="object" class="btn-primary"/>-->
<button class="oe_stat_button" groups="sf_base.group_sf_mrp_user"
name="open_functional_tool_dismantle_form"
icon="fa-credit-card"
type="object"
attrs="{'invisible': [('dismantle_num', '=', 0)]}">
<div name="dismantle_num" class="o_field_widget o_readonly_modifier o_field_statinfo">
<span class="o_stat_info o_stat_value">
<field name="dismantle_num"/>
</span>
<span class="o_stat_text">拆解单</span>
</div>
</button>
<button class="oe_stat_button" groups="sf_base.group_sf_mrp_user"
name="open_functional_tool_warning"
icon="fa-list-ul"
@@ -235,26 +250,29 @@
<field name="name">sf.functional.tool.warning.tree</field>
<field name="model">sf.functional.tool.warning</field>
<field name="arch" type="xml">
<tree string="功能刀具预警" create="0" edit="0" delete="0" editable="bottom">
<field name="production_line_id" optional="hide"/>
<field name="maintenance_equipment_id" optional="hide"/>
<field name="machine_tool_code"/>
<field name="cutter_spacing_code_id"/>
<field name="barcode_id" invisible="1"/>
<tree string="功能刀具预警" create="0" edit="0" delete="0" editable="bottom" default_order="id desc"
action="action_open_dismantle" type="object">
<field name="production_line_id" invisible="1"/>
<field name="maintenance_equipment_id" invisible="1"/>
<field name="machine_tool_code" invisible="1"/>
<field name="cutter_spacing_code_id" invisible="1"/>
<field name="on_board_time" invisible="1"/>
<field name="functional_tool_status" invisible="1"/>
<field name="functional_tool_name_id" invisible="1"/>
<field name="rfid"/>
<field name="functional_tool_name_id"/>
<field name="diameter"/>
<field name="knife_tip_r_angle"/>
<field name="functional_tool_id"/>
<field name="barcode_id" optional="hide"/>
<field name="diameter" optional="hide"/>
<field name="knife_tip_r_angle" optional="hide"/>
<field name="install_tool_time" optional="hide"/>
<field name="on_board_time" optional="hide"/>
<field name="max_lifetime_value"/>
<field name="alarm_value"/>
<field name="used_value"/>
<field name="functional_tool_status"/>
<field name="alarm_time"/>
<field name="dispose_user"/>
<field name="dispose_time"/>
<field name="dispose_func"/>
<field name="dispose_func" optional="hide"/>
<!-- <button name="enroll_functional_tool_warning" string="刀具预警注册" type="object"-->
<!-- class="btn-primary"/>-->
</tree>
@@ -266,31 +284,19 @@
<field name="model">sf.functional.tool.warning</field>
<field name="arch" type="xml">
<search string="功能刀具预警">
<field name="machine_tool_code"/>
<field name="cutter_spacing_code_id"/>
<field name="barcode_id"/>
<field name="rfid"/>
<field name="functional_tool_name_id"/>
<field name="diameter"/>
<field name="knife_tip_r_angle"/>
<field name="install_tool_time" optional="hide"/>
<field name="on_board_time" optional="hide"/>
<field name="max_lifetime_value"/>
<field name="alarm_value"/>
<field name="used_value"/>
<field name="functional_tool_status"/>
<field name="alarm_time"/>
<field name="dispose_user"/>
<field name="dispose_time"/>
<field name="dispose_func"/>
<field name="production_line_id" invisible="True"/>
<filter string="已归档" name="inactive" domain="[('active', '=', False)]"/>
<searchpanel>
<field name="production_line_id" icon="fa-building" enable_counters="1"/>
<field name="maintenance_equipment_id" icon="fa-building" enable_counters="1"/>
<field name="cutter_spacing_code_id" icon="fa-building" enable_counters="1"/>
<field name="functional_tool_status" icon="fa-building" enable_counters="1"/>
</searchpanel>
<group expand="0">
<filter string="报警时间" name="alarm_time" domain="[]"
context="{'group_by': 'alarm_time'}"/>
</group>
</search>
</field>
</record>

View File

@@ -704,10 +704,10 @@
<field name="model">sf.functional.tool.assembly</field>
<field name="arch" type="xml">
<search>
<field name="functional_tool_name"/>
<field name="assembly_order_code"/>
<field name="code" string="功能刀具编码"/>
<field name="barcode_id"/>
<field name="functional_tool_name"/>
<field name="functional_tool_type_id"/>
<field name="tool_groups_id"/>
<field name="loading_task_source" string="任务来源"/>
@@ -800,10 +800,16 @@
</h1>
</div>
<field name="_barcode_scanned" widget="barcode_handler"/>
<script>
setTimeout(function(){
$('#functional_tool_id').blur()
}, 100)
</script>
<group>
<group>
<field name="functional_tool_id" placeholder="请选择将要拆解的功能刀具"
options="{'no_create': True}" attrs="{'readonly': [('state', '=', '已拆解')]}"/>
options="{'no_create': True}"
attrs="{'readonly': ['|',('state', '=', '已拆解'),('id', '!=', False)]}"/>
<field name="rfid" attrs="{'invisible': [('rfid', '=', '')]}"/>
<field name="rfid_dismantle" attrs="{'invisible': [('rfid_dismantle', '=', False)]}"/>
<field name="tool_type_id"/>
@@ -833,10 +839,26 @@
<notebook>
<page string="物料组装信息">
<group>
<group string="刀柄" attrs="{'invisible': [('handle_product_id', '=', False)]}">
<group string="刀柄" attrs="{'invisible': [('handle_product_id', '=', False)]}"
col="1">
<group attrs="{'invisible': [('dismantle_cause', 'not in', ['寿命到期报废','崩刀报废'])]}">
<group col="3">
<group>
<field name="scrap_boolean" string="是否报废"/>
</group>
<group></group>
<group>
<button string="报废" name="tool_scrap" type="object"
class="btn-primary" confirm="是否确认报废刀柄"
attrs="{'invisible': [('scrap_boolean', '=', True)]}"/>
<button string="取消" name="tool_no_scrap" type="object"
class="btn-primary" confirm="是否取消报废刀柄"
attrs="{'invisible': [('scrap_boolean', '=', False)]}"/>
<group></group>
</group>
</group>
</group>
<group>
<field name="scrap_boolean" string="是否报废"
attrs="{'invisible': [('dismantle_cause', 'not in', ['寿命到期报废','崩刀报废'])], 'readonly': [('state', '=', '已拆解')]}"/>
<field name="handle_rfid" string="Rfid"/>
<field name="handle_lot_id" string="序列号"/>
<field name="handle_product_id" string="名称"/>
@@ -911,7 +933,7 @@
</group>
</group>
</page>
<page string="报废"
<page string="报废"
attrs="{'invisible':[('dismantle_cause', 'not in', ['寿命到期报废','崩刀报废'])]}">
<field name="scrap_ids">
<tree>
@@ -952,8 +974,9 @@
<field name="model">sf.functional.tool.dismantle</field>
<field name="arch" type="xml">
<search>
<field name="rfid"/>
<field name="functional_tool_id"/>
<field name="code" string="拆解单编码"/>
<field name="code" string="拆解单"/>
<filter name="no_dismantle_state" string="未拆解" domain="[('state','!=','已拆解')]"/>
<filter name="dismantle_state" string="已拆解" domain="[('state','=','已拆解')]"/>
<separator/>

View File

@@ -842,6 +842,8 @@ class StockPicking(models.Model):
stock_move_id = self.env['stock.move']
datas = {'data': [], 'picking_id': picking_id}
if obj.handle_code_id:
# 修改刀柄序列号状态为【在用】
obj.handle_code_id.sudo().write({'tool_material_status': '在用'})
datas['data'].append(
{'current_location_id': self.env['sf.shelf.location'], 'lot_id': obj.handle_code_id})
if obj.integral_product_id:

View File

@@ -201,6 +201,11 @@
</group>
</group>
</group>
<script>
setTimeout(function(){
$('#handle_code_id').blur()
}, 100)
</script>
<group string="组装物料信息" col="1">
<field name="_barcode_scanned" widget="barcode_handler"/>
<group col="1">
@@ -368,7 +373,7 @@
<group>
<field name="obtain_measurement_status" invisible="1"/>
<button name="get_tool_preset_parameter" string="获取测量值" type="object"
attrs="{'invisible': [('enable_tool_presetter', '=', False)]}"
attrs="{'invisible': [('enable_tool_presetter', '=', False)]}"
class="btn-primary"/>
</group>
</group>

View File

@@ -507,13 +507,13 @@ class ShelfLocation(models.Model):
print('eeeeeee空闲', e)
# 调取获取货位信息接口
def get_sf_shelf_location_info(self):
def get_sf_shelf_location_info(self, device_id='Cabinet-AL'):
config = self.env['res.config.settings'].get_values()
headers = {'Authorization': config['center_control_Authorization']}
crea_url = config['center_control_url'] + "/AutoDeviceApi/GetLocationInfos"
params = {'DeviceId': 'Cabinet-AL'}
params = {'DeviceId': device_id}
r = requests.get(crea_url, params=params, headers=headers)
ret = r.json()

View File

@@ -13,88 +13,92 @@ class MrsShelfLocationDataSync(models.Model):
_name = 'sf.shelf.location.datasync'
_description = '同步库存信息'
def get_total_data(self):
# 建立对应关系的函数
def align_data(my_data, their_data):
paired_data = list(zip(my_data, their_data))
return paired_data
logging.info('============================get_total_data()======================')
shelf_1_obj = self.env['sf.shelf'].search([('name', '=', '一号产线-一号线边刀架')], limit=1)
tool_location_objs_1 = self.env['sf.shelf.location'].search([('shelf_id', '=', shelf_1_obj.id)], order='id')
location_codes_1 = [location.barcode for location in tool_location_objs_1]
print(location_codes_1)
# 对方的数据列表
their_data_1 = [f"ToolCab1-{i:02}" for i in range(1, 73)]
# 执行对齐
aligned_data_1 = align_data(location_codes_1, their_data_1)
# 2
shelf_2_obj = self.env['sf.shelf'].search([('name', '=', '一号产线-二号线边刀架')], limit=1)
tool_location_objs_2 = self.env['sf.shelf.location'].search([('shelf_id', '=', shelf_2_obj.id)], order='id')
location_codes_2 = [location.barcode for location in tool_location_objs_2]
print(location_codes_2)
# 对方的数据列表
their_data_2 = [f"ToolCab2-{i:02}" for i in range(1, 73)]
# 执行对齐
aligned_data_2 = align_data(location_codes_2, their_data_2)
# 4
shelf_4_obj = self.env['sf.shelf'].search([('name', '=', '一号产线-一号线边料架')], limit=1)
tool_location_objs_4 = self.env['sf.shelf.location'].search([('shelf_id', '=', shelf_4_obj.id)], order='id')
location_codes_4 = [location.barcode for location in tool_location_objs_4]
print(location_codes_4)
# 对方的数据列表
their_data_4 = [f"PartCab4-{i:02}" for i in range(1, 17)]
# 执行对齐
aligned_data_4 = align_data(location_codes_4, their_data_4)
# 3
shelf_3_obj = self.env['sf.shelf'].search([('name', '=', '一号产线-二号线边料架')], limit=1)
tool_location_objs_3 = self.env['sf.shelf.location'].search([('shelf_id', '=', shelf_3_obj.id)], order='id')
location_codes_3 = [location.barcode for location in tool_location_objs_3]
print(location_codes_3)
# 对方的数据列表
their_data_3 = [f"PartCab3-{i:02}" for i in range(1, 13)]
# 执行对齐
aligned_data_3 = align_data(location_codes_3, their_data_3)
# 5
shelf_5_obj = self.env['sf.shelf'].search([('name', '=', '一号产线-三号线边料架')], limit=1)
tool_location_objs_5 = self.env['sf.shelf.location'].search([('shelf_id', '=', shelf_5_obj.id)], order='id')
location_codes_5 = [location.barcode for location in tool_location_objs_5]
print(location_codes_5)
# 对方的数据列表
their_data_5 = [f"PartCab5-{i:02}" for i in range(1, 13)]
# 执行对齐
aligned_data_5 = align_data(location_codes_5, their_data_5)
total_data = aligned_data_1 + aligned_data_2 + aligned_data_3 + aligned_data_4 + aligned_data_5
print(total_data)
logging.info(f"total_data: {total_data}")
return total_data
def find_our_code(self, total_data, their_code):
for code_pair in total_data:
if code_pair[1] == their_code:
return code_pair[0]
return None # 如果没有找到对应的值返回None或适当的默认值
def _cron_shelf_location_datasync(self):
try:
# 建立对应关系的函数
def align_data(my_data, their_data):
paired_data = list(zip(my_data, their_data))
return paired_data
shelf_1_obj = self.env['sf.shelf'].search([('name', '=', '一号产线-一号线边刀架')], limit=1)
tool_location_objs_1 = self.env['sf.shelf.location'].search([('shelf_id', '=', shelf_1_obj.id)], order='id')
location_codes_1 = [location.barcode for location in tool_location_objs_1]
print(location_codes_1)
# 对方的数据列表
their_data_1 = [f"ToolCab1-{i:02}" for i in range(1, 73)]
# 执行对齐
aligned_data_1 = align_data(location_codes_1, their_data_1)
# 2
shelf_2_obj = self.env['sf.shelf'].search([('name', '=', '一号产线-二号线边刀架')], limit=1)
tool_location_objs_2 = self.env['sf.shelf.location'].search([('shelf_id', '=', shelf_2_obj.id)], order='id')
location_codes_2 = [location.barcode for location in tool_location_objs_2]
print(location_codes_2)
# 对方的数据列表
their_data_2 = [f"ToolCab2-{i:02}" for i in range(1, 73)]
# 执行对齐
aligned_data_2 = align_data(location_codes_2, their_data_2)
# 4
shelf_4_obj = self.env['sf.shelf'].search([('name', '=', '一号产线-一号线边料架')], limit=1)
tool_location_objs_4 = self.env['sf.shelf.location'].search([('shelf_id', '=', shelf_4_obj.id)], order='id')
location_codes_4 = [location.barcode for location in tool_location_objs_4]
print(location_codes_4)
# 对方的数据列表
their_data_4 = [f"PartCab4-{i:02}" for i in range(1, 17)]
# 执行对齐
aligned_data_4 = align_data(location_codes_4, their_data_4)
# 3
shelf_3_obj = self.env['sf.shelf'].search([('name', '=', '一号产线-二号线边料架')], limit=1)
tool_location_objs_3 = self.env['sf.shelf.location'].search([('shelf_id', '=', shelf_3_obj.id)], order='id')
location_codes_3 = [location.barcode for location in tool_location_objs_3]
print(location_codes_3)
# 对方的数据列表
their_data_3 = [f"PartCab3-{i:02}" for i in range(1, 13)]
# 执行对齐
aligned_data_3 = align_data(location_codes_3, their_data_3)
# 5
shelf_5_obj = self.env['sf.shelf'].search([('name', '=', '一号产线-三号线边料架')], limit=1)
tool_location_objs_5 = self.env['sf.shelf.location'].search([('shelf_id', '=', shelf_5_obj.id)], order='id')
location_codes_5 = [location.barcode for location in tool_location_objs_5]
print(location_codes_5)
# 对方的数据列表
their_data_5 = [f"PartCab5-{i:02}" for i in range(1, 13)]
# 执行对齐
aligned_data_5 = align_data(location_codes_5, their_data_5)
total_data = aligned_data_1 + aligned_data_2 + aligned_data_3 + aligned_data_4 + aligned_data_5
print(total_data)
logging.info(f"total_data: {total_data}")
def find_their_code(my_code, aligned_data):
for code_pair in aligned_data:
if code_pair[0] == my_code:
return code_pair[1]
return None # 如果没有找到对应的值返回None或适当的默认值
def find_our_code(their_code, aligned_data):
for code_pair in aligned_data:
if code_pair[1] == their_code:
return code_pair[0]
return None # 如果没有找到对应的值返回None或适当的默认值
# 定时更新所有设备机床刀库信息
equipment_ids = self.env['maintenance.equipment'].search(
[('equipment_type', '=', '机床'), ('function_type', '!=', False)])
@@ -103,9 +107,10 @@ class MrsShelfLocationDataSync(models.Model):
equipment_id.register_equipment_tool()
shelfinfo = self.env['sf.shelf.location'].get_sf_shelf_location_info()
total_data = self.get_total_data()
print('shelfinfo:', shelfinfo)
for item in shelfinfo:
shelf_barcode = find_our_code(item['Postion'], total_data)
shelf_barcode = self.find_our_code(total_data, item['Postion'])
location_id = self.env['sf.shelf.location'].search([('barcode', '=', shelf_barcode)], limit=1)
if location_id:
# 如果是线边刀库信息,则对功能刀具移动生成记录
@@ -115,6 +120,13 @@ class MrsShelfLocationDataSync(models.Model):
tool.tool_in_out_stock_location(location_id)
if tool:
location_id.product_sn_id = tool.barcode_id.id
# 修改功能刀具状态
tool_state = {'Nomal': '正常', 'Warning': '报警'}
if tool_state.get(item.get('State')):
if tool_state.get(item.get('State')) != tool.functional_tool_status:
tool.write({
'functional_tool_status': tool_state.get(item['State'])
})
else:
location_id.product_sn_id = False
else: