Merge branch 'develop' into feature/update_process_start_time
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
],
|
||||
'web.assets_backend': [
|
||||
'sf_base/static/src/scss/*.scss',
|
||||
'sf_base/static/src/js/*.js',
|
||||
],
|
||||
|
||||
},
|
||||
|
||||
@@ -5,3 +5,4 @@ from . import fixture
|
||||
from . import functional_fixture
|
||||
from . import tool_other_features
|
||||
from . import basic_parameters_fixture
|
||||
from . import ir_sequence
|
||||
|
||||
69
sf_base/models/ir_sequence.py
Normal file
69
sf_base/models/ir_sequence.py
Normal file
@@ -0,0 +1,69 @@
|
||||
import calendar
|
||||
from datetime import timedelta
|
||||
|
||||
from odoo import models, fields
|
||||
|
||||
|
||||
class IrSequence(models.Model):
|
||||
_inherit = 'ir.sequence'
|
||||
|
||||
date_range_period = fields.Selection(
|
||||
[('day', '每日'), ('month', '每月'), ('year', '每年')],
|
||||
string='日期期间',
|
||||
)
|
||||
|
||||
def _next(self, sequence_date=None):
|
||||
""" Returns the next number in the preferred sequence in all the ones given in self."""
|
||||
if not self.use_date_range:
|
||||
return self._next_do()
|
||||
# date mode
|
||||
dt = sequence_date or self._context.get('ir_sequence_date', fields.Date.today())
|
||||
seq_date = self.env['ir.sequence.date_range'].search(
|
||||
[('sequence_id', '=', self.id), ('date_from', '<=', dt), ('date_to', '>=', dt)], limit=1)
|
||||
if not seq_date:
|
||||
if self.date_range_period:
|
||||
seq_date = self._create_date_range_seq_by_period(dt, self.date_range_period)
|
||||
else:
|
||||
seq_date = self._create_date_range_seq(dt)
|
||||
return seq_date.with_context(ir_sequence_date_range=seq_date.date_from)._next()
|
||||
|
||||
def _create_date_range_seq_by_period(self, date, period):
|
||||
if period == 'year':
|
||||
year = fields.Date.from_string(date).strftime('%Y')
|
||||
date_from = '{}-01-01'.format(year)
|
||||
date_to = '{}-12-31'.format(year)
|
||||
if period == 'month':
|
||||
# 计算当前月份的第一天和最后一天
|
||||
year = fields.Date.from_string(date).strftime('%Y')
|
||||
month = fields.Date.from_string(date).strftime('%m')
|
||||
date_from = fields.Date.from_string(date).strftime('%Y-%m-01')
|
||||
date_to = '{}-{}-{}'.format(year, month, calendar.monthrange(int(year), int(month))[1])
|
||||
if period == 'day':
|
||||
date_from = date
|
||||
date_to = date
|
||||
date_range = self.env['ir.sequence.date_range'].search(
|
||||
[
|
||||
('sequence_id', '=', self.id),
|
||||
('date_to', '>=', date_from),
|
||||
('date_to', '<=', date),
|
||||
('date_range_period', '=', period)
|
||||
],
|
||||
order='date_to desc', limit=1)
|
||||
if date_range:
|
||||
date_from = date_range.date_to + timedelta(days=1)
|
||||
seq_date_range = self.env['ir.sequence.date_range'].sudo().create({
|
||||
'date_from': date_from,
|
||||
'date_to': date_to,
|
||||
'sequence_id': self.id,
|
||||
'date_range_period': period,
|
||||
})
|
||||
return seq_date_range
|
||||
|
||||
|
||||
class IrSequenceDateRange(models.Model):
|
||||
_inherit = 'ir.sequence.date_range'
|
||||
|
||||
date_range_period = fields.Selection(
|
||||
[('day', '每日'), ('month', '每月'), ('year', '每年')],
|
||||
string='日期期间',
|
||||
)
|
||||
23
sf_base/static/src/js/remove_focus.js
Normal file
23
sf_base/static/src/js/remove_focus.js
Normal file
@@ -0,0 +1,23 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registry } from '@web/core/registry';
|
||||
|
||||
import { formView } from '@web/views/form/form_view';
|
||||
import { FormController } from '@web/views/form/form_controller';
|
||||
|
||||
import { onRendered, onMounted } from "@odoo/owl";
|
||||
|
||||
export class RemoveFocusController extends FormController {
|
||||
setup() {
|
||||
super.setup();
|
||||
|
||||
onMounted(() => {
|
||||
this.__owl__.bdom.el.querySelectorAll(':focus').forEach(element => element.blur());
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
registry.category('views').add('remove_focus_view', {
|
||||
...formView,
|
||||
Controller: RemoveFocusController,
|
||||
});
|
||||
@@ -36,7 +36,7 @@ class Http(models.AbstractModel):
|
||||
post_time = int(datas['HTTP_TIMESTAMP'])
|
||||
datetime_post = datetime.fromtimestamp(post_time)
|
||||
datetime_now = datetime.now().replace(microsecond=0)
|
||||
datetime_del = datetime_now + timedelta(seconds=5)
|
||||
datetime_del = datetime_now + timedelta(seconds=30)
|
||||
if datetime_post > datetime_del:
|
||||
raise AuthenticationError('请求已过期')
|
||||
check_str = '%s%s%s' % (datas['HTTP_TOKEN'], post_time, factory_secret.sf_secret_key)
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
'views/machine_info_present.xml',
|
||||
'views/delivery_record.xml',
|
||||
'views/res_config_settings_views.xml',
|
||||
'views/maintenance_views.xml',
|
||||
|
||||
],
|
||||
'assets': {
|
||||
|
||||
@@ -1,10 +1,55 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import ast
|
||||
import json
|
||||
import base64
|
||||
import logging
|
||||
import psycopg2
|
||||
from datetime import datetime, timedelta
|
||||
from odoo import http
|
||||
from odoo.http import request
|
||||
|
||||
# 数据库连接配置
|
||||
db_config = {
|
||||
"database": "timeseries_db",
|
||||
"user": "postgres",
|
||||
"password": "postgres",
|
||||
"port": "5432",
|
||||
"host": "172.16.10.98"
|
||||
}
|
||||
|
||||
|
||||
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):
|
||||
|
||||
@@ -18,6 +63,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 +83,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,11 +152,614 @@ 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,
|
||||
# 停机时间:关机时间 - 运行时间
|
||||
# 停机时长:关机时间 - 初次上线时间
|
||||
'img': f'data:image/png;base64,{machine_data.machine_tool_picture.decode("utf-8")}',
|
||||
})
|
||||
|
||||
return json.JSONEncoder().encode(res)
|
||||
return json.dumps(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)
|
||||
# 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)
|
||||
|
||||
# 返回产线列表
|
||||
|
||||
@http.route('/api/LineList', type='http', auth='public', methods=['GET', 'POST'], csrf=False,
|
||||
|
||||
cors="*")
|
||||
def LineList(self, **kw):
|
||||
"""
|
||||
获取产线列表
|
||||
:param kw:
|
||||
:return:
|
||||
"""
|
||||
|
||||
try:
|
||||
res = {'Succeed': True}
|
||||
line_list_obj = request.env['sf.production.line'].sudo().search([('name', 'ilike', 'CNC')])
|
||||
line_list = list(map(lambda x: x.name, line_list_obj))
|
||||
print('line_list: %s' % line_list)
|
||||
res['LineList'] = line_list
|
||||
|
||||
except Exception as e:
|
||||
res = {'Succeed': False, 'ErrorCode': 202, 'Error': e}
|
||||
logging.info('LineList error:%s' % e)
|
||||
|
||||
return json.JSONEncoder().encode(res)
|
||||
|
||||
# 获取产线产量相关
|
||||
|
||||
@http.route('/api/LineProduct', type='http', auth='public', methods=['GET', 'POST'], csrf=False,
|
||||
|
||||
cors="*")
|
||||
def LineProduct(self, **kw):
|
||||
"""
|
||||
获取产线产量相关
|
||||
:param kw:
|
||||
:return:
|
||||
"""
|
||||
res = {'status': 1, 'message': '成功', 'data': {}}
|
||||
logging.info('前端请求产线产量数据的参数为:%s' % kw)
|
||||
|
||||
try:
|
||||
plan_obj = request.env['sf.production.plan'].sudo()
|
||||
line_list = ast.literal_eval(kw['line_list'])
|
||||
print('line_list: %s' % line_list)
|
||||
for line in line_list:
|
||||
plan_data = plan_obj.search([('production_line_id.name', '=', line)])
|
||||
# 工单总量
|
||||
plan_data_total_counts = plan_obj.search_count([('production_line_id.name', '=', line)])
|
||||
# 工单完成量
|
||||
plan_data_finish_counts = plan_obj.search_count(
|
||||
[('production_line_id.name', '=', line), ('state', 'not in', ['draft'])])
|
||||
# 工单计划量
|
||||
plan_data_plan_counts = plan_obj.search_count(
|
||||
[('production_line_id.name', '=', line), ('state', 'not in', ['finished'])])
|
||||
# 工单不良累计
|
||||
plan_data_fault_counts = plan_obj.search_count(
|
||||
[('production_line_id.name', '=', line), ('production_id.state', 'in', ['scrap', 'cancel'])])
|
||||
|
||||
# 工单返工数量
|
||||
|
||||
plan_data_rework_counts = plan_obj.search_count(
|
||||
[('production_line_id.name', '=', line), ('production_id.state', 'in', ['rework'])])
|
||||
|
||||
# 工单完成率
|
||||
finishe_rate = round(
|
||||
(plan_data_finish_counts / plan_data_total_counts if plan_data_total_counts > 0 else 0), 3)
|
||||
|
||||
# 工单进度偏差
|
||||
plan_data_progress_deviation = plan_data_finish_counts - plan_data_plan_counts
|
||||
|
||||
if plan_data:
|
||||
data = {
|
||||
'plan_data_total_counts': plan_data_total_counts,
|
||||
'plan_data_finish_counts': plan_data_finish_counts,
|
||||
'plan_data_plan_counts': plan_data_plan_counts,
|
||||
'plan_data_fault_counts': plan_data_fault_counts,
|
||||
'finishe_rate': finishe_rate,
|
||||
'plan_data_progress_deviation': plan_data_progress_deviation,
|
||||
'plan_data_rework_counts': plan_data_rework_counts
|
||||
}
|
||||
res['data'][line] = data
|
||||
|
||||
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)
|
||||
|
||||
# 日完成量统计
|
||||
class DailyFinishCount(http.Controller):
|
||||
@http.route('/api/DailyFinishCount', type='http', auth='public', methods=['GET', 'POST'], csrf=False, cors="*")
|
||||
def DailyFinishCount(self, **kw):
|
||||
"""
|
||||
获取日完成量统计
|
||||
:param kw:
|
||||
:return:
|
||||
"""
|
||||
res = {'status': 1, 'message': '成功', 'data': {}}
|
||||
plan_obj = request.env['sf.production.plan'].sudo()
|
||||
line_list = ast.literal_eval(kw['line_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('line_list: %s' % line_list)
|
||||
|
||||
def get_date_list(start_date, end_date):
|
||||
date_list = []
|
||||
current_date = start_date
|
||||
while current_date <= end_date:
|
||||
date_list.append(current_date)
|
||||
current_date += timedelta(days=1)
|
||||
return date_list
|
||||
|
||||
for line in line_list:
|
||||
date_list = get_date_list(begin_time, end_time)
|
||||
order_counts = []
|
||||
|
||||
date_field_name = 'actual_end_time' # 替换为你模型中的实际字段名
|
||||
|
||||
for date in date_list:
|
||||
next_day = date + timedelta(days=1)
|
||||
orders = plan_obj.search([('production_line_id.name', '=', line), ('state', 'not in', ['draft']),
|
||||
(date_field_name, '>=', date.strftime('%Y-%m-%d 00:00:00')),
|
||||
(date_field_name, '<', next_day.strftime('%Y-%m-%d 00:00:00'))
|
||||
])
|
||||
|
||||
rework_orders = plan_obj.search(
|
||||
[('production_line_id.name', '=', line), ('state', 'in', ['rework']),
|
||||
(date_field_name, '>=', date.strftime('%Y-%m-%d 00:00:00')),
|
||||
(date_field_name, '<', next_day.strftime('%Y-%m-%d 00:00:00'))
|
||||
])
|
||||
not_passed_orders = plan_obj.search(
|
||||
[('production_line_id.name', '=', line), ('state', 'in', ['scrap', 'cancel']),
|
||||
(date_field_name, '>=', date.strftime('%Y-%m-%d 00:00:00')),
|
||||
(date_field_name, '<', next_day.strftime('%Y-%m-%d 00:00:00'))
|
||||
])
|
||||
order_counts.append({
|
||||
'date': date.strftime('%Y-%m-%d'),
|
||||
'order_count': len(orders),
|
||||
'rework_orders': len(rework_orders),
|
||||
'not_passed_orders': len(not_passed_orders)
|
||||
})
|
||||
# 外面包一层,没什么是包一层不能解决的,包一层就能区分了,类似于包一层div
|
||||
# 外面包一层的好处是,可以把多个数据结构打包在一起,方便前端处理
|
||||
|
||||
# date_list_dict = {line: order_counts}
|
||||
|
||||
res['data'][line] = order_counts
|
||||
return json.dumps(res)
|
||||
|
||||
# 实时产量
|
||||
@http.route('/api/RealTimeProduct', type='http', auth='public', methods=['GET', 'POST'], csrf=False, cors="*")
|
||||
def RealTimeProduct(self, **kw):
|
||||
"""
|
||||
获取实时产量
|
||||
:param kw:
|
||||
:return:
|
||||
"""
|
||||
res = {'status': 1, 'message': '成功', 'data': {}}
|
||||
plan_obj = request.env['sf.production.plan'].sudo()
|
||||
line_list = ast.literal_eval(kw['line_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')
|
||||
|
||||
def get_hourly_intervals(start_time, end_time):
|
||||
intervals = []
|
||||
current_time = start_time
|
||||
while current_time < end_time:
|
||||
next_hour = current_time + timedelta(hours=1)
|
||||
intervals.append((current_time, min(next_hour, end_time)))
|
||||
current_time = next_hour
|
||||
return intervals
|
||||
|
||||
# 当班计划量
|
||||
for line in line_list:
|
||||
plan_order_nums = plan_obj.search_count(
|
||||
[('production_line_id.name', '=', line), ('state', 'not in', ['draft']),
|
||||
('date_planned_start', '>=', begin_time),
|
||||
('date_planned_start', '<', end_time)
|
||||
])
|
||||
finish_order_nums = plan_obj.search_count(
|
||||
[('production_line_id.name', '=', line), ('state', 'in', ['finished']),
|
||||
('date_planned_start', '>=', begin_time),
|
||||
('date_planned_start', '<', end_time)
|
||||
])
|
||||
hourly_intervals = get_hourly_intervals(begin_time, end_time)
|
||||
production_counts = []
|
||||
|
||||
for start, end in hourly_intervals:
|
||||
orders = plan_obj.search([
|
||||
('actual_end_time', '>=', start.strftime('%Y-%m-%d %H:%M:%S')),
|
||||
('actual_end_time', '<', end.strftime('%Y-%m-%d %H:%M:%S')),
|
||||
('production_line_id.name', '=', line)
|
||||
])
|
||||
production_counts.append({
|
||||
'start_time': start.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'end_time': end.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'production_count': len(orders)
|
||||
})
|
||||
production_counts_dict = {'production_counts': production_counts,
|
||||
'plan_order_nums': plan_order_nums,
|
||||
'finish_order_nums': finish_order_nums,
|
||||
}
|
||||
|
||||
res['data'][line] = production_counts_dict
|
||||
# res['data'].append({line: production_counts})
|
||||
return json.dumps(res)
|
||||
|
||||
# 工单明细
|
||||
@http.route('/api/OrderDetail', type='http', auth='public', methods=['GET', 'POST'], csrf=False, cors="*")
|
||||
def OrderDetail(self, **kw):
|
||||
"""
|
||||
获取工单明细
|
||||
:param kw:
|
||||
:return:
|
||||
"""
|
||||
|
||||
# res = {'status': 1, 'message': '成功', 'not_done_data': [], 'done_data': []}
|
||||
res = {'status': 1, 'message': '成功', 'data': {}}
|
||||
plan_obj = request.env['sf.production.plan'].sudo()
|
||||
line_list = ast.literal_eval(kw['line_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('line_list: %s' % line_list)
|
||||
not_done_data = []
|
||||
done_data = []
|
||||
final_data = {}
|
||||
|
||||
for line in line_list:
|
||||
# 未完成订单
|
||||
not_done_orders = plan_obj.search(
|
||||
[('production_line_id.name', '=', line), ('state', 'not in', ['finished'])])
|
||||
print(not_done_orders)
|
||||
# 完成订单
|
||||
finish_orders = plan_obj.search([('production_line_id.name', '=', line), ('state', 'in', ['finished'])])
|
||||
print(finish_orders)
|
||||
|
||||
# 获取所有未完成订单的ID列表
|
||||
order_ids = [order.id for order in not_done_orders]
|
||||
# 获取所有已完成订单的ID列表
|
||||
finish_order_ids = [order.id for order in finish_orders]
|
||||
|
||||
# 对ID进行排序
|
||||
sorted_order_ids = sorted(order_ids)
|
||||
|
||||
finish_sorted_order_ids = sorted(finish_order_ids)
|
||||
|
||||
# 创建ID与序号的对应关系
|
||||
id_to_sequence = {order_id: index + 1 for index, order_id in enumerate(sorted_order_ids)}
|
||||
|
||||
finish_id_to_sequence = {order_id: index + 1 for index, order_id in enumerate(finish_sorted_order_ids)}
|
||||
|
||||
# # 输出结果或进一步处理
|
||||
# for order_id, sequence in id_to_sequence.items():
|
||||
# print(f"Order ID: {order_id} - Sequence: {sequence}")
|
||||
|
||||
for order in not_done_orders:
|
||||
blank_name = ''
|
||||
try:
|
||||
blank_name = order.production_id.move_raw_ids[0].product_id.name
|
||||
except:
|
||||
continue
|
||||
# blank_name = 'R-S00109-1 [碳素结构钢 Q235-118.0 * 72.0 * 21.0]'
|
||||
# 正则表达式
|
||||
material_pattern = r'\[(.*?)-' # 从 [ 开始,碰到 - 停止
|
||||
dimensions = blank_name.split('-')[-1].split(']')[0]
|
||||
|
||||
# 匹配材料名称
|
||||
material_match = re.search(material_pattern, blank_name)
|
||||
material = material_match.group(1) if material_match else 'No match found'
|
||||
|
||||
state_dict = {
|
||||
'draft': '待排程',
|
||||
'done': '已排程',
|
||||
'processing': '生产中',
|
||||
'finished': '已完成'
|
||||
}
|
||||
|
||||
line_dict = {
|
||||
'sequence': id_to_sequence[order.id],
|
||||
'workorder_name': order.name,
|
||||
'blank_name': blank_name,
|
||||
'material': material,
|
||||
'dimensions': dimensions,
|
||||
'order_qty': order.product_qty,
|
||||
'state': state_dict[order.state],
|
||||
|
||||
}
|
||||
not_done_data.append(line_dict)
|
||||
|
||||
for finish_order in finish_orders:
|
||||
blank_name = ''
|
||||
try:
|
||||
blank_name = finish_order.production_id.move_raw_ids[0].product_id.name
|
||||
except:
|
||||
continue
|
||||
|
||||
material_pattern = r'\[(.*?)-' # 从 [ 开始,碰到 - 停止
|
||||
dimensions = blank_name.split('-')[-1].split(']')[0]
|
||||
|
||||
# 匹配材料名称
|
||||
material_match = re.search(material_pattern, blank_name)
|
||||
material = material_match.group(1) if material_match else 'No match found'
|
||||
|
||||
line_dict = {
|
||||
'sequence': finish_id_to_sequence[finish_order.id],
|
||||
'workorder_name': finish_order.name,
|
||||
'blank_name': blank_name,
|
||||
'material': material,
|
||||
'dimensions': dimensions,
|
||||
'order_qty': finish_order.product_qty,
|
||||
'finish_time': finish_order.actual_end_time.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
|
||||
}
|
||||
done_data.append(line_dict)
|
||||
|
||||
# 开始包一层
|
||||
res['data'][line] = {'not_done_data': not_done_data, 'done_data': done_data}
|
||||
return json.dumps(res)
|
||||
|
||||
# 查询pg库来获得待机次数
|
||||
@http.route('/api/IdleAlarmCount', type='http', auth='public', methods=['GET', 'POST'], csrf=False, cors="*")
|
||||
def idle_count(self, **kw):
|
||||
"""
|
||||
查询设备的待机次数
|
||||
"""
|
||||
res = {'status': 1, 'message': '成功', 'data': {}}
|
||||
logging.info('前端请求机床数据的参数为:%s' % kw)
|
||||
|
||||
# 连接数据库
|
||||
conn = psycopg2.connect(**db_config)
|
||||
cur = conn.cursor()
|
||||
try:
|
||||
# 获取请求的机床数据
|
||||
machine_list = ast.literal_eval(kw['machine_list'])
|
||||
idle_times = []
|
||||
idle_dict = {}
|
||||
|
||||
for item in machine_list:
|
||||
sql = '''
|
||||
SELECT idle_start_time,alarm_time,alarm_repair_time FROM device_data WHERE device_name = %s;
|
||||
'''
|
||||
# 执行SQL命令
|
||||
cur.execute(sql, (item,))
|
||||
result = cur.fetchall()
|
||||
# # print('result', result)
|
||||
#
|
||||
# # 将查询结果添加到idle_times列表中
|
||||
# idle_times = [row[0] for row in result if row[0] is not None]
|
||||
#
|
||||
# # 对结果去重
|
||||
# unique_idle_times = set(idle_times)
|
||||
# # print('unique_idle_times', unique_idle_times)
|
||||
#
|
||||
# # 统计去重后的数量
|
||||
# idle_count = len(unique_idle_times)
|
||||
# # idle_dict[item] = idle_count
|
||||
#
|
||||
# res['data'][item] = idle_count
|
||||
|
||||
total_alarm_time = 0
|
||||
alarm_count = 0
|
||||
alarm_time_list = []
|
||||
idle_times = []
|
||||
alarm_times = []
|
||||
|
||||
for row in result:
|
||||
idle_start_time = row[0]
|
||||
alarm_time = row[1]
|
||||
alarm_repair_time = row[2]
|
||||
|
||||
alarm_time_list.append(alarm_time) # 将时长累加,以秒为单位
|
||||
idle_times.append(idle_start_time)
|
||||
# if alarm_repair_time is not None:
|
||||
# alarm_times.append(alarm_repair_time)
|
||||
alarm_times.append(alarm_repair_time)
|
||||
|
||||
# 对结果去重
|
||||
unique_total_alarm_time = set(alarm_time_list)
|
||||
unique_idle_times = set(idle_times)
|
||||
unique_alarm_times = set(alarm_times)
|
||||
|
||||
# 统计去重后的数量
|
||||
idle_count = len(unique_idle_times)
|
||||
|
||||
for alarm_time in unique_total_alarm_time:
|
||||
if alarm_time is not None:
|
||||
total_alarm_time += abs(float(alarm_time))
|
||||
|
||||
alarm_count = len(unique_alarm_times) if unique_alarm_times else 0
|
||||
alarm_count = alarm_count if total_alarm_time else 0
|
||||
|
||||
# 存储待机次数和总待机时长(单位:秒)
|
||||
res['data'][item] = {
|
||||
'idle_count': idle_count,
|
||||
'total_alarm_time': total_alarm_time / 3600, # 以秒为单位
|
||||
'alarm_count': alarm_count
|
||||
}
|
||||
|
||||
# 返回统计结果
|
||||
return json.dumps(res)
|
||||
except Exception as e:
|
||||
print(f"An error occurred: {e}")
|
||||
return json.dumps(res)
|
||||
finally:
|
||||
cur.close()
|
||||
conn.close()
|
||||
|
||||
# 查询pg库来获得异常情况
|
||||
@http.route('/api/alarm/logs', type='http', auth='public', methods=['GET', 'POST'], csrf=False, cors="*")
|
||||
def idle_count(self, **kw):
|
||||
"""
|
||||
查询设备的异常情况
|
||||
"""
|
||||
res = {'status': 1, 'message': '成功', 'data': {}}
|
||||
logging.info('前端请求机床数据的参数为:%s' % kw)
|
||||
|
||||
# 连接数据库
|
||||
conn = psycopg2.connect(**db_config)
|
||||
cur = conn.cursor()
|
||||
try:
|
||||
# 获取请求的机床数据
|
||||
# machine_list = ast.literal_eval(kw['machine_list'])
|
||||
# idle_times = []
|
||||
# idle_dict = {}
|
||||
|
||||
# for item in machine_list:
|
||||
sql = '''
|
||||
SELECT DISTINCT ON (alarm_time) alarm_time, alarm_message, system_date, system_time, alarm_repair_time
|
||||
FROM device_data
|
||||
WHERE alarm_time IS NOT NULL
|
||||
ORDER BY alarm_time, time;
|
||||
|
||||
'''
|
||||
# 执行SQL命令
|
||||
cur.execute(sql)
|
||||
result = cur.fetchall()
|
||||
print('result', result)
|
||||
|
||||
# 将查询结果转换为字典列表
|
||||
data = []
|
||||
for row in result:
|
||||
record = {
|
||||
'alarm_time': row[0],
|
||||
'alarm_message': row[1],
|
||||
'system_date': row[2],
|
||||
'system_time': row[3],
|
||||
'alarm_repair_time': row[4]
|
||||
}
|
||||
data.append(record)
|
||||
|
||||
# 将数据填充到返回结果中
|
||||
res['data'] = data
|
||||
|
||||
# 返回统计结果
|
||||
return json.dumps(res, ensure_ascii=False)
|
||||
except Exception as e:
|
||||
print(f"An error occurred: {e}")
|
||||
return json.dumps(res)
|
||||
finally:
|
||||
cur.close()
|
||||
conn.close()
|
||||
|
||||
@@ -2,3 +2,4 @@ from . import ftp_client
|
||||
from . import ftp_operate
|
||||
from . import py2opcua
|
||||
from . import res_config_setting
|
||||
from . import mrp_workorder
|
||||
|
||||
@@ -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):
|
||||
|
||||
38
sf_machine_connect/models/mrp_workorder.py
Normal file
38
sf_machine_connect/models/mrp_workorder.py
Normal 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
|
||||
@@ -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>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
17
sf_machine_connect/views/maintenance_views.xml
Normal file
17
sf_machine_connect/views/maintenance_views.xml
Normal 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>
|
||||
@@ -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')
|
||||
|
||||
|
||||
@@ -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','<',(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>
|
||||
|
||||
@@ -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',
|
||||
]
|
||||
|
||||
},
|
||||
|
||||
@@ -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,80 @@ 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:
|
||||
logging.info('货架已获取信息:%s' % item)
|
||||
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
|
||||
# 修改功能刀具状态
|
||||
if item.get('State') == '报警':
|
||||
if tool.functional_tool_status != item.get('State'):
|
||||
tool.write({
|
||||
'functional_tool_status': item['State']
|
||||
})
|
||||
else:
|
||||
location_id.product_sn_id = False
|
||||
if item['RfidCode']:
|
||||
logging.info('Rfid为【%s】的功能刀具在系统中不存在!' % item['RfidCode'])
|
||||
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 +496,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 +513,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 +522,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 +568,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 +577,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 +585,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 +595,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 +667,32 @@ 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/AgvStationState', 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)
|
||||
ret = ret['param']
|
||||
params = {}
|
||||
for i in range(len(ret)):
|
||||
if 'DeviceId' in ret[i] and 'AtHome' in ret[i]:
|
||||
logging.info('DeviceId:%s, AtHome:%s' % (ret[i]['DeviceId'], ret[i]['AtHome']))
|
||||
params[ret[i]['DeviceId']] = '占用' if ret[i]['AtHome'] else '空闲'
|
||||
if params:
|
||||
request.env['sf.agv.site'].update_site_state(params)
|
||||
except Exception as e:
|
||||
res = {'Succeed': False, 'ErrorCode': 202, 'Error': str(e)}
|
||||
logging.info('AGVDownProduct error:%s' % e)
|
||||
return json.JSONEncoder().encode(res)
|
||||
@@ -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:
|
||||
|
||||
16
sf_manufacturing/data/agv_scheduling_data.xml
Normal file
16
sf_manufacturing/data/agv_scheduling_data.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?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="date_range_period">day</field>
|
||||
<field name="company_id" eval="False"/>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -9,3 +9,4 @@ from . import stock
|
||||
from . import res_user
|
||||
from . import production_line_base
|
||||
from . import agv_setting
|
||||
from . import agv_scheduling
|
||||
|
||||
265
sf_manufacturing/models/agv_scheduling.py
Normal file
265
sf_manufacturing/models/agv_scheduling.py
Normal file
@@ -0,0 +1,265 @@
|
||||
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_id = fields.Many2one('sf.agv.task.route', '任务路线')
|
||||
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_id': idle_route.id})
|
||||
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_id': idle_route.id})
|
||||
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
|
||||
)
|
||||
if task_route:
|
||||
# 下发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.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_id = agv_task_route.id
|
||||
# rec._delivery_avg()
|
||||
# 更新接驳站状态
|
||||
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': '已配送',
|
||||
'feeder_station_destination_id': self.end_site_id.id,
|
||||
'route_id': self.agv_route_id.id,
|
||||
'task_completion_time': fields.Datetime.now()
|
||||
})
|
||||
elif vals['state'] == '配送中':
|
||||
self.env['sf.workpiece.delivery'].search([('agv_scheduling_id', '=', self.id)]).write({
|
||||
'feeder_station_destination_id': self.end_site_id.id,
|
||||
'route_id': self.agv_route_id.id,
|
||||
'task_delivery_time': fields.Datetime.now()
|
||||
})
|
||||
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', '!=', '已取消')])
|
||||
@@ -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'
|
||||
|
||||
@@ -6,7 +6,9 @@ import os
|
||||
import re
|
||||
import requests
|
||||
from itertools import groupby
|
||||
from odoo import api, fields, models, _
|
||||
from datetime import datetime
|
||||
from collections import defaultdict, namedtuple
|
||||
from odoo import api, fields, models, SUPERUSER_ID, _
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
from odoo.addons.sf_base.commons.common import Common
|
||||
from odoo.tools import float_compare, float_round, float_is_zero, format_datetime
|
||||
@@ -77,9 +79,10 @@ class MrpProduction(models.Model):
|
||||
('pending_cam', '待加工'),
|
||||
('progress', '加工中'),
|
||||
('rework', '返工'),
|
||||
('scrap', '报废'),
|
||||
('to_close', 'To Close'),
|
||||
('done', 'Done'),
|
||||
('cancel', '报废')], string='State',
|
||||
('cancel', '已取消')], string='State',
|
||||
compute='_compute_state', copy=False, index=True, readonly=True,
|
||||
store=True, tracking=True,
|
||||
help=" * Draft: The MO is not confirmed yet.\n"
|
||||
@@ -122,6 +125,7 @@ class MrpProduction(models.Model):
|
||||
|
||||
manual_quotation = fields.Boolean('人工编程', default=False, readonly=True)
|
||||
is_scrap = fields.Boolean('是否报废', default=False)
|
||||
is_remanufacture = fields.Boolean('是否重新制造', default=False)
|
||||
|
||||
@api.depends(
|
||||
'move_raw_ids.state', 'move_raw_ids.quantity_done', 'move_finished_ids.state', 'tool_state',
|
||||
@@ -166,7 +170,7 @@ class MrpProduction(models.Model):
|
||||
production.state = 'pending_cam'
|
||||
|
||||
if production.state == 'progress':
|
||||
if all(wo_state not in ('progress', 'done', 'rework') for wo_state in
|
||||
if all(wo_state not in ('progress', 'done', 'rework', 'scrap') for wo_state in
|
||||
production.workorder_ids.mapped('state')):
|
||||
production.state = 'pending_cam'
|
||||
if production.is_rework is True:
|
||||
@@ -184,9 +188,14 @@ class MrpProduction(models.Model):
|
||||
for wo in
|
||||
production.workorder_ids):
|
||||
production.state = 'rework'
|
||||
if any(wo.test_results == '报废' and wo.state == 'done' for wo in production.workorder_ids):
|
||||
production.state = 'scrap'
|
||||
if any(dr.test_results == '报废' and dr.handle_result == '已处理' for dr in
|
||||
production.detection_result_ids):
|
||||
production.state = 'cancel'
|
||||
# 如果制造订单的功能刀具为【无效刀】则制造订单状态改为返工
|
||||
# if production.tool_state == '2':
|
||||
# production.state = 'rework'
|
||||
if production.tool_state == '2':
|
||||
production.state = 'rework'
|
||||
|
||||
def action_check(self):
|
||||
"""
|
||||
@@ -788,6 +797,23 @@ class MrpProduction(models.Model):
|
||||
})
|
||||
return action
|
||||
|
||||
# 报废
|
||||
def button_scrap_new(self):
|
||||
cloud_programming = self._cron_get_programming_state()
|
||||
return {
|
||||
'name': _('报废'),
|
||||
'type': 'ir.actions.act_window',
|
||||
'view_mode': 'form',
|
||||
'res_model': 'sf.production.wizard',
|
||||
'target': 'new',
|
||||
'context': {
|
||||
'default_production_id': self.id,
|
||||
'default_programming_state': '编程中' if cloud_programming[
|
||||
'programming_state'] != '已下发' else '已下发',
|
||||
'default_is_reprogramming': True if cloud_programming['programming_state'] in ['已下发'] else False
|
||||
}
|
||||
}
|
||||
|
||||
# 返工
|
||||
def button_rework(self):
|
||||
cloud_programming = None
|
||||
@@ -912,44 +938,129 @@ class MrpProduction(models.Model):
|
||||
logging.info('get_new_program error:%s' % e)
|
||||
raise UserError("从云平台获取最新程序失败,请联系管理员")
|
||||
|
||||
def recreateManufacturing(self):
|
||||
def recreateManufacturing(self, item):
|
||||
"""
|
||||
重新生成制造订单
|
||||
"""
|
||||
if self.is_scrap is True:
|
||||
sale_order = self.env['sale.order'].sudo().search([('name', '=', productions.origin)])
|
||||
values = self.env['mrp.production'].create_production1_values(self.production_id)
|
||||
productions = self.env['mrp.production'].with_user(SUPERUSER_ID).sudo().with_company(
|
||||
self.production_id.company_id).create(
|
||||
values)
|
||||
# self.env['stock.move'].sudo().create(productions._get_moves_raw_values())
|
||||
self.env['stock.move'].sudo().create(productions._get_moves_finished_values())
|
||||
productions._create_workorder()
|
||||
productions.filtered(lambda p: (not p.orderpoint_id and p.move_raw_ids) or \
|
||||
(
|
||||
p.move_dest_ids.procure_method != 'make_to_order' and
|
||||
not p.move_raw_ids and not p.workorder_ids)).action_confirm()
|
||||
for production_item in productions:
|
||||
process_parameter_workorder = self.env['mrp.workorder'].search(
|
||||
[('surface_technics_parameters_id', '!=', False), ('production_id', '=', production_item.id),
|
||||
('is_subcontract', '=', True)])
|
||||
if process_parameter_workorder:
|
||||
is_pick = False
|
||||
consecutive_workorders = []
|
||||
m = 0
|
||||
sorted_workorders = sorted(process_parameter_workorder, key=lambda w: w.id)
|
||||
for i in range(len(sorted_workorders) - 1):
|
||||
if m == 0:
|
||||
is_pick = False
|
||||
if sorted_workorders[i].supplier_id.id == sorted_workorders[i + 1].supplier_id.id and \
|
||||
sorted_workorders[i].is_subcontract == sorted_workorders[i + 1].is_subcontract and \
|
||||
sorted_workorders[i].id == sorted_workorders[i + 1].id - 1:
|
||||
if sorted_workorders[i] not in consecutive_workorders:
|
||||
consecutive_workorders.append(sorted_workorders[i])
|
||||
consecutive_workorders.append(sorted_workorders[i + 1])
|
||||
m += 1
|
||||
continue
|
||||
else:
|
||||
procurement_requests = []
|
||||
sale_order = self.env['sale.order'].sudo().search([('name', '=', self.origin)])
|
||||
values = self.env['mrp.production'].create_production1_values(self)
|
||||
# productions = self.env['mrp.production'].with_user(SUPERUSER_ID).sudo().with_company(
|
||||
# self.company_id).create(
|
||||
# values)
|
||||
# 查询出库移动记录
|
||||
out_picking = self.env['stock.picking'].search(
|
||||
[('origin', '=', sale_order.name), ('name', 'ilike', 'WH/OUT/')])
|
||||
move = out_picking.move_ids.filtered(lambda pd: pd.product_id == self.product_id)
|
||||
move_values = {'product_description_variants': '',
|
||||
'date_planned': datetime.now(),
|
||||
'date_deadline': datetime.now(),
|
||||
# 'move_dest_ids': self.env['stock.move'].search([('id', '=', move.id)]),
|
||||
'move_dest_ids': move,
|
||||
'group_id': False,
|
||||
'route_ids': [],
|
||||
'warehouse_id': self.warehouse_id,
|
||||
'priority': 0,
|
||||
'orderpoint_id': False,
|
||||
'product_packaging_id': False}
|
||||
rule = self.env['stock.rule'].search(
|
||||
[('action', '=', 'pull'), ('procure_method', '=', 'mts_else_mto'), (
|
||||
'location_dest_id', '=', self.env['stock.location'].search([('parent_path', '=', '2/5/')]).id),
|
||||
('location_src_id', '=', self.env['stock.location'].search(
|
||||
[('barcode', '=', 'CP')]).id)])
|
||||
# origin = move._prepare_procurement_origin()
|
||||
procurement_requests.append(self.env['procurement.group'].Procurement(
|
||||
move.product_id, 1.0, move.product_uom,
|
||||
self.env['stock.location'].search([('barcode', '=', 'CP')]),
|
||||
rule and rule.name or "/",
|
||||
sale_order.name, move.company_id, move_values))
|
||||
self.env['procurement.group'].run(procurement_requests,
|
||||
raise_user_error=not self.env.context.get('from_orderpoint'))
|
||||
# self.env['stock.move'].sudo().create(productions._get_moves_finished_values())
|
||||
# productions.filtered(lambda p: (not p.orderpoint_id and p.move_raw_ids) or \
|
||||
# (
|
||||
# p.move_dest_ids.procure_method != 'make_to_order' and
|
||||
# not p.move_raw_ids and not p.workorder_ids)).action_confirm()
|
||||
productions = self.env['mrp.production'].sudo().search(
|
||||
[('origin', '=', self.origin)], order='id desc', limit=1)
|
||||
move = self.env['stock.move'].search([('origin', '=', productions.name)], order='id desc')
|
||||
for mo in move:
|
||||
if mo.procure_method == 'make_to_order' and mo.name != productions.name:
|
||||
if mo.name == '/':
|
||||
domain = [('barcode', '=', 'WH-PC'), ('sequence_code', '=', 'PC')]
|
||||
elif mo.name == '拉':
|
||||
domain = [('barcode', '=', 'WH-INTERNAL'), ('sequence_code', '=', 'INT')]
|
||||
picking_type = self.env['stock.picking.type'].search(domain)
|
||||
mo.write({'picking_type_id': picking_type.id})
|
||||
mo._assign_picking()
|
||||
else:
|
||||
if mo.reference != productions.name:
|
||||
mo.reference = productions.name
|
||||
if mo.production_id:
|
||||
if mo.production_id != productions:
|
||||
mo.production_id = False
|
||||
mo_move = self.env['stock.move'].search(
|
||||
[('origin', '=', sale_order.name), ('reference', 'ilike', 'WH/MO/')])
|
||||
if mo_move:
|
||||
sfp_move = self.env['stock.move'].search(
|
||||
[('origin', '=', sale_order.name), ('reference', 'ilike', 'WH/SFP/')], limit=1)
|
||||
mo_move.write({'reference': sfp_move.reference, 'partner_id': sfp_move.partner_id.id,
|
||||
'picking_id': sfp_move.picking_id.id, 'picking_type_id': sfp_move.picking_type_id.id,
|
||||
'production_id': False})
|
||||
productions.write({'programming_no': self.programming_no, 'is_remanufacture': True})
|
||||
productions.procurement_group_id.mrp_production_ids.move_dest_ids.write(
|
||||
{'group_id': self.env['procurement.group'].search([('name', '=', sale_order.name)])})
|
||||
scarp_process_parameter_workorder = self.env['mrp.workorder'].search(
|
||||
[('surface_technics_parameters_id', '!=', False), ('production_id', '=', self.id),
|
||||
('is_subcontract', '=', True)])
|
||||
if scarp_process_parameter_workorder:
|
||||
production_programming = self.env['mrp.production'].search(
|
||||
[('programming_no', '=', self.programming_no)], order='name asc')
|
||||
production_list = [production.name for production in production_programming]
|
||||
purchase_orders = self.env['purchase.order'].search([('origin', '=', ','.join(production_list))])
|
||||
for purchase_item in purchase_orders.order_line:
|
||||
for process_item in scarp_process_parameter_workorder:
|
||||
if purchase_item.product_id.categ_type == '表面工艺':
|
||||
if purchase_item.product_id.server_product_process_parameters_id == process_item.surface_technics_parameters_id:
|
||||
print(purchase_orders.find(productions.name))
|
||||
if purchase_orders.find(productions.name) == -1:
|
||||
purchase_orders.origin += productions.name
|
||||
if item['is_reprogramming'] is False:
|
||||
productions._create_workorder(item)
|
||||
productions.programming_state = '已编程'
|
||||
for production_item in productions:
|
||||
process_parameter_workorder = self.env['mrp.workorder'].search(
|
||||
[('surface_technics_parameters_id', '!=', False), ('production_id', '=', production_item.id),
|
||||
('is_subcontract', '=', True)])
|
||||
if process_parameter_workorder:
|
||||
is_pick = False
|
||||
consecutive_workorders = []
|
||||
m = 0
|
||||
sorted_workorders = sorted(process_parameter_workorder, key=lambda w: w.id)
|
||||
for i in range(len(sorted_workorders) - 1):
|
||||
if m == 0:
|
||||
is_pick = False
|
||||
if sorted_workorders[i].supplier_id.id == sorted_workorders[i + 1].supplier_id.id and \
|
||||
sorted_workorders[i].is_subcontract == sorted_workorders[i + 1].is_subcontract and \
|
||||
sorted_workorders[i].id == sorted_workorders[i + 1].id - 1:
|
||||
if sorted_workorders[i] not in consecutive_workorders:
|
||||
consecutive_workorders.append(sorted_workorders[i])
|
||||
consecutive_workorders.append(sorted_workorders[i + 1])
|
||||
m += 1
|
||||
continue
|
||||
else:
|
||||
if m == len(consecutive_workorders) - 1 and m != 0:
|
||||
self.env['stock.picking'].create_outcontract_picking(consecutive_workorders,
|
||||
production_item)
|
||||
if sorted_workorders[i] in consecutive_workorders:
|
||||
is_pick = True
|
||||
consecutive_workorders = []
|
||||
m = 0
|
||||
# 当前面的连续工序生成对应的外协出入库单再生成当前工序的外协出入库单
|
||||
if is_pick is False:
|
||||
self.env['stock.picking'].create_outcontract_picking(sorted_workorders[i],
|
||||
production_item)
|
||||
if m == len(consecutive_workorders) - 1 and m != 0:
|
||||
self.env['stock.picking'].create_outcontract_picking(consecutive_workorders,
|
||||
production_item)
|
||||
@@ -957,71 +1068,24 @@ class MrpProduction(models.Model):
|
||||
is_pick = True
|
||||
consecutive_workorders = []
|
||||
m = 0
|
||||
# 当前面的连续工序生成对应的外协出入库单再生成当前工序的外协出入库单
|
||||
if is_pick is False:
|
||||
self.env['stock.picking'].create_outcontract_picking(sorted_workorders[i],
|
||||
production_item)
|
||||
if m == len(consecutive_workorders) - 1 and m != 0:
|
||||
self.env['stock.picking'].create_outcontract_picking(consecutive_workorders,
|
||||
production_item)
|
||||
if sorted_workorders[i] in consecutive_workorders:
|
||||
is_pick = True
|
||||
consecutive_workorders = []
|
||||
m = 0
|
||||
if m == len(consecutive_workorders) - 1 and m != 0:
|
||||
self.env['stock.picking'].create_outcontract_picking(consecutive_workorders, production_item)
|
||||
if is_pick is False and m == 0:
|
||||
if len(sorted_workorders) == 1:
|
||||
self.env['stock.picking'].create_outcontract_picking(sorted_workorders, production_item)
|
||||
else:
|
||||
self.env['stock.picking'].create_outcontract_picking(sorted_workorders[i], production_item)
|
||||
|
||||
for production in productions:
|
||||
origin_production = production.move_dest_ids and production.move_dest_ids[
|
||||
0].raw_material_production_id or False
|
||||
orderpoint = production.orderpoint_id
|
||||
if orderpoint and orderpoint.create_uid.id == SUPERUSER_ID and orderpoint.trigger == 'manual':
|
||||
production.message_post(
|
||||
body=_('This production order has been created from Replenishment Report.'),
|
||||
message_type='comment',
|
||||
subtype_xmlid='mail.mt_note')
|
||||
elif orderpoint:
|
||||
production.message_post_with_view(
|
||||
'mail.message_origin_link',
|
||||
values={'self': production, 'origin': orderpoint},
|
||||
subtype_id=self.env.ref('mail.mt_note').id)
|
||||
elif origin_production:
|
||||
production.message_post_with_view(
|
||||
'mail.message_origin_link',
|
||||
values={'self': production, 'origin': origin_production},
|
||||
subtype_id=self.env.ref('mail.mt_note').id)
|
||||
|
||||
'''
|
||||
创建生产计划
|
||||
'''
|
||||
# 工单耗时
|
||||
workorder_duration = 0
|
||||
for workorder in productions.workorder_ids:
|
||||
workorder_duration += workorder.duration_expected
|
||||
|
||||
if sale_order:
|
||||
sale_order.mrp_production_ids |= productions
|
||||
# sale_order.write({'schedule_status': 'to schedule'})
|
||||
self.env['sf.production.plan'].sudo().with_company(self.production_id.company_id).create({
|
||||
'name': productions.name,
|
||||
'order_deadline': sale_order.deadline_of_delivery,
|
||||
'production_id': productions.id,
|
||||
'date_planned_start': productions.date_planned_start,
|
||||
'origin': productions.origin,
|
||||
'product_qty': productions.product_qty,
|
||||
'product_id': productions.product_id.id,
|
||||
'state': 'draft',
|
||||
})
|
||||
if is_pick is False and m == 0:
|
||||
if len(sorted_workorders) == 1:
|
||||
self.env['stock.picking'].create_outcontract_picking(sorted_workorders, production_item)
|
||||
else:
|
||||
self.env['stock.picking'].create_outcontract_picking(sorted_workorders[i],
|
||||
production_item)
|
||||
else:
|
||||
productions.programming_state = '编程中'
|
||||
return productions
|
||||
|
||||
# 在之前的销售单上重新生成制造订单
|
||||
def create_production1_values(self, production, sale_order):
|
||||
def create_production1_values(self, production):
|
||||
production_values_str = {'origin': production.origin,
|
||||
'product_id': production.product_id.id,
|
||||
'programming_state': '已编程',
|
||||
'product_description_variants': production.product_description_variants,
|
||||
'product_qty': production.product_qty,
|
||||
'product_uom_id': production.product_uom_id.id,
|
||||
@@ -1031,7 +1095,8 @@ class MrpProduction(models.Model):
|
||||
'date_deadline': production.date_deadline,
|
||||
'date_planned_start': production.date_planned_start,
|
||||
'date_planned_finished': production.date_planned_finished,
|
||||
'procurement_group_id': sale_order.id,
|
||||
# 'procurement_group_id': self.env["procurement.group"].create(
|
||||
# {'name': production.name}).id,
|
||||
'propagate_cancel': production.propagate_cancel,
|
||||
'orderpoint_id': production.orderpoint_id.id,
|
||||
'picking_type_id': production.picking_type_id.id,
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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):
|
||||
@@ -921,7 +934,7 @@ class ResMrpWorkOrder(models.Model):
|
||||
workorder.state = 'waiting'
|
||||
elif workorder.routing_type == '解除装夹' and workorder.state not in ['done', 'rework', 'cancel']:
|
||||
if cnc_workorder:
|
||||
if not cnc_workorder_pending:
|
||||
if not cnc_workorder_pending or unclamp_workorder.test_results == '报废':
|
||||
workorder.state = 'waiting'
|
||||
# else:
|
||||
# if workorder.production_id.is_rework is True:
|
||||
@@ -953,10 +966,9 @@ class ResMrpWorkOrder(models.Model):
|
||||
workorder.state = 'ready'
|
||||
else:
|
||||
workorder.state = 'waiting'
|
||||
|
||||
# else:
|
||||
# if workorder.state not in ['cancel', 'rework']:
|
||||
# workorder.state = 'rework'
|
||||
elif workorder.production_id.state == 'scrap':
|
||||
if workorder.routing_type == '解除装夹' and unclamp_workorder.test_results == '报废':
|
||||
workorder.state = 'waiting'
|
||||
if workorder.routing_type == '装夹预调' and workorder.state in ['waiting', 'ready', 'pending']:
|
||||
workorder_ids = workorder.production_id.workorder_ids
|
||||
work_bo = True
|
||||
@@ -1149,16 +1161,23 @@ 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': '待加工'})
|
||||
record.production_id.process_state = '待解除装夹'
|
||||
self.env['sf.production.plan'].sudo().search([('name', '=', record.production_id.name)]).write({
|
||||
'state': 'finished',
|
||||
'actual_end_time': datetime.now()
|
||||
})
|
||||
record.production_id.write({'detection_result_ids': [(0, 0, {
|
||||
'rework_reason': record.reason,
|
||||
'detailed_reason': record.detailed_reason,
|
||||
'processing_panel': record.processing_panel,
|
||||
'routing_type': record.routing_type,
|
||||
'handle_result': '待处理' if record.test_results == '返工' or record.is_rework is True else '',
|
||||
'handle_result': '待处理' if record.test_results in ['返工',
|
||||
'报废'] or record.is_rework is True else '',
|
||||
'test_results': record.test_results,
|
||||
'test_report': record.detection_report})],
|
||||
'is_scrap': True if record.test_results == '报废' else False})
|
||||
@@ -1236,6 +1255,18 @@ 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 +1306,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 +1574,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 +1646,24 @@ 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:
|
||||
@@ -1579,14 +1675,14 @@ class WorkPieceDelivery(models.Model):
|
||||
obj.feeder_station_start_id.name, obj.feeder_station_destination_id.name)
|
||||
return obj
|
||||
|
||||
@api.constrains('route_id')
|
||||
def _check_route_id(self):
|
||||
if self.type == '运送空料架':
|
||||
if self.route_id and self.name is False:
|
||||
route = self.sudo().search(
|
||||
[('route_id', '=', self.route_id.id), ('id', '!=', self.id), ('name', 'ilike', '运送空料架路线')])
|
||||
if route:
|
||||
raise UserError("该任务路线已存在,请重新选择")
|
||||
# @api.constrains('route_id')
|
||||
# def _check_route_id(self):
|
||||
# if self.type == '运送空料架':
|
||||
# if self.route_id and self.name is False:
|
||||
# route = self.sudo().search(
|
||||
# [('route_id', '=', self.route_id.id), ('id', '!=', self.id), ('name', 'ilike', '运送空料架路线')])
|
||||
# if route:
|
||||
# raise UserError("该任务路线已存在,请重新选择")
|
||||
|
||||
# @api.constrains('name')
|
||||
# def _check_name(self):
|
||||
@@ -1615,84 +1711,44 @@ 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):
|
||||
|
||||
@@ -68,6 +68,7 @@ class StockRule(models.Model):
|
||||
|
||||
@api.model
|
||||
def _run_pull(self, procurements):
|
||||
logging.info(procurements)
|
||||
moves_values_by_company = defaultdict(list)
|
||||
mtso_products_by_locations = defaultdict(list)
|
||||
|
||||
@@ -176,7 +177,10 @@ class StockRule(models.Model):
|
||||
for company_id, moves_values in moves_values_by_company.items():
|
||||
# create the move as SUPERUSER because the current user may not have the rights to do it (mto product
|
||||
# launched by a sale for example)
|
||||
moves = self.env['stock.move'].with_user(SUPERUSER_ID).sudo().with_company(company_id).create(moves_values)
|
||||
logging.info(moves_values)
|
||||
moves = self.env['stock.move'].with_user(SUPERUSER_ID).sudo().with_company(company_id).create(
|
||||
moves_values)
|
||||
logging.info(moves)
|
||||
# Since action_confirm launch following procurement_group we should activate it.
|
||||
moves._action_confirm()
|
||||
|
||||
|
||||
@@ -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,15 +1,14 @@
|
||||
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')
|
||||
RFID = ''
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
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}) {
|
||||
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 => {
|
||||
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;
|
||||
});
|
||||
57
sf_manufacturing/static/src/xml/button_show_on_tree.xml
Normal file
57
sf_manufacturing/static/src/xml/button_show_on_tree.xml
Normal 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>
|
||||
79
sf_manufacturing/views/agv_scheduling_views.xml
Normal file
79
sf_manufacturing/views/agv_scheduling_views.xml
Normal 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_id"/>
|
||||
<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_id"/>
|
||||
<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>
|
||||
@@ -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>
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
<field name="model">mrp.production</field>
|
||||
<field name="inherit_id" ref="mrp.mrp_production_tree_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<!-- <xpath expr="//button[@name='do_unreserve']" position="after">-->
|
||||
<!-- <button name="do_update_program" type="object" string="更新程序"-->
|
||||
<!-- groups="sf_base.group_sf_mrp_user"/>-->
|
||||
<!-- </xpath>-->
|
||||
<!-- <xpath expr="//button[@name='do_unreserve']" position="after">-->
|
||||
<!-- <button name="do_update_program" type="object" string="更新程序"-->
|
||||
<!-- groups="sf_base.group_sf_mrp_user"/>-->
|
||||
<!-- </xpath>-->
|
||||
<xpath expr="//field[@name='product_id']" position="replace"/>
|
||||
<xpath expr="//field[@name='product_qty']" position="replace"/>
|
||||
<xpath expr="//field[@name='product_uom_id']" position="replace"/>
|
||||
@@ -70,7 +70,7 @@
|
||||
<!-- <attribute name="statusbar_visible">draft,confirmed,progress,pending_processing,completed,done -->
|
||||
<!-- </attribute> -->
|
||||
<attribute name="statusbar_visible">
|
||||
confirmed,pending_cam,progress,done
|
||||
confirmed,pending_cam,progress,rework,scrap,done
|
||||
</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//sheet//group//group[2]//label" position="before">
|
||||
@@ -122,15 +122,15 @@
|
||||
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', '!=', '已编程未下发')]}"/>
|
||||
<button name="button_rework" string="返工" type="object" groups="sf_base.group_sf_mrp_user"
|
||||
attrs="{'invisible': ['|',('state', '!=', 'rework') ,('programming_state', '!=', '已编程')]}"/>
|
||||
<!-- <button name="%(sf_manufacturing.action_sf_production_wizard)d" string="报废" type="action"-->
|
||||
<!-- groups="sf_base.group_sf_mrp_user"-->
|
||||
<!-- attrs="{'invisible': [('is_scrap', '=', False)]}"/>-->
|
||||
<button name="button_scrap_new" string="报废" type="object"
|
||||
groups="sf_base.group_sf_mrp_user"
|
||||
attrs="{'invisible': ['|',('is_scrap', '=', False),('state','=','cancel')]}"/>
|
||||
</xpath>
|
||||
<xpath expr="(//header//button[@name='button_mark_done'])[3]" position="replace">
|
||||
<button name="button_mark_done" attrs="{'invisible': [
|
||||
@@ -460,6 +460,7 @@
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//filter[@name='filter_in_progress']" position="before">
|
||||
<filter string="返工" name="filter_rework" domain="[('state', '=', 'rework')]"/>
|
||||
<filter string="报废" name="filter_scrap" domain="[('state', '=', 'scrap')]"/>
|
||||
</xpath>
|
||||
<xpath expr="//filter[@name='planning_issues']" position="before">
|
||||
<separator/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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),'&',('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),'&',('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>
|
||||
@@ -522,7 +538,7 @@
|
||||
<xpath expr="//page[1]" position="before">
|
||||
<page string="CNC程序" attrs='{"invisible": [("routing_type","!=","CNC加工")]}'>
|
||||
<field name="cnc_ids" widget="one2many" string="工作程序" default_order="sequence_number,id"
|
||||
readonly="1">
|
||||
readonly="0">
|
||||
<tree>
|
||||
<field name="sequence_number"/>
|
||||
<field name="program_name"/>
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
# Part of YiZuo. See LICENSE file for full copyright and licensing details.
|
||||
import logging
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
from collections import defaultdict, namedtuple
|
||||
from odoo.addons.stock.models.stock_rule import ProcurementException
|
||||
from datetime import datetime
|
||||
from odoo import models, api, fields, _
|
||||
|
||||
@@ -11,11 +13,86 @@ class ProductionWizard(models.TransientModel):
|
||||
_description = '制造订单向导'
|
||||
|
||||
production_id = fields.Many2one('mrp.production', string='制造订单号')
|
||||
is_reprogramming = fields.Boolean(string='申请重新编程', default=True)
|
||||
is_reprogramming = fields.Boolean(string='申请重新编程', default=False)
|
||||
is_remanufacture = fields.Boolean(string='重新生成制造订单', default=True)
|
||||
programming_state = fields.Selection(
|
||||
[('待编程', '待编程'), ('编程中', '编程中'), ('已编程', '已编程'), ('已编程未下发', '已编程未下发'),
|
||||
('已下发', '已下发')],
|
||||
string='编程状态')
|
||||
|
||||
@api.onchange('is_remanufacture')
|
||||
def _onchange_is_reprogramming(self):
|
||||
if self.is_remanufacture is False:
|
||||
self.is_reprogramming = False
|
||||
|
||||
def confirm(self):
|
||||
if self.is_reprogramming is True:
|
||||
self.production_id.update_programming_state()
|
||||
self.production_id.action_cancel()
|
||||
self.production_id.action_cancel()
|
||||
self.production_id.detection_result_ids.write({'handle_result': '已处理'})
|
||||
self.production_id.write({'state': 'cancel', 'scrap_ids': [(0, 0, {
|
||||
'name': self.env['ir.sequence'].next_by_code('stock.scrap') or _('New'),
|
||||
'product_id': self.production_id.product_id.id,
|
||||
'scrap_qty': 1,
|
||||
'lot_id': self.production_id.move_line_raw_ids.lot_id.id,
|
||||
'location_id': self.production_id.move_raw_ids.filtered(lambda x: x.state not in (
|
||||
'done',
|
||||
'cancel')) and self.production_id.location_src_id.id or self.production_id.location_dest_id.id,
|
||||
'scrap_location_id': self.env['stock.scrap']._get_default_scrap_location_id(),
|
||||
'state': 'done'})]})
|
||||
if self.is_remanufacture is True:
|
||||
ret = {'programming_list': [], 'is_reprogramming': self.is_reprogramming}
|
||||
if self.is_reprogramming is True:
|
||||
self.production_id.update_programming_state()
|
||||
else:
|
||||
scrap_cnc = self.production_id.workorder_ids.filtered(lambda crw: crw.routing_type == 'CNC加工').cnc_ids
|
||||
scrap_cmm = self.production_id.workorder_ids.filtered(lambda cm: cm.routing_type == 'CNC加工').cmm_ids
|
||||
for item_line in scrap_cnc:
|
||||
vals = {
|
||||
'sequence_number': item_line.sequence_number,
|
||||
'program_name': item_line.program_name,
|
||||
'cutting_tool_name': item_line.cutting_tool_name,
|
||||
'cutting_tool_no': item_line.cutting_tool_no,
|
||||
'processing_type': item_line.processing_type,
|
||||
'margin_x_y': item_line.margin_x_y,
|
||||
'margin_z': item_line.margin_z,
|
||||
'depth_of_processing_z': item_line.depth_of_processing_z,
|
||||
'cutting_tool_extension_length': item_line.cutting_tool_extension_length,
|
||||
'estimated_processing_time': item_line.estimated_processing_time,
|
||||
'cutting_tool_handle_type': item_line.cutting_tool_handle_type,
|
||||
'ftp_path': item_line.program_path,
|
||||
'processing_panel': item_line.workorder_id.processing_panel,
|
||||
'program_create_date': datetime.strftime(item_line.program_create_date,
|
||||
'%Y-%m-%d %H:%M:%S'),
|
||||
'remark': item_line.remark
|
||||
}
|
||||
ret['programming_list'].append(vals)
|
||||
for cmm_line in scrap_cmm:
|
||||
vals = {
|
||||
'sequence_number': cmm_line.sequence_number,
|
||||
'program_name': cmm_line.program_name,
|
||||
'ftp_path': cmm_line.program_path,
|
||||
'processing_panel': item_line.workorder_id.processing_panel,
|
||||
'program_create_date': datetime.strftime(
|
||||
cmm_line.program_create_date,
|
||||
'%Y-%m-%d %H:%M:%S')
|
||||
}
|
||||
ret['programming_list'].append(vals)
|
||||
|
||||
new_production = self.production_id.recreateManufacturing(ret)
|
||||
if self.is_reprogramming is False:
|
||||
for panel in new_production.product_id.model_processing_panel.split(','):
|
||||
scrap_cnc_workorder = max(
|
||||
self.production_id.workorder_ids.filtered(
|
||||
lambda
|
||||
scn: scn.processing_panel == panel and scn.routing_type == 'CNC加工'),
|
||||
key=lambda w: w.create_date)
|
||||
scrap_pre_workorder = max(self.production_id.workorder_ids.filtered(
|
||||
lambda
|
||||
pr: pr.processing_panel == panel and pr.routing_type == '装夹预调'),
|
||||
key=lambda w1: w1.create_date)
|
||||
new_cnc_workorder = new_production.workorder_ids.filtered(
|
||||
lambda
|
||||
nc: nc.processing_panel == panel and nc.routing_type == 'CNC加工')
|
||||
new_cnc_workorder.write({'cnc_worksheet': scrap_cnc_workorder.cnc_worksheet})
|
||||
new_pre_workorder = new_production.workorder_ids.filtered(lambda
|
||||
p: p.routing_type == '装夹预调' and p.processing_panel == panel)
|
||||
new_pre_workorder.write({'processing_drawing': scrap_pre_workorder.processing_drawing})
|
||||
|
||||
@@ -6,14 +6,17 @@
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<sheet>
|
||||
<field name="production_id" invisible="True"/>
|
||||
<field name="production_id" invisible="1"/>
|
||||
<field name="programming_state" invisible="1"/>
|
||||
<div>
|
||||
重新生成制造订单
|
||||
<field name="is_remanufacture"/>
|
||||
<field name="is_remanufacture" force_save="1"/>
|
||||
</div>
|
||||
<div>
|
||||
申请重新编程
|
||||
<field name="is_reprogramming" attrs='{"invisible": [("is_remanufacture","=",False)]}'/>
|
||||
<div attrs='{"invisible": [("is_remanufacture","=",False)]}'>
|
||||
<span style='font-weight:bold;'>申请重新编程
|
||||
<field name="is_reprogramming" force_save="1"
|
||||
attrs='{"readonly": [("programming_state","not in",["已下发"])]}'/>
|
||||
</span>
|
||||
</div>
|
||||
<footer>
|
||||
<button string="确认" name="confirm" type="object" class="oe_highlight" confirm="是否确认报废"/>
|
||||
@@ -28,6 +31,9 @@
|
||||
<field name="name">报废</field>
|
||||
<field name="res_model">sf.production.wizard</field>
|
||||
<field name="view_mode">form</field>
|
||||
<!-- <field name="context">{-->
|
||||
<!-- 'default_production_id': active_id}-->
|
||||
<!-- </field>-->
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
|
||||
|
||||
@@ -71,9 +71,9 @@ class ReworkWizard(models.TransientModel):
|
||||
lambda ap: ap.processing_panel == panel.name and ap.state != 'rework')
|
||||
if panel_workorder:
|
||||
panel_workorder.write({'state': 'rework'})
|
||||
panel_workorder.filtered(
|
||||
lambda wo: wo.routing_type == '装夹预调').workpiece_delivery_ids.filtered(
|
||||
lambda wd: wd.status == '待下发').write({'status': '已取消'})
|
||||
# panel_workorder.filtered(
|
||||
# lambda wo: wo.routing_type == '装夹预调').workpiece_delivery_ids.filtered(
|
||||
# lambda wd: wd.status == '待下发').write({'status': '已取消'})
|
||||
# workpiece = self.env['sf.workpiece.delivery'].search([('status', '=', '待下发'), (
|
||||
# 'workorder_id', '=',
|
||||
# panel_workorder.filtered(lambda wd: wd.routing_type == '装夹预调').id)])
|
||||
|
||||
@@ -16,10 +16,10 @@
|
||||
attrs='{"invisible": [("routing_type","=","装夹预调")]}' widget="many2many_tags"/>
|
||||
</group>
|
||||
<div attrs='{"invisible": [("reprogramming_num","=",0)]}'>
|
||||
注意: 该制造订单的产品已重复编程过<field
|
||||
注意: 该制造订单产品已申请重新编程次数为<field
|
||||
name="reprogramming_num" string=""
|
||||
readonly="1"
|
||||
style='color:red;'/>次,且当前编程状态为
|
||||
style='color:red;'/>,且当前编程状态为
|
||||
<field name="programming_state" string=""
|
||||
decoration-info="programming_state == '待编程'"
|
||||
decoration-success="programming_state == '已下发'"
|
||||
|
||||
@@ -4,30 +4,22 @@
|
||||
<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 js_class="remove_focus_view">
|
||||
<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"/>
|
||||
</footer>
|
||||
</sheet>
|
||||
|
||||
@@ -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
@@ -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 -->
|
||||
|
||||
@@ -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': [
|
||||
|
||||
@@ -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=[
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
23
sf_sale/views/purchase_menu.xml
Normal file
23
sf_sale/views/purchase_menu.xml
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
@@ -6,6 +6,14 @@
|
||||
<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 +43,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': ['|','&',('check_status', '!=', 'approved'),('state', 'in', ['draft','cancel']),'&','&',('check_status', '=', 'approved'),('state', 'in', ['sale','cancel']),('delivery_status', '!=', False)]}"/>
|
||||
attrs="{'invisible': ['|', ('state', 'in', ['cancel']), '|','&',('check_status', '!=', 'approved'),('state', 'in', ['draft','cancel']),'&','&',('check_status', '=', 'approved'),('state', 'in', ['sale','cancel']),('delivery_status', '!=', False)]}"/>
|
||||
</xpath>
|
||||
<xpath expr="//form/header/button[@name='action_cancel']" position="attributes">
|
||||
<attribute name="attrs">{'invisible': ['|','&',('check_status', '!=', 'approved'),('state',
|
||||
'in', ['draft','cancel']),'&','&',('check_status', '=', 'approved'),('state', 'in',
|
||||
['sale','cancel']),('delivery_status', '!=', False)]}
|
||||
<attribute name="attrs">{'invisible': ['|', ('state', 'in', ['cancel']), '|','&',
|
||||
('check_status', '!=', 'approved'),('state', 'in', ['draft','cancel']),'&','&',('check_status',
|
||||
'=', 'approved'),('state', 'in', ['sale','cancel']),('delivery_status', '!=', False)]}
|
||||
</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//form/header/button[@name='action_draft']" position="attributes">
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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']:
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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/>
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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,11 @@ 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)
|
||||
logging.info('货架已获取信息:%s' % item)
|
||||
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,8 +121,16 @@ class MrsShelfLocationDataSync(models.Model):
|
||||
tool.tool_in_out_stock_location(location_id)
|
||||
if tool:
|
||||
location_id.product_sn_id = tool.barcode_id.id
|
||||
# 修改功能刀具状态
|
||||
if item.get('State') == '报警':
|
||||
if tool.functional_tool_status != item.get('State'):
|
||||
tool.write({
|
||||
'functional_tool_status': item['State']
|
||||
})
|
||||
else:
|
||||
location_id.product_sn_id = False
|
||||
if item['RfidCode']:
|
||||
logging.info('Rfid为【%s】的功能刀具在系统中不存在!' % item['RfidCode'])
|
||||
else:
|
||||
stock_lot_obj = self.env['stock.lot'].search([('rfid', '=', item['RfidCode'])], limit=1)
|
||||
if stock_lot_obj:
|
||||
@@ -124,7 +138,6 @@ class MrsShelfLocationDataSync(models.Model):
|
||||
else:
|
||||
location_id.product_sn_id = False
|
||||
|
||||
logging.info('货架已获取信息:%s' % item)
|
||||
except Exception as e:
|
||||
logging.info("捕获错误信息:%s" % e)
|
||||
raise ValidationError("数据错误导致同步失败,请联系管理员")
|
||||
|
||||
Reference in New Issue
Block a user