增加agv调度系统

This commit is contained in:
胡尧
2024-08-08 17:47:03 +08:00
parent 5d8f0f83b2
commit a79500d0ad
9 changed files with 168 additions and 126 deletions

View File

@@ -15,6 +15,7 @@
'data/stock_data.xml',
'data/empty_racks_data.xml',
'data/panel_data.xml',
'data/agv_dispatch_data.xml',
'security/group_security.xml',
'security/ir.model.access.csv',
'wizard/workpiece_delivery_views.xml',

View File

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

View File

@@ -1,20 +1,24 @@
from odoo import models, fields, api
from odoo.exceptions import ValidationError
from odoo import models, fields, api, _
from odoo.exceptions import UserError
import logging
_logger = logging.getLogger(__name__)
class AgvDispatch(models.Model):
_name = 'sf.agv.dispatch'
_description = 'agv调度'
name = fields.Char('任务单号')
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='任务类型')
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', '起点接驳站')
end_site_id = fields.Many2one('sf.agv.site', '终点接驳站')
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='终点接驳站状态')
@@ -22,7 +26,8 @@ class AgvDispatch(models.Model):
('待下发', '待下发'),
('配送中', '配送中'),
('已配送', '已配送'),
('已取消', '已取消')], string='状态', default='待下发')
('已取消', '已取消')], string='状态', default='待下发', tracking=True)
productions = fields.Char('制造订单', compute='_compute_productions')
workpiece_delivery_ids = fields.One2many('sf.workpiece.delivery', 'agv_dispatch_id', string='工件配送单')
delivery_workpieces = fields.Char('配送工件', compute='_compute_delivery_workpieces')
@@ -44,7 +49,28 @@ class AgvDispatch(models.Model):
else:
rec.task_duration = ''
def add_queue(self, vals):
if not vals.get('agv_route_type') or vals.get('agv_route_type') not in self._get_agv_route_type_selection():
raise ValidationError('无效的路线类型!')
self.env['sf.agv.dispatch'].sudo().create(vals)
@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.dispatch') or _('New')
return super().create(vals_list)
def add_queue(self, agv_start_site_id, agv_route_type, production_ids, delivery_ids):
"""
根据参数增加AGV调度任务
"""
vals = {
'start_site_id': agv_start_site_id,
'agv_route_type': agv_route_type,
'productions': ''.join(production_ids.mapped('name')),
'workpiece_delivery_ids': delivery_ids or [],
'task_create_time': fields.Datetime.now()
}
try:
dispatch = self.env['sf.agv.dispatch'].sudo().create(vals)
except Exception as e:
_logger.error('添加AGV调度任务失败: %s', e)
raise UserError(e)
return dispatch

View File

@@ -17,7 +17,7 @@ class AgvSetting(models.Model):
('空闲', '空闲')], string='状态')
divide_the_work = fields.Char('主要分工')
active = fields.Boolean('有效', default=True)
region = fields.Many2one(string='所属区域', comodel_name='mrp.workcenter', tracking=True,
workcenter_id = fields.Many2one(string='所属区域', comodel_name='mrp.workcenter', tracking=True,
domain=[('is_agv_dispatch', '=', True)])
def update_site_state(self):
@@ -73,14 +73,16 @@ class AgvTaskRoute(models.Model):
if self.end_site_id == self.start_site_id:
raise UserError("您选择的终点接驳站与起点接驳站重复,请重新选择")
region = fields.Many2one(string='所属区域', comodel_name='mrp.workcenter', domain=[('is_agv_dispatch', '=', True)],
workcenter_id = fields.Many2one(string='所属区域', comodel_name='mrp.workcenter', domain=[('is_agv_dispatch', '=', True)],
compute="_compute_region")
@api.depends('end_site_id')
def _compute_region(self):
for record in self:
if record.end_site_id:
record.region = record.end_site_id.region
record.workcenter_id = record.end_site_id.workcenter_id
else:
record.workcenter_id = None
class Center_controlInterfaceLog(models.Model):

View File

@@ -634,25 +634,33 @@ 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': '上产线',
# '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):
@@ -1182,6 +1190,8 @@ class ResMrpWorkOrder(models.Model):
raw_move.write({'state': 'done'})
record.production_id.button_mark_done1()
# record.production_id.state = 'done'
# 生成工件配送单
record.workpiece_delivery_ids = record._json_workpiece_delivery_list()
# 将FTP的检测报告文件下载到临时目录
def download_reportfile_tmp(self, workorder, reportpath):
@@ -1485,7 +1495,7 @@ class SfWorkOrderBarcodes(models.Model):
class WorkPieceDelivery(models.Model):
_name = "sf.workpiece.delivery"
_inherit = ['mail.thread', 'mail.activity.mixin']
_inherit = ['mail.thread', 'mail.activity.mixin', "barcodes.barcode_events_mixin"]
_description = '工件配送'
name = fields.Char('单据编码')
@@ -1501,11 +1511,14 @@ 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='状态',
[('待下发', '待下发'), ('已下发', '已下发'), ('已配送', '已配送'), ('已取消', '已取消')], string='状态',
default='待下发',
tracking=True)
is_cnc_program_down = fields.Boolean('程序是否下发', default=False, tracking=True)
@@ -1564,82 +1577,41 @@ class WorkPieceDelivery(models.Model):
production_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
production_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)
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(
"您所选择制造订单的【任务路线】的【终点接驳站】已占用,请在该接驳站空闲时或选择其他路线进行配送")
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_type': production_type,
}}
# 验证agv站点是否可用
def _check_avgsite_state(self):
@@ -1747,6 +1719,9 @@ class WorkPieceDelivery(models.Model):
# agv调度单
agv_dispatch_id = fields.Many2one('sf.agv.dispatch', 'agv调度单')
def on_barcode_scanned(self, barcode):
logging.info('Rfid:%s' % barcode)
class CMMprogram(models.Model):
_name = 'sf.cmm.program'

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="region" required="1" options="{'no_create': True}"/>
<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>
@@ -42,7 +42,7 @@
<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="region"/>
<field name="workcenter_id"/>
</tree>
</field>
</record>

View File

@@ -643,15 +643,15 @@
<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>

View File

@@ -9,23 +9,17 @@
<field name="delivery_ids" invisible="True"/>
<field name="workorder_id" invisible="True"/>
<field name="type" invisible="True"/>
<group attrs="{'invisible': [('type', 'in', ['运送空料架'])]}">
<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="type" readonly="1"/>
<field name="feeder_station_start_id" options="{'no_create': True}" require="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="取消" class="btn btn-secondary" special="cancel"/>

View File

@@ -18,15 +18,40 @@ class WorkpieceDeliveryWizard(models.TransientModel):
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)
@api.onchange('type')
def _onchange_type(self):
routes = self.env['sf.agv.task.route'].search([('route_type', '=', self.type)])
if self.type:
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']
type = fields.Selection(selection=_get_agv_route_type_selection, string='类型')
def confirm(self):
if self.type != '运送空料架':
if not self.route_id:
raise UserError('请选择路线')
if self.workorder_id:
self.workorder_id.workpiece_delivery_ids[0]._delivery_avg()
self.workorder_id.workpiece_delivery_ids[0].agv_dispatch_id()
else:
is_not_production_line = 0
same_production_line_id = None
@@ -40,7 +65,14 @@ class WorkpieceDeliveryWizard(models.TransientModel):
if is_not_production_line >= 1:
raise UserError('制造订单号为%s的目的生产线不一致' % notsame_production_line_str)
else:
self.delivery_ids._delivery_avg()
# self.delivery_ids._delivery_avg()
agv_dispatch_id = self.env['sf.agv.dispatch'].add_queue(
agv_start_site_id=self.feeder_station_start_id.id,
agv_route_type=self.type,
production_ids=self.production_ids,
delivery_ids=self.delivery_ids.ids
)
self.delivery_ids.agv_dispatch_id = agv_dispatch_id
def recognize_production(self):
# production_ids = []