Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/优化制造功能

This commit is contained in:
mgw
2024-08-20 11:27:34 +08:00
27 changed files with 689 additions and 290 deletions

View File

@@ -43,6 +43,7 @@
'sf_manufacturing/static/src/js/kanban_change.js',
'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

@@ -388,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']
@@ -398,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)
@@ -458,7 +503,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 != '':
rfid_codes.append(rfid_code)
domain = [
('rfid_code', '=', rfid_code),
@@ -531,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')

View File

@@ -6,6 +6,9 @@
<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>

View File

@@ -50,6 +50,21 @@ class AgvScheduling(models.Model):
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:
@@ -73,6 +88,7 @@ class AgvScheduling(models.Model):
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)

View File

@@ -151,6 +151,11 @@ access_sf_production_wizard_group_sf_order_user,sf_production_wizard_group_sf_or
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
151
152
153
154
155
156
157
158
159
160
161

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

@@ -37,10 +37,34 @@
</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
@@ -49,6 +73,7 @@
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

@@ -844,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

@@ -18,8 +18,8 @@
<field name="workcenter_id" options="{'no_create': True}"/>
</group>
<footer>
<button string="确认配送" name="confirm" type="object" class="oe_highlight" attrs="{'invisible': [('confirm_button', '!=', '确认配送')]}"/>
<button string="确认解除" name="confirm" type="object" class="oe_highlight" attrs="{'invisible': [('confirm_button', '!=', '确认解除')]}"/>
<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(){

View File

@@ -1,9 +1,16 @@
# -*- 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):
@@ -34,18 +41,18 @@ class WorkpieceDeliveryWizard(models.TransientModel):
self.workcenter_id = workcenter_ids[0]
return {
'domain':
{
'feeder_station_start_id': [('id', 'in', start_site_ids)],
'workcenter_id': [('id', 'in', workcenter_ids)],
}
{
'feeder_station_start_id': [('id', 'in', start_site_ids)],
'workcenter_id': [('id', 'in', workcenter_ids)],
}
}
else:
return {
'domain':
{
'feeder_station_start_id': [],
'workcenter_id': [],
}
{
'feeder_station_start_id': [],
'workcenter_id': [],
}
}
def _get_agv_route_type_selection(self):
@@ -53,6 +60,31 @@ class WorkpieceDeliveryWizard(models.TransientModel):
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):
try:
# if self.workorder_id:
@@ -89,18 +121,7 @@ class WorkpieceDeliveryWizard(models.TransientModel):
item.button_start()
item.button_finish()
# 显示通知
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'},
}
}
return scheduling.read()[0]
except Exception as e:
logging.info('%s任务下发失败:%s' % (self.delivery_type, e))
raise UserError('%s任务下发失败:%s' % (self.delivery_type, e))
@@ -167,7 +188,8 @@ class WorkpieceDeliveryWizard(models.TransientModel):
[('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:
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)
# 将对象添加到对应的同模型且是多对多类型里