Merge branch 'refs/heads/develop' into feature/machining_accuracy_sync
This commit is contained in:
@@ -21,8 +21,8 @@ class WorkorderExceptionConroller(http.Controller):
|
||||
try:
|
||||
res = {'Succeed': True, 'ErrorCode': 0, 'Error': ''}
|
||||
datas = request.httprequest.data
|
||||
ret = json.loads(datas)['Datas']
|
||||
if not ret.get('RfidCode') or not ret.get('ErrorType'):
|
||||
ret = json.loads(datas)
|
||||
if not ret.get('RfidCode') or not ret.get('coding'):
|
||||
res = {'Succeed': False, 'ErrorCode': 400, 'Error': '参数错误'}
|
||||
return json.JSONEncoder().encode(res)
|
||||
|
||||
@@ -38,7 +38,7 @@ class WorkorderExceptionConroller(http.Controller):
|
||||
# 创建工单异常记录,关联工单
|
||||
request.env['jikimo.workorder.exception'].sudo().create({
|
||||
'workorder_id': workorder.id,
|
||||
'exception_code': ret.get('ErrorType'),
|
||||
'exception_code': ret.get('coding'),
|
||||
'exception_content': ret.get('Error', '')
|
||||
})
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<field name="urgency">urgent</field>
|
||||
<field name="content">### 生产线无功能刀具提醒
|
||||
单号:工单[{{workorder_id.production_id.name}}]({{url}})
|
||||
原因:生产线无加工程序用的{{function_tool_name}}名称功能刀具</field>
|
||||
原因:生产线无加工程序要用的功能刀具</field>
|
||||
</record>
|
||||
<record id="template_no_position_data" model="jikimo.message.template">
|
||||
<field name="name">工单无定位数据提醒</field>
|
||||
@@ -19,7 +19,7 @@
|
||||
<field name="bussiness_node_id" ref="bussiness_no_position_data"/>
|
||||
<field name="msgtype">markdown</field>
|
||||
<field name="urgency">urgent</field>
|
||||
<field name="content">### 生产线无功能刀具提醒
|
||||
<field name="content">### 工单无定位数据提醒
|
||||
单号:工单[{{workorder_id.production_id.name}}]({{url}})
|
||||
原因:无装夹定位测量数据</field>
|
||||
</record>
|
||||
|
||||
@@ -34,7 +34,7 @@ class JikimoWorkorderException(models.Model):
|
||||
rec.add_queue('无定位数据')
|
||||
elif rec.exception_code == 'YC0004':
|
||||
# 无FTP文件,调用cloud接口
|
||||
data = {'name': rec.workorder_id.programming_no, 'exception_code': 'YC0004'}
|
||||
data = {'name': rec.workorder_id.production_id.programming_no, 'exception_code': 'YC0004'}
|
||||
configsettings = self.env['res.config.settings'].sudo().get_values()
|
||||
config_header = Common.get_headers(self, configsettings['token'], configsettings['sf_secret_key'])
|
||||
url = '/api/message/workorder_exception'
|
||||
|
||||
@@ -14,12 +14,25 @@ class TestJikimoWorkorderExceptionNotify(TestJikimoWorkorderExceptionNotifyCommo
|
||||
('model', '=', 'jikimo.workorder.exception')
|
||||
]))
|
||||
self.assertTrue(self.env['jikimo.message.template'].search([
|
||||
('name', '=', '加工失败'),
|
||||
('name', '=', '工单加工失败提醒'),
|
||||
('model', '=', 'jikimo.workorder.exception')
|
||||
]))
|
||||
|
||||
def test_create_message_queue_yc0001(self):
|
||||
exception_record = self.env['jikimo.workorder.exception'].create({
|
||||
'workorder_id': self.workorder.id,
|
||||
'exception_code': 'YC0001',
|
||||
'exception_content': '无CNC程序'
|
||||
})
|
||||
|
||||
def test_create_message_queue(self):
|
||||
message_record = self.env['jikimo.message.queue'].search([
|
||||
('res_id', '=', exception_record.id),
|
||||
('model', '=', 'jikimo.workorder.exception'),
|
||||
('message_status', '=', 'pending')
|
||||
])
|
||||
self.assertFalse(message_record)
|
||||
|
||||
def test_create_message_queue_yc0002(self):
|
||||
exception_record = self.env['jikimo.workorder.exception'].create({
|
||||
'workorder_id': self.workorder.id,
|
||||
'exception_code': 'YC0002',
|
||||
@@ -43,7 +56,45 @@ class TestJikimoWorkorderExceptionNotify(TestJikimoWorkorderExceptionNotifyCommo
|
||||
('message_template_id', '=', message_template.id)
|
||||
])
|
||||
self.assertTrue(message_record)
|
||||
|
||||
def test_create_message_queue_yc0003(self):
|
||||
exception_record = self.env['jikimo.workorder.exception'].create({
|
||||
'workorder_id': self.workorder.id,
|
||||
'exception_code': 'YC0003',
|
||||
'exception_content': '无定位数据'
|
||||
})
|
||||
|
||||
bussiness_node = self.env['jikimo.message.bussiness.node'].search([
|
||||
('name', '=', '无定位数据'),
|
||||
('model', '=', 'jikimo.workorder.exception')
|
||||
])
|
||||
|
||||
message_template = self.env['jikimo.message.template'].search([
|
||||
('bussiness_node_id', '=', bussiness_node.id),
|
||||
('model', '=', 'jikimo.workorder.exception')
|
||||
])
|
||||
|
||||
message_record = self.env['jikimo.message.queue'].search([
|
||||
('res_id', '=', exception_record.id),
|
||||
('model', '=', 'jikimo.workorder.exception'),
|
||||
('message_status', '=', 'pending'),
|
||||
('message_template_id', '=', message_template.id)
|
||||
])
|
||||
self.assertTrue(message_record)
|
||||
|
||||
def test_create_message_queue_yc0004(self):
|
||||
exception_record = self.env['jikimo.workorder.exception'].create({
|
||||
'workorder_id': self.workorder.id,
|
||||
'exception_code': 'YC0004',
|
||||
'exception_content': '无CNC程序'
|
||||
})
|
||||
|
||||
message_record = self.env['jikimo.message.queue'].search([
|
||||
('res_id', '=', exception_record.id),
|
||||
('model', '=', 'jikimo.workorder.exception'),
|
||||
('message_status', '=', 'pending')
|
||||
])
|
||||
self.assertFalse(message_record)
|
||||
|
||||
def test_get_message(self):
|
||||
exception_record = self.env['jikimo.workorder.exception'].create({
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
<field name='is_bfm' invisible="1"/>
|
||||
<field name='categ_type' invisible="1"/>
|
||||
<field name='part_number' attrs="{'invisible': [('categ_type', '!=', '成品')]}"/>
|
||||
<!-- <field name='machining_drawings' attrs="{'invisible': [('categ_type', '!=', '成品')]}" widget="image"/>-->
|
||||
<!-- <field name='quality_standard' attrs="{'invisible': [('categ_type', '!=', '成品')]}"/>-->
|
||||
<field name='manual_quotation' attrs="{'invisible':[('upload_model_file', '=', [])]}"/>
|
||||
<field name="upload_model_file"
|
||||
widget="many2many_binary"
|
||||
@@ -111,6 +109,19 @@
|
||||
'刀具')], 'required': True}
|
||||
</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//sheet//notebook" position="inside">
|
||||
<page string="2D加工图纸">
|
||||
<field name='machining_drawings' attrs="{'invisible': [('categ_type', '!=', '成品')]}"
|
||||
widget="adaptive_viewer"/>
|
||||
</page>
|
||||
</xpath>
|
||||
<xpath expr="//sheet//notebook" position="inside">
|
||||
<page string="质检标准">
|
||||
<field name='quality_standard' attrs="{'invisible': [('categ_type', '!=', '成品')]}"
|
||||
widget="adaptive_viewer"/>
|
||||
|
||||
</page>
|
||||
</xpath>
|
||||
<!-- <xpath expr="//field[@name='default_code']" position="attributes">-->
|
||||
<!-- <attribute name="attrs">{'readonly': [('categ_type', '=', '刀具')], 'invisible':-->
|
||||
<!-- [('product_variant_count', '>' , 1)]}-->
|
||||
|
||||
@@ -1272,6 +1272,7 @@ class Sf_Dashboard_Connect(http.Controller):
|
||||
time_threshold = datetime.now() - timedelta(days=1)
|
||||
|
||||
alarm_last_24_time = 0.0
|
||||
alarm_all_time = 0.0
|
||||
|
||||
def fetch_result_as_dict(cursor):
|
||||
"""辅助函数:将查询结果转为字典"""
|
||||
@@ -1332,6 +1333,35 @@ class Sf_Dashboard_Connect(http.Controller):
|
||||
alarm_last_24_time += float(result[0])
|
||||
else:
|
||||
alarm_last_24_time += 0.0
|
||||
|
||||
alarm_all_nums = []
|
||||
with conn.cursor() as cur:
|
||||
cur.execute("""
|
||||
SELECT DISTINCT ON (alarm_start_time) alarm_time, alarm_start_time
|
||||
FROM device_data
|
||||
WHERE device_name = %s
|
||||
AND alarm_start_time IS NOT NULL;
|
||||
""", (item,))
|
||||
results = cur.fetchall()
|
||||
for result in results:
|
||||
alarm_all_nums.append(result[1])
|
||||
if result[0]:
|
||||
if float(result[0]) >= 1000:
|
||||
continue
|
||||
alarm_all_time += float(result[0])
|
||||
else:
|
||||
alarm_all_time += 0.0
|
||||
|
||||
# with conn.cursor() as cur:
|
||||
# cur.execute("""
|
||||
# SELECT * FROM device_data
|
||||
# WHERE device_name = %s
|
||||
# AND total_count IS NOT NULL
|
||||
# ORDER BY time ASC
|
||||
# LIMIT 1;
|
||||
# """, (item, ))
|
||||
# total_count = fetch_result_as_dict(cur)
|
||||
|
||||
# 返回数据
|
||||
res['data'][item] = {
|
||||
'wait_time': last_all_time['run_time'] if last_all_time['run_time'] is not None else 0,
|
||||
@@ -1343,6 +1373,9 @@ class Sf_Dashboard_Connect(http.Controller):
|
||||
'alarm_last_24_nums': len(list(set(alarm_last_24_nums))),
|
||||
'idle_count': idle_count,
|
||||
'first_online_time': first_online_duration,
|
||||
'alarm_all_time': alarm_all_time,
|
||||
'alarm_all_nums': len(list(set(alarm_all_nums)))
|
||||
# 'total_count': total_count['total_count'] if total_count else 0
|
||||
}
|
||||
|
||||
conn.close()
|
||||
|
||||
@@ -2,3 +2,4 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import models
|
||||
from . import wizard
|
||||
|
||||
@@ -11,12 +11,14 @@
|
||||
'security/group_security.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'security/ir_rule_data.xml',
|
||||
'data/scheduled_actions.xml',
|
||||
'views/maintenance_logs_views.xml',
|
||||
'views/maintenance_equipment_oee_views.xml',
|
||||
'views/maintenance_views.xml',
|
||||
'views/equipment_maintenance_standards_views.xml',
|
||||
'views/maintenance_request_views.xml',
|
||||
'views/maintenance_equipment_category_views.xml',
|
||||
'wizard/maintenance_request_wizard.xml',
|
||||
],
|
||||
'installable': True,
|
||||
'application': False,
|
||||
|
||||
14
sf_maintenance/data/scheduled_actions.xml
Normal file
14
sf_maintenance/data/scheduled_actions.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<record id="ir_cron_oee_get_running_datas" model="ir.cron">
|
||||
<field name="name">设备运行数据</field>
|
||||
<field name="model_id" ref="model_maintenance_equipment_oee"/>
|
||||
<field name="state">code</field>
|
||||
<field name="code">model.get_running_datas()</field>
|
||||
<field name="interval_number">15</field>
|
||||
<field name="interval_type">minutes</field>
|
||||
<field name="numbercall">-1</field>
|
||||
<field name="active" eval="True"/>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -1,11 +1,12 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import json
|
||||
import base64
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
import requests
|
||||
from odoo.addons.sf_base.commons.common import Common
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
|
||||
|
||||
class SfMaintenanceEquipmentCategory(models.Model):
|
||||
@@ -122,6 +123,13 @@ class SfMaintenanceEquipment(models.Model):
|
||||
'sf_maintenance_equipment_ids', string='设备维保标准')
|
||||
eq_maintenance_id = fields.Many2one('equipment.maintenance.standards', string='设备保养标准',
|
||||
domain="[('maintenance_type','=','保养')]")
|
||||
|
||||
initial_action_date = fields.Date(string='重置保养日期')
|
||||
initial_action_date_old = fields.Date(string='重置保养日期(旧)')
|
||||
next_action_date = fields.Date(string='下次预防保养')
|
||||
|
||||
initial_overhaul_date = fields.Date(string='重置维修日期')
|
||||
initial_overhaul_date_old = fields.Date(string='重置维修日期(旧)')
|
||||
overhaul_date = fields.Date(string='下次预防检修')
|
||||
overhaul_period = fields.Integer(string='预防检修频次')
|
||||
overhaul_duration = fields.Float(string='检修时长')
|
||||
@@ -129,6 +137,61 @@ class SfMaintenanceEquipment(models.Model):
|
||||
overhaul_id = fields.Many2one('equipment.maintenance.standards', string='设备检修标准',
|
||||
domain="[('maintenance_type','=','检修')]")
|
||||
|
||||
def confirm_maintenance(self):
|
||||
"""
|
||||
确认保养/检修
|
||||
"""
|
||||
context = self.env.context
|
||||
if context['type'] == '保养':
|
||||
if not self.initial_action_date:
|
||||
raise ValidationError('重置保养日期不能为空!!')
|
||||
elif self.initial_action_date < fields.Date.today():
|
||||
raise ValidationError('重置保养日期不能小于当前日期!!')
|
||||
elif context['type'] == '检修':
|
||||
if not self.initial_overhaul_date:
|
||||
raise ValidationError('重置检修日期不能为空!!')
|
||||
elif self.initial_overhaul_date < fields.Date.today():
|
||||
raise ValidationError('重置检修日期不能小于当前日期!!')
|
||||
|
||||
request_ids = self.env['maintenance.request'].search([('stage_id.done', '=', False),
|
||||
('equipment_id', '=', self.id),
|
||||
('maintenance_type', '=', 'preventive'),
|
||||
('sf_maintenance_type', '=', context['type'])])
|
||||
|
||||
if not request_ids:
|
||||
return self.create_maintenance_request(context['type'])
|
||||
else:
|
||||
return {
|
||||
"type": "ir.actions.act_window",
|
||||
"res_model": "maintenance.request.wizard",
|
||||
"views": [[False, "form"]],
|
||||
"target": "new",
|
||||
'context': {
|
||||
'equipment_id': self.id
|
||||
}
|
||||
}
|
||||
|
||||
def create_maintenance_request(self, maintenance_request_type):
|
||||
"""
|
||||
根据条件创建维保计划
|
||||
"""
|
||||
if maintenance_request_type == '保养':
|
||||
self._create_new_request(self.initial_action_date + timedelta(days=self.period))
|
||||
self.initial_action_date_old = self.initial_action_date
|
||||
elif maintenance_request_type == '检修':
|
||||
self._create_new_request1(self.initial_overhaul_date + timedelta(days=self.overhaul_period))
|
||||
self.initial_overhaul_date_old = self.initial_overhaul_date
|
||||
return {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'display_notification',
|
||||
'params': {
|
||||
'title': f'创建{maintenance_request_type}计划',
|
||||
'message': f'新{maintenance_request_type}维保计划创建成功',
|
||||
'type': 'success',
|
||||
'next': {'type': 'ir.actions.act_window_close'},
|
||||
}
|
||||
}
|
||||
|
||||
@api.onchange('eq_maintenance_id', 'overhaul_id')
|
||||
def _compute_equipment_maintenance_standards_ids(self):
|
||||
for record in self:
|
||||
@@ -591,11 +654,13 @@ class SfMaintenanceEquipment(models.Model):
|
||||
('equipment_id', '=', equipment.id),
|
||||
('sf_maintenance_type', '=', '保养'),
|
||||
('stage_id.done', '!=', True),
|
||||
('active', '!=', False),
|
||||
('close_date', '=', False)], order="request_date asc", limit=1)
|
||||
last_maintenance_done = self.env['maintenance.request'].search([
|
||||
('equipment_id', '=', equipment.id),
|
||||
('sf_maintenance_type', '=', '保养'),
|
||||
('stage_id.done', '=', True),
|
||||
('active', '!=', False),
|
||||
('close_date', '!=', False)], order="close_date desc", limit=1)
|
||||
if next_maintenance_todo and last_maintenance_done:
|
||||
next_date = next_maintenance_todo.request_date
|
||||
@@ -624,7 +689,7 @@ class SfMaintenanceEquipment(models.Model):
|
||||
if next_date < date_now:
|
||||
next_date = date_now
|
||||
else:
|
||||
next_date = equipment.effective_date + timedelta(days=equipment.period)
|
||||
next_date = equipment.initial_action_date + timedelta(days=equipment.period)
|
||||
equipment.next_action_date = next_date
|
||||
else:
|
||||
self.next_action_date = False
|
||||
@@ -635,11 +700,13 @@ class SfMaintenanceEquipment(models.Model):
|
||||
('equipment_id', '=', equipment.id),
|
||||
('sf_maintenance_type', '=', '检修'),
|
||||
('stage_id.done', '!=', True),
|
||||
('active', '!=', False),
|
||||
('close_date', '=', False)], order="request_date asc", limit=1)
|
||||
last_maintenance_done = self.env['maintenance.request'].search([
|
||||
('equipment_id', '=', equipment.id),
|
||||
('sf_maintenance_type', '=', '检修'),
|
||||
('stage_id.done', '=', True),
|
||||
('active', '!=', False),
|
||||
('close_date', '!=', False)], order="close_date desc", limit=1)
|
||||
if next_maintenance_todo and last_maintenance_done:
|
||||
next_date = next_maintenance_todo.request_date
|
||||
@@ -668,7 +735,7 @@ class SfMaintenanceEquipment(models.Model):
|
||||
if next_date < date_now:
|
||||
next_date = date_now
|
||||
else:
|
||||
next_date = equipment.effective_date + timedelta(days=equipment.overhaul_period)
|
||||
next_date = equipment.initial_overhaul_date + timedelta(days=equipment.overhaul_period)
|
||||
equipment.overhaul_date = next_date
|
||||
else:
|
||||
self.overhaul_date = False
|
||||
@@ -735,6 +802,7 @@ class SfMaintenanceEquipment(models.Model):
|
||||
next_requests = self.env['maintenance.request'].search([('stage_id.done', '=', False),
|
||||
('equipment_id', '=', equipment.id),
|
||||
('maintenance_type', '=', 'preventive'),
|
||||
('active', '=', True),
|
||||
('request_date', '=', equipment.next_action_date),
|
||||
('sf_maintenance_type', '=', '保养')])
|
||||
if not next_requests:
|
||||
@@ -743,6 +811,7 @@ class SfMaintenanceEquipment(models.Model):
|
||||
next_requests = self.env['maintenance.request'].search([('stage_id.done', '=', False),
|
||||
('equipment_id', '=', equipment.id),
|
||||
('maintenance_type', '=', 'preventive'),
|
||||
('active', '=', True),
|
||||
('request_date', '=', equipment.overhaul_date),
|
||||
('sf_maintenance_type', '=', '检修')])
|
||||
if not next_requests:
|
||||
|
||||
@@ -88,6 +88,69 @@ class SfMaintenanceEquipmentOEE(models.Model):
|
||||
begin_time = fields.Date('开始时间')
|
||||
end_time = fields.Date('结束时间')
|
||||
|
||||
def get_running_datas(self):
|
||||
base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
|
||||
url_time = base_url + '/api/RunningTimeDetail'
|
||||
cnc_list_obj = self.env['maintenance.equipment'].sudo().search(
|
||||
[('function_type', '!=', False), ('active', '=', True)])
|
||||
machine_list = list(map(lambda x: x.code, cnc_list_obj))
|
||||
# print('machine_list: %s' % machine_list)
|
||||
|
||||
data_time = {
|
||||
"machine_list": str(machine_list)
|
||||
}
|
||||
# 发送POST请求
|
||||
response_time = requests.post(url_time, json={}, data=data_time)
|
||||
# print(response_time.json())
|
||||
if response_time.status_code == 200:
|
||||
result_time = response_time.json()
|
||||
if result_time['status'] == 1:
|
||||
real_dict = result_time['data']
|
||||
for key in real_dict:
|
||||
# print(key)
|
||||
equipment_obj = self.env['maintenance.equipment.oee'].sudo().search([('equipment_code', '=', key)])
|
||||
if real_dict[key]['power_on_time'] == 0:
|
||||
equipment_obj.online_time = 0
|
||||
equipment_obj.idle_time = 0
|
||||
equipment_obj.idle_rate = 0
|
||||
equipment_obj.work_rate = 0
|
||||
equipment_obj.fault_time = 0
|
||||
equipment_obj.fault_rate = 0
|
||||
equipment_obj.fault_nums = 0
|
||||
equipment_obj.idle_nums = 0
|
||||
equipment_obj.work_time = 0
|
||||
else:
|
||||
equipment_obj.online_time = round(convert_to_seconds(real_dict[key]['power_on_time']) / 3600, 2)
|
||||
equipment_obj.work_time = round(convert_to_seconds(real_dict[key]['cut_time']) / 3600, 2)
|
||||
equipment_obj.fault_nums = real_dict[key]['alarm_all_nums']
|
||||
equipment_obj.idle_nums = real_dict[key]['idle_count']
|
||||
equipment_obj.fault_time = round((float(real_dict[key]['alarm_all_time']) if real_dict[key][
|
||||
'alarm_all_time'] else 0) / 3600, 2)
|
||||
equipment_obj.idle_time = float(equipment_obj.online_time) - float(
|
||||
equipment_obj.work_time) if equipment_obj.online_time and equipment_obj.work_time else 0
|
||||
equipment_obj.idle_rate = round(
|
||||
float(equipment_obj.idle_time) / (
|
||||
float(equipment_obj.online_time) if equipment_obj.online_time else 1) * 100, 2)
|
||||
equipment_obj.work_rate = round(
|
||||
float(equipment_obj.work_time) / (
|
||||
float(equipment_obj.online_time) if equipment_obj.online_time else 1) * 100, 2)
|
||||
|
||||
equipment_obj.fault_rate = round(
|
||||
float(equipment_obj.fault_time) / (
|
||||
float(equipment_obj.online_time) if equipment_obj.online_time else 1) * 100, 2)
|
||||
|
||||
# 获取当前时间的时间戳
|
||||
current_timestamp = datetime.datetime.now().timestamp()
|
||||
# 机床上线时间段
|
||||
first_online_duration = current_timestamp - int(equipment_obj.equipment_id.first_online_time.timestamp())
|
||||
|
||||
if equipment_obj.online_time:
|
||||
equipment_obj.offline_time = round((first_online_duration - float(equipment_obj.online_time)) / 3600, 2)
|
||||
else:
|
||||
equipment_obj.offline_time = False
|
||||
# equipment_obj.offline_time = equipment_obj.equipment_id.first_online_time - (
|
||||
# float(equipment_obj.online_time) if equipment_obj.online_time else 0)
|
||||
|
||||
# 获取日志详情
|
||||
def get_day_logs(self):
|
||||
base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
|
||||
@@ -150,12 +213,11 @@ class SfMaintenanceEquipmentOEE(models.Model):
|
||||
self.fault_nums = real_dict['alarm_last_24_nums']
|
||||
self.idle_nums = real_dict['idle_count']
|
||||
self.work_time = round(
|
||||
(convert_to_seconds(real_dict['cut_time']) - convert_to_seconds(real_dict['cut_24_time'])) / 3600,
|
||||
(convert_to_seconds(real_dict['cut_time']) - convert_to_seconds(
|
||||
real_dict['cut_24_time'])) / 3600,
|
||||
2)
|
||||
self.offline_time = 24 - (float(self.online_time) if self.online_time else 0)
|
||||
|
||||
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
print('============', result)
|
||||
|
||||
@@ -14,6 +14,8 @@ class SfMaintenanceEquipmentCategory(models.Model):
|
||||
equipment_maintenance_id = fields.Many2one('equipment.maintenance.standards', string='设备维保标准',
|
||||
domain="[('maintenance_type','=',sf_maintenance_type)]")
|
||||
|
||||
active = fields.Boolean('有效', default=True)
|
||||
|
||||
@api.onchange('sf_maintenance_type')
|
||||
def _compute_equipment_maintenance_request_id(self):
|
||||
for record in self:
|
||||
|
||||
@@ -20,7 +20,8 @@ access_maintenance_equipment_agv_log,maintenance_equipment_agv_log,model_mainten
|
||||
|
||||
|
||||
|
||||
access_maintenance_system_user,equipment.request system user,maintenance.model_maintenance_request,base.group_user,1,0,0,0
|
||||
access_maintenance_system_user,equipment.request system user,maintenance.model_maintenance_request,base.group_user,1,1,1,0
|
||||
access_maintenance_wizard_system_user,maintenance.request.wizard system user,model_maintenance_request_wizard,base.group_user,1,1,1,0
|
||||
|
||||
access_maintenance_equipment_group_plan_dispatch,maintenance.equipment,maintenance.model_maintenance_equipment,sf_base.group_plan_dispatch,1,0,0,0
|
||||
access_maintenance_equipment_oee_group_plan_dispatch,maintenance_equipment_oee,model_maintenance_equipment_oee,sf_base.group_plan_dispatch,1,0,0,0
|
||||
|
||||
|
@@ -79,12 +79,12 @@
|
||||
</group>
|
||||
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="24H日志详情">
|
||||
<group>
|
||||
<button name="get_day_logs" type="object" string="查看24H日志" t-attf-style="white-space:nowrap;"/>
|
||||
</group>
|
||||
<field name="day_logs_detail" readonly="1" widget="html"/>
|
||||
<!-- <notebook> -->
|
||||
<!-- <page string="24H日志详情"> -->
|
||||
<!-- <group> -->
|
||||
<!-- <button name="get_day_logs" type="object" string="查看24H日志" t-attf-style="white-space:nowrap;"/> -->
|
||||
<!-- </group> -->
|
||||
<!-- <field name="day_logs_detail" readonly="1" widget="html"/> -->
|
||||
<!-- <field name="page_num"/> -->
|
||||
<!-- <group> -->
|
||||
<!-- <group> -->
|
||||
@@ -109,7 +109,7 @@
|
||||
<!-- </div> -->
|
||||
<!-- </div> -->
|
||||
<!-- </group> -->
|
||||
</page>
|
||||
<!-- </page> -->
|
||||
<!-- <page string="历史日志详情"> -->
|
||||
<!-- <group> -->
|
||||
<!-- <group> -->
|
||||
@@ -132,7 +132,7 @@
|
||||
<!-- </group> -->
|
||||
<!-- <field name="history_logs_detail"/> -->
|
||||
<!-- </page> -->
|
||||
</notebook>
|
||||
<!-- </notebook> -->
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
|
||||
@@ -60,9 +60,9 @@
|
||||
<field name="function_type"/>
|
||||
<field name="code" readonly="1"/>
|
||||
<field name="equipment_type" invisible="1"/>
|
||||
<field name="brand_id" force_save="1" />
|
||||
<field name="brand_id" force_save="1"/>
|
||||
<field name="type_id" attrs="{'required': [('equipment_type', '=', '机床')]}"
|
||||
domain="[('brand_id', '=', brand_id)]" />
|
||||
domain="[('brand_id', '=', brand_id)]"/>
|
||||
<field name="machine_tool_category" readonly="1" attrs="{'invisible': [('type_id', '=', False)]}"
|
||||
force_save="1"/>
|
||||
<field name="run_time" force_save="1"/>
|
||||
@@ -73,7 +73,7 @@
|
||||
<group>
|
||||
<group string="基础参数">
|
||||
<field name="control_system_id" attrs="{'required': [('equipment_type', '=', '机床')]}"
|
||||
options="{'no_create': True}" />
|
||||
options="{'no_create': True}"/>
|
||||
<label for="workbench_L" string="工作台尺寸(mm)"/>
|
||||
<div class="test_model">
|
||||
<label for="workbench_L" string="长"/>
|
||||
@@ -85,7 +85,7 @@
|
||||
<field name="workbench_W" class="o_address_zip"
|
||||
attrs="{'required': [('equipment_type', '=', '机床')]}"
|
||||
options="{'format': false}"/>
|
||||
<span>&nbsp;</span>
|
||||
<span>&nbsp;</span>
|
||||
<label for="workbench_H" string="高"/>
|
||||
<field name="workbench_H" class="o_address_zip"
|
||||
attrs="{'required': [('equipment_type', '=', '机床')]}"
|
||||
@@ -134,7 +134,7 @@
|
||||
<!-- <field name="guide_rail" required="1"/>-->
|
||||
<field name="number_of_axles" attrs="{'required': [('equipment_type', '=', '机床')]}"
|
||||
widget="radio"
|
||||
options="{'horizontal': true}" />
|
||||
options="{'horizontal': true}"/>
|
||||
<label for="x_axis" string="加工行程(mm)"
|
||||
attrs="{'invisible': [('number_of_axles', '=', False)]}"/>
|
||||
<div class="test_model"
|
||||
@@ -196,8 +196,8 @@
|
||||
<field name="T_tool_time"/>
|
||||
<field name="C_tool_time"/>
|
||||
</group>
|
||||
<group string="主轴">
|
||||
<field name="taper_type_id" attrs="{'required': [('equipment_type', '=', '机床')]}" />
|
||||
<group string="主轴">
|
||||
<field name="taper_type_id" attrs="{'required': [('equipment_type', '=', '机床')]}"/>
|
||||
<label for="distance_min" string="主轴端面-工作台距离(mm)"/>
|
||||
<div class="test_model">
|
||||
<label for="distance_min" string="最小(min)"/>
|
||||
@@ -237,7 +237,7 @@
|
||||
<field name="c_precision"/>
|
||||
<field name="c_precision_repeat"/>
|
||||
</group>
|
||||
<group string="进给参数">
|
||||
<group string="进给参数">
|
||||
<field name="X_axis_rapid_traverse_speed"/>
|
||||
<field name="Y_axis_rapid_traverse_speed"/>
|
||||
<field name="Z_axis_rapid_traverse_speed"/>
|
||||
@@ -252,21 +252,21 @@
|
||||
|
||||
|
||||
</page>
|
||||
<page string="AGV运行日志" name="sf_equipment"
|
||||
<page string="AGV运行日志" name="sf_equipment"
|
||||
attrs="{'invisible': [('equipment_type', '!=', 'AGV小车')]}">
|
||||
<field name="agv_logs">
|
||||
<tree create="1" edit="1" delete="1" editable="bottom">
|
||||
<field name = 'run_type'/>
|
||||
<field name = 'run_code'/>
|
||||
<field name = 'run_first'/>
|
||||
<field name = 'run_last'/>
|
||||
<field name = 'production_line'/>
|
||||
<field name = 'workorder'/>
|
||||
<field name = 'time'/>
|
||||
<field name = 'state'/>
|
||||
</tree>
|
||||
</field>
|
||||
</page>
|
||||
<field name="agv_logs">
|
||||
<tree create="1" edit="1" delete="1" editable="bottom">
|
||||
<field name='run_type'/>
|
||||
<field name='run_code'/>
|
||||
<field name='run_first'/>
|
||||
<field name='run_last'/>
|
||||
<field name='production_line'/>
|
||||
<field name='workorder'/>
|
||||
<field name='time'/>
|
||||
<field name='state'/>
|
||||
</tree>
|
||||
</field>
|
||||
</page>
|
||||
|
||||
<page string="设备参数" name="sf_equipment"
|
||||
attrs="{'invisible': [('equipment_type', '!=', 'AGV小车')]}">
|
||||
@@ -979,31 +979,58 @@
|
||||
</group>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//field[@name='next_action_date']" position="before">
|
||||
|
||||
<field name='eq_maintenance_id' force_save="1" widget="many2one"/>
|
||||
<xpath expr="//page[@name='maintenance']" position="replace">
|
||||
<page string="维保" name="maintenance">
|
||||
<group>
|
||||
<group string="保养">
|
||||
<field name='eq_maintenance_id' force_save="1" widget="many2one"/>
|
||||
<field name="initial_action_date"/>
|
||||
<field name="next_action_date" string="下次预防保养"/>
|
||||
<label for="period" string="预防保养频次"/>
|
||||
<div class="o_row">
|
||||
<field name="period"/>
|
||||
days
|
||||
</div>
|
||||
<label for="maintenance_duration" string="保养时长"/>
|
||||
<div class="o_row">
|
||||
<field name="maintenance_duration"/>
|
||||
hours
|
||||
</div>
|
||||
<div class="col-12 col-lg-6 o_setting_box" style="white-space: nowrap">
|
||||
<button name="confirm_maintenance" string="确认保养" type="object"
|
||||
class="oe_highlight" context="{'type': '保养'}"/>
|
||||
</div>
|
||||
</group>
|
||||
<group string="检修">
|
||||
<field name='overhaul_id'/>
|
||||
<field name="initial_overhaul_date"/>
|
||||
<field name="overhaul_date" string="下次预防检修"/>
|
||||
<label for="overhaul_period" string="预防检修频次"/>
|
||||
<div class="o_row">
|
||||
<field name="overhaul_period"/>
|
||||
days
|
||||
</div>
|
||||
|
||||
<label for="overhaul_duration" string="检修时长"/>
|
||||
<div class="o_row">
|
||||
<field name="overhaul_duration"/>
|
||||
hours
|
||||
</div>
|
||||
<field name='equipment_maintenance_standards_ids' widget="many2many_tags"
|
||||
invisible="1"/>
|
||||
|
||||
<div class="col-12 col-lg-6 o_setting_box" style="white-space: nowrap">
|
||||
<button name="confirm_maintenance" string="确认检修" type="object"
|
||||
class="oe_highlight" context="{'type': '检修'}"/>
|
||||
</div>
|
||||
</group>
|
||||
</group>
|
||||
|
||||
|
||||
</page>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//div[hasclass('o_row')][field[@name='maintenance_duration']]" position="after">
|
||||
|
||||
|
||||
<field name='overhaul_id' options="{'no_create':True}"/>
|
||||
<field name="overhaul_date" string="下次预防检修"/>
|
||||
<label for="overhaul_period" string="预防检修频次"/>
|
||||
<div class="o_row">
|
||||
<field name="overhaul_period"/>
|
||||
days
|
||||
</div>
|
||||
|
||||
<label for="overhaul_duration" string="检修时长"/>
|
||||
<div class="o_row">
|
||||
<field name="overhaul_duration"/>
|
||||
hours
|
||||
</div>
|
||||
<field name='equipment_maintenance_standards_ids' widget="many2many_tags" invisible="1"/>
|
||||
|
||||
</xpath>
|
||||
<xpath expr="//page[@name='description']" position="attributes">
|
||||
<attribute name="invisible">1</attribute>
|
||||
</xpath>
|
||||
@@ -1189,7 +1216,7 @@
|
||||
<field name="name" readonly="1"/>
|
||||
<field name="type" readonly="1"/>
|
||||
<field name="image" widget="image" readonly="1"/>
|
||||
<!-- <field name="equipment_id"/>-->
|
||||
<!-- <field name="equipment_id"/>-->
|
||||
<field name="active" invisible="1"/>
|
||||
</tree>
|
||||
</field>
|
||||
|
||||
1
sf_maintenance/wizard/__init__.py
Normal file
1
sf_maintenance/wizard/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import maintenance_request_wizard
|
||||
26
sf_maintenance/wizard/maintenance_request_wizard.py
Normal file
26
sf_maintenance/wizard/maintenance_request_wizard.py
Normal file
@@ -0,0 +1,26 @@
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class MaintenanceRequestWizard(models.TransientModel):
|
||||
_name = 'maintenance.request.wizard'
|
||||
_description = '维保二次确认弹窗'
|
||||
|
||||
name = fields.Char('')
|
||||
|
||||
def submit(self):
|
||||
context = self.env.context
|
||||
equipment_id = self.env['maintenance.equipment'].sudo().search([('id', '=', context['equipment_id'])])
|
||||
request_ids = self.env['maintenance.request'].search([('stage_id.done', '=', False),
|
||||
('equipment_id', '=', equipment_id.id),
|
||||
('maintenance_type', '=', 'preventive'),
|
||||
('sf_maintenance_type', '=', context['type'])])
|
||||
request_ids.write({'active': False})
|
||||
return equipment_id.create_maintenance_request(context['type'])
|
||||
|
||||
def cancel(self):
|
||||
context = self.env.context
|
||||
equipment_id = self.env['maintenance.equipment'].sudo().search([('id', '=', context['equipment_id'])])
|
||||
if context['type'] == '保养':
|
||||
equipment_id.initial_action_date = equipment_id.initial_action_date_old
|
||||
elif context['type'] == '检修':
|
||||
equipment_id.initial_overhaul_date = equipment_id.initial_overhaul_date_old
|
||||
29
sf_maintenance/wizard/maintenance_request_wizard.xml
Normal file
29
sf_maintenance/wizard/maintenance_request_wizard.xml
Normal file
@@ -0,0 +1,29 @@
|
||||
<openerp>
|
||||
<data>
|
||||
<record id="action_maintenance_request_wizard" model="ir.actions.act_window">
|
||||
<field name="name">维保计划</field>
|
||||
<field name="res_model">maintenance.request.wizard</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="maintenance_request_wizard_form_view">
|
||||
<field name="name">maintenance.request.wizard.form.view</field>
|
||||
<field name="model">maintenance.request.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<div>
|
||||
<field name="name" invisible="1"/>
|
||||
有未执行的历史维保计划,是否创建新维保计划!!
|
||||
</div>
|
||||
<footer>
|
||||
<button string="确认" name="submit" type="object" class="oe_highlight"/>
|
||||
<button string="取消" name="cancel" type="object" class="oe_highlight"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
@@ -18,7 +18,7 @@ class MrpProduction(models.Model):
|
||||
_inherit = 'mrp.production'
|
||||
_description = "制造订单"
|
||||
_order = 'create_date desc'
|
||||
|
||||
deadline_of_delivery = fields.Date('订单交期', tracking=True, compute='_compute_deadline_of_delivery')
|
||||
# tray_ids = fields.One2many('sf.tray', 'production_id', string="托盘")
|
||||
maintenance_count = fields.Integer(compute='_compute_maintenance_count', string="Number of maintenance requests")
|
||||
request_ids = fields.One2many('maintenance.request', 'production_id')
|
||||
@@ -34,6 +34,29 @@ class MrpProduction(models.Model):
|
||||
tool_state_remark = fields.Text(string='功能刀具状态备注(缺刀)', compute='_compute_tool_state_remark', store=True)
|
||||
tool_state_remark2 = fields.Text(string='功能刀具状态备注(无效刀)', readonly=True)
|
||||
|
||||
@api.depends('procurement_group_id.mrp_production_ids.move_dest_ids.group_id.sale_id')
|
||||
def _compute_deadline_of_delivery(self):
|
||||
for production in self:
|
||||
# 确保 procurement_group_id 和相关字段存在
|
||||
if production.procurement_group_id:
|
||||
# 获取相关的 sale_id
|
||||
sale_order_id = production.procurement_group_id.mrp_production_ids.mapped(
|
||||
'move_dest_ids.group_id.sale_id')
|
||||
|
||||
# 确保 sale_order_id 是有效的 ID 列表
|
||||
if sale_order_id:
|
||||
# 获取 sale.order 记录
|
||||
sale_id = self.env['sale.order'].sudo().browse(sale_order_id.ids) # 使用 mapped 返回的 ID 列表
|
||||
|
||||
# 处理 sale_id
|
||||
if sale_id:
|
||||
# 假设我们只需要第一个 sale_id
|
||||
production.deadline_of_delivery = sale_id[0].deadline_of_delivery if sale_id else False
|
||||
else:
|
||||
production.deadline_of_delivery = False
|
||||
else:
|
||||
production.deadline_of_delivery = False
|
||||
|
||||
@api.depends('workorder_ids.tool_state_remark')
|
||||
def _compute_tool_state_remark(self):
|
||||
for item in self:
|
||||
@@ -118,10 +141,12 @@ class MrpProduction(models.Model):
|
||||
], string='工序状态', default='待装夹')
|
||||
|
||||
# 零件图号
|
||||
part_number = fields.Char('零件图号')
|
||||
part_number = fields.Char('零件图号', readonly=True)
|
||||
|
||||
# 上传零件图纸
|
||||
part_drawing = fields.Binary('零件图纸')
|
||||
part_drawing = fields.Binary('零件图纸', readonly=True)
|
||||
|
||||
quality_standard = fields.Binary('质检标准', readonly=True)
|
||||
|
||||
@api.depends('product_id.manual_quotation')
|
||||
def _compute_manual_quotation(self):
|
||||
@@ -948,6 +973,8 @@ class MrpProduction(models.Model):
|
||||
if production.programming_no in program_to_production_names:
|
||||
productions_not_delivered = self.env['mrp.production'].search(
|
||||
[('programming_no', '=', production.programming_no), ('programming_state', '=', '已编程未下发')])
|
||||
productions = self.env['mrp.production'].search(
|
||||
[('programming_no', '=', production.programming_no), ('state', 'not in', ('cancel', 'done'))])
|
||||
rework_workorder = production.workorder_ids.filtered(lambda m: m.state == 'rework')
|
||||
if rework_workorder:
|
||||
for rework_item in rework_workorder:
|
||||
@@ -960,12 +987,12 @@ class MrpProduction(models.Model):
|
||||
productions_not_delivered.write(
|
||||
{'state': 'progress', 'programming_state': '已编程', 'is_rework': False})
|
||||
|
||||
# 对制造订单所以面的cnc工单的程序用刀进行校验
|
||||
try:
|
||||
logging.info(f'已更新制造订单:{productions_not_delivered}')
|
||||
productions_not_delivered.production_cnc_tool_checkout()
|
||||
except Exception as e:
|
||||
logging.info(f'对cnc工单的程序用刀进行校验报错:{e}')
|
||||
# 对制造订单所以面的cnc工单的程序用刀进行校验
|
||||
try:
|
||||
logging.info(f'已更新制造订单:{productions_not_delivered}')
|
||||
productions.production_cnc_tool_checkout()
|
||||
except Exception as e:
|
||||
logging.info(f'对cnc工单的程序用刀进行校验报错:{e}')
|
||||
|
||||
# 从cloud获取重新编程过的最新程序
|
||||
def get_new_program(self, processing_panel):
|
||||
|
||||
@@ -225,6 +225,9 @@ class ResMrpWorkOrder(models.Model):
|
||||
material_height = fields.Float(string='高')
|
||||
# 零件图号
|
||||
part_number = fields.Char(related='production_id.part_number', string='零件图号')
|
||||
machining_drawings = fields.Binary('2D加工图纸', related='production_id.part_drawing', readonly=True)
|
||||
quality_standard = fields.Binary('质检标准', related='production_id.quality_standard', readonly=True)
|
||||
|
||||
# 工序状态
|
||||
process_state = fields.Selection([
|
||||
('待装夹', '待装夹'),
|
||||
@@ -1048,6 +1051,17 @@ class ResMrpWorkOrder(models.Model):
|
||||
if workorder.production_id.tool_state in ['1', '2'] and workorder.state == 'ready':
|
||||
workorder.state = 'waiting'
|
||||
continue
|
||||
if (workorder.production_id.tool_state in ['1', '2']
|
||||
and not workorder.production_id.workorder_ids.filtered(lambda a: a.sequence == 0)
|
||||
and workorder.production_id.programming_state == '编程中' and workorder.name == '装夹预调'):
|
||||
if workorder.state == 'pending' and workorder == self.search(
|
||||
[('production_id', '=', workorder.production_id.id),
|
||||
('routing_type', '=', '装夹预调'),
|
||||
('state', 'not in', ['rework', 'done', 'cancel'])],
|
||||
limit=1,
|
||||
order="sequence"):
|
||||
workorder.state = 'waiting'
|
||||
continue
|
||||
|
||||
# elif workorder.routing_type == 'CNC加工' and workorder.state not in ['done', 'cancel', 'progress',
|
||||
# 'rework']:
|
||||
|
||||
@@ -775,8 +775,10 @@ class ResProductMo(models.Model):
|
||||
# bfm下单
|
||||
manual_quotation = fields.Boolean('人工编程', default=False, readonly=True)
|
||||
part_number = fields.Char(string='零件图号', readonly=True)
|
||||
# machining_drawings = fields.Binary('2D加工图纸', readonly=True)
|
||||
# quality_standard = fields.Binary('质检标准', readonly=True)
|
||||
machining_drawings = fields.Binary('2D加工图纸', readonly=True)
|
||||
quality_standard = fields.Binary('质检标准', readonly=True)
|
||||
machining_drawings_name = fields.Char('2D加工图纸名', readonly=True)
|
||||
quality_standard_name = fields.Char('质检标准名', readonly=True)
|
||||
|
||||
@api.constrains('tool_length')
|
||||
def _check_tool_length_size(self):
|
||||
@@ -838,6 +840,11 @@ class ResProductMo(models.Model):
|
||||
else:
|
||||
return self.env.ref('sf_dlm.product_uom_cubic_millimeter')
|
||||
|
||||
def attachment_update(self, name, res_id, res_field):
|
||||
attachment_info = self.env['ir.attachment'].sudo().search(
|
||||
[('res_id', '=', res_id), ('res_field', '=', res_field)], limit=1)
|
||||
attachment_info.write({'name': name})
|
||||
|
||||
# 业务平台分配工厂后在智能工厂先创建销售订单再创建该产品
|
||||
def product_create(self, product_id, item, order_id, order_number, i):
|
||||
copy_product_id = product_id.with_user(self.env.ref("base.user_admin")).copy()
|
||||
@@ -876,8 +883,11 @@ class ResProductMo(models.Model):
|
||||
'manual_quotation': item['manual_quotation'] or False,
|
||||
'part_number': item.get('part_number') or '',
|
||||
'active': True,
|
||||
# 'machining_drawings': '' if not item['machining_drawings'] else base64.b64decode(item['machining_drawings']),
|
||||
# 'quality_standard': '' if not item['quality_standard'] else base64.b64decode(item['quality_standard']),
|
||||
'machining_drawings_name': item['machining_drawings_name'],
|
||||
'quality_standard_name': item['quality_standard_name'],
|
||||
'machining_drawings': '' if not item['machining_drawings'] else base64.b64decode(
|
||||
item['machining_drawings']),
|
||||
'quality_standard': '' if not item['quality_standard'] else base64.b64decode(item['quality_standard']),
|
||||
}
|
||||
tax_id = self.env['account.tax'].sudo().search(
|
||||
[('type_tax_use', '=', 'sale'), ('amount', '=', item.get('tax')), ('price_include', '=', 'True')])
|
||||
@@ -885,6 +895,12 @@ class ResProductMo(models.Model):
|
||||
vals.update({'taxes_id': [(6, 0, [int(tax_id)])]})
|
||||
copy_product_id.sudo().write(vals)
|
||||
product_id.product_tmpl_id.active = False
|
||||
if item['machining_drawings'] and item['machining_drawings_name']:
|
||||
self.attachment_update(item['machining_drawings_name'], copy_product_id.product_tmpl_id.id,
|
||||
'machining_drawings')
|
||||
if item['quality_standard'] and item['quality_standard_name']:
|
||||
self.attachment_update(item['quality_standard_name'], copy_product_id.product_tmpl_id.id,
|
||||
'quality_standard')
|
||||
return copy_product_id
|
||||
|
||||
def _get_ids(self, param):
|
||||
|
||||
@@ -182,6 +182,11 @@ class StockRule(models.Model):
|
||||
moves._action_confirm()
|
||||
return True
|
||||
|
||||
def attachment_update(self, name, res_id, res_field):
|
||||
attachment_info = self.env['ir.attachment'].sudo().search(
|
||||
[('res_id', '=', res_id), ('res_field', '=', res_field)], limit=1)
|
||||
attachment_info.write({'name': name})
|
||||
|
||||
@api.model
|
||||
def _run_manufacture(self, procurements):
|
||||
productions_values_by_company = defaultdict(list)
|
||||
@@ -272,6 +277,16 @@ class StockRule(models.Model):
|
||||
if quick_easy_order:
|
||||
production.write({'part_number': quick_easy_order.part_drawing_number,
|
||||
'part_drawing': quick_easy_order.machining_drawings})
|
||||
else:
|
||||
production.write({'part_number': production.product_id.part_number,
|
||||
'part_drawing': production.product_id.machining_drawings,
|
||||
'quality_standard': production.product_id.quality_standard})
|
||||
if production.product_id.machining_drawings and production.product_id.machining_drawings_name:
|
||||
self.attachment_update(production.product_id.machining_drawings_name, production.id,
|
||||
'part_drawing')
|
||||
if production.product_id.quality_standard and production.product_id.quality_standard_name:
|
||||
self.attachment_update(production.product_id.quality_standard_name, production.id,
|
||||
'quality_standard')
|
||||
if sale_order:
|
||||
# sale_order.write({'schedule_status': 'to schedule'})
|
||||
self.env['sf.production.plan'].sudo().with_company(company_id).create({
|
||||
|
||||
@@ -98,9 +98,9 @@
|
||||
<field name="production_line_id" readonly="1"/>
|
||||
<!-- <field name="production_line_state" readonly="1"/>-->
|
||||
<field name="part_number" string="成品的零件图号"/>
|
||||
<field name="part_drawing"/>
|
||||
<field name="tool_state"/>
|
||||
<field name="tool_state_remark" string="备注" attrs="{'invisible': [('tool_state', '!=', '1')]}"/>
|
||||
<field name="deadline_of_delivery" readonly="1"/>
|
||||
<field name="tool_state_remark2" invisible="1"/>
|
||||
</xpath>
|
||||
<xpath expr="//header//button[@name='action_cancel']" position="replace">
|
||||
@@ -328,6 +328,16 @@
|
||||
<xpath expr="//field[@name='components_availability']" position="attributes">
|
||||
<attribute name="string">投料状态</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//sheet//notebook" position="inside">
|
||||
<page string="零件图纸">
|
||||
<field name="part_drawing" widget="adaptive_viewer"/>
|
||||
</page>
|
||||
</xpath>
|
||||
<xpath expr="//sheet//notebook" position="inside">
|
||||
<page string="质检标准">
|
||||
<field name="quality_standard" widget="adaptive_viewer"/>
|
||||
</page>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
@@ -589,7 +589,18 @@
|
||||
mrp.group_mrp_manager,sf_base.group_sf_mrp_manager,sf_base.group_sf_equipment_user,sf_base.group_sf_order_user
|
||||
</attribute>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//sheet//notebook" position="inside">
|
||||
<page string="2D加工图纸">
|
||||
<field name="machining_drawings" widget="adaptive_viewer"
|
||||
attrs="{'invisible': [('routing_type', '!=', 'CNC加工')]}"/>
|
||||
</page>
|
||||
</xpath>
|
||||
<xpath expr="//sheet//notebook" position="inside">
|
||||
<page string="质检标准">
|
||||
<field name="quality_standard" widget="adaptive_viewer"
|
||||
attrs="{'invisible': [('routing_type', '!=', 'CNC加工')]}"/>
|
||||
</page>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
'data/cron_data.xml',
|
||||
'data/template_data.xml',
|
||||
'security/ir.model.access.csv',
|
||||
|
||||
'views/mrp_workorder_views.xml',
|
||||
],
|
||||
'test': [
|
||||
],
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from odoo import models, fields, api
|
||||
|
||||
class SFMessageMaintenanceLogs(models.Model):
|
||||
_name = 'sf.maintenance.logs'
|
||||
_inherit = ['sf.maintenance.logs', 'jikimo.message.dispatch']
|
||||
|
||||
@api._model_create_multi
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
res = super(SFMessageMaintenanceLogs, self).create(vals_list)
|
||||
for rec in res:
|
||||
rec.add_queue()
|
||||
rec.add_queue('设备故障')
|
||||
return res
|
||||
|
||||
def _get_message(self, message_queue_ids):
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import logging
|
||||
import re
|
||||
from odoo import models, fields, api, _
|
||||
from urllib.parse import urlencode
|
||||
@@ -35,19 +36,20 @@ class SFMessageMrpProduction(models.Model):
|
||||
[('origin', '=', mrp_production.origin), ('picking_type_id.sequence_code', '=', 'SFP'),
|
||||
('state', '=', 'assigned')], limit=1)
|
||||
if stock_picking_sfp:
|
||||
url = self.request_url()
|
||||
url = self.request_url(stock_picking_sfp.id)
|
||||
content = content.replace('{{name}}', stock_picking_sfp.name).replace(
|
||||
'{{sale_order_name}}', mrp_production.origin).replace('{{request_url}}', url)
|
||||
contents.append(content)
|
||||
logging.info('生产完工入库提醒: %s' % contents)
|
||||
return contents
|
||||
|
||||
def request_url(self):
|
||||
def request_url(self, id):
|
||||
url = self.env['ir.config_parameter'].get_param('web.base.url')
|
||||
action_id = self.env.ref('mrp.mrp_production_action').id
|
||||
action_id = self.env.ref('stock.action_picking_tree_all').id
|
||||
menu_id = self.env['ir.model.data'].search([('name', '=', 'module_theme_treehouse')]).id
|
||||
# 查询参数
|
||||
params = {'menu_id': menu_id, 'action': action_id, 'model': 'mrp.production',
|
||||
'view_type': 'kanban'}
|
||||
params = {'id': id, 'menu_id': menu_id, 'action': action_id, 'model': 'mrp.production',
|
||||
'view_type': 'form'}
|
||||
# 拼接查询参数
|
||||
query_string = urlencode(params)
|
||||
# 拼接URL
|
||||
|
||||
@@ -103,39 +103,58 @@ class SFMessageSale(models.Model):
|
||||
today = datetime.today().date()
|
||||
deadline_check = today + timedelta(days=1)
|
||||
logging.info(f"today: {today}, deadline_check: {deadline_check}")
|
||||
sale_order = self.sudo().search([('state', 'in', ['sale']), ('deadline_of_delivery', '!=', False)])
|
||||
sale_order = self.sudo().search(
|
||||
[('state', 'in', ['sale']), ('deadline_of_delivery', '!=', False), ('delivery_status', '!=', 'full')])
|
||||
for item in sale_order:
|
||||
production = self.env['mrp.production'].search([('origin', '=', item.name)])
|
||||
production_not_done = production.filtered(lambda p: p.state not in ['done', 'scrap', 'cancel'])
|
||||
production_done_count = len(production.filtered(lambda p: p.state in ['done', 'scrap', 'cancel']))
|
||||
if len(production_not_done) != item.mrp_production_count:
|
||||
if deadline_check == item.deadline_of_delivery:
|
||||
if (len(production_not_done) >= 1 and len(production_not_done) != item.mrp_production_count) or len(
|
||||
production_not_done) != production_done_count:
|
||||
logging.info("-----不等于----")
|
||||
logging.info(f"name: {item.name}")
|
||||
logging.info(
|
||||
f"production_not_done: {len(production_not_done)}, production_done_count: {production_done_count}")
|
||||
logging.info(f"deadline_of_delivery: {item.deadline_of_delivery}")
|
||||
if deadline_check == item.deadline_of_delivery and item.delivery_warning not in ['warning']:
|
||||
item.delivery_warning = 'warning'
|
||||
elif today == item.deadline_of_delivery:
|
||||
elif today >= item.deadline_of_delivery and item.delivery_warning not in ['overdue']:
|
||||
item.delivery_warning = 'overdue'
|
||||
elif production_done_count == item.mrp_production_count:
|
||||
logging.info("-----等于----")
|
||||
logging.info(f"name: {item.name}")
|
||||
logging.info(
|
||||
f"production_not_done: {len(production_not_done)}, production_done_count: {production_done_count}")
|
||||
logging.info(f"deadline_of_delivery: {item.deadline_of_delivery}")
|
||||
if item.delivery_status in ['pending', 'partial']:
|
||||
if deadline_check == item.deadline_of_delivery:
|
||||
if deadline_check == item.deadline_of_delivery and item.delivery_warning not in ['warning']:
|
||||
item.delivery_warning = 'warning'
|
||||
elif today == item.deadline_of_delivery:
|
||||
elif today >= item.deadline_of_delivery and item.delivery_warning not in ['overdue']:
|
||||
item.delivery_warning = 'overdue'
|
||||
else:
|
||||
logging.info("-----1111111----")
|
||||
logging.info(f"name: {item.name}")
|
||||
logging.info(
|
||||
f"production_not_done: {len(production_not_done)}, production_done_count: {production_done_count}")
|
||||
continue
|
||||
# 获取业务节点
|
||||
business_node_ids = {
|
||||
'warning': self.env.ref('sf_message.bussiness_sale_order_overdue_warning').id,
|
||||
'overdue': self.env.ref('sf_message.bussiness_sale_order_overdue').id
|
||||
}
|
||||
overdue_orders = self.sudo().search([('delivery_warning', 'in', ['warning', 'overdue'])])
|
||||
for wo in overdue_orders:
|
||||
message_template = self.env["jikimo.message.template"].search([
|
||||
("model", "=", self._name),
|
||||
("bussiness_node_id", "=", self.env.ref('sf_message.bussiness_sale_order_overdue_warning').id)
|
||||
])
|
||||
sale_order_has = self.env['jikimo.message.queue'].search([
|
||||
('res_id', '=', wo.id),
|
||||
('message_status', '=', 'pending'),
|
||||
('message_template_id', '=', message_template.id)
|
||||
])
|
||||
if not sale_order_has:
|
||||
if wo.delivery_warning == 'warning':
|
||||
wo.add_queue('销售订单逾期预警')
|
||||
elif wo.delivery_warning == 'overdue':
|
||||
wo.add_queue('销售订单已逾期')
|
||||
|
||||
|
||||
business_node_id = business_node_ids.get(wo.delivery_warning)
|
||||
if business_node_id:
|
||||
message_template = self.env["jikimo.message.template"].search([
|
||||
("model", "=", self._name),
|
||||
("bussiness_node_id", "=", business_node_id)
|
||||
], limit=1)
|
||||
sale_order_has = self.env['jikimo.message.queue'].search([
|
||||
('res_id', '=', wo.id),
|
||||
('message_status', '=', 'pending'),
|
||||
('message_template_id', '=', message_template.id)
|
||||
])
|
||||
if not sale_order_has:
|
||||
message_name = '销售订单逾期预警' if wo.delivery_warning == 'warning' else '销售订单已逾期'
|
||||
wo.add_queue(message_name)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import logging
|
||||
import re
|
||||
from odoo import models, fields, api, _
|
||||
from urllib.parse import urlencode
|
||||
@@ -40,9 +41,10 @@ class SFMessageStockPicking(models.Model):
|
||||
('picking_type_id.sequence_code', '=', 'OUT')])
|
||||
if stock_picking_out and len(stock_picking_out) > 0:
|
||||
content = message_queue_id.message_template_id.content
|
||||
url = self.request_url()
|
||||
url = self.request_url1(stock_picking_out.id)
|
||||
content = content.replace('{{name}}', stock_picking_out.name).replace(
|
||||
'{{sale_order_name}}', stock_picking_out.origin).replace('{{request_url}}', url)
|
||||
logging.info('订单发货提醒: %s' % content)
|
||||
return content
|
||||
|
||||
def _get_message(self, message_queue_ids):
|
||||
@@ -96,3 +98,16 @@ class SFMessageStockPicking(models.Model):
|
||||
# 拼接URL
|
||||
full_url = url + "/web#" + query_string
|
||||
return full_url
|
||||
|
||||
def request_url1(self, id):
|
||||
url = self.env['ir.config_parameter'].get_param('web.base.url')
|
||||
action_id = self.env.ref('stock.action_picking_tree_all').id
|
||||
menu_id = self.env['ir.model.data'].search([('name', '=', 'module_theme_treehouse')]).id
|
||||
# 查询参数
|
||||
params = {'id': id, 'menu_id': menu_id, 'action': action_id, 'model': 'stock.picking',
|
||||
'view_type': 'form'}
|
||||
# 拼接查询参数
|
||||
query_string = urlencode(params)
|
||||
# 拼接URL
|
||||
full_url = url + "/web#" + query_string
|
||||
return full_url
|
||||
|
||||
@@ -73,7 +73,7 @@ class SFMessageWork(models.Model):
|
||||
if record:
|
||||
i += 1
|
||||
if i >= 1:
|
||||
action_id = self.env.ref('sf_manufacturing.mrp_workorder_action_tablet').id
|
||||
action_id = self.env.ref('sf_message.mrp_workorder_action_notify').id
|
||||
url_with_id = f"{url}/web#view_type=list&action={action_id}"
|
||||
content_template = content.replace('{{url}}', url_with_id)
|
||||
if bussiness_node in template_names['预警']:
|
||||
@@ -85,7 +85,7 @@ class SFMessageWork(models.Model):
|
||||
|
||||
def request_url(self):
|
||||
url = self.env['ir.config_parameter'].get_param('web.base.url')
|
||||
action_id = self.env.ref('sf_manufacturing.mrp_workorder_action_tablet').id
|
||||
action_id = self.env.ref('sf_message.mrp_workorder_action_notify').id
|
||||
menu_id = self.env['ir.model.data'].search([('name', '=', 'module_stock_dropshipping')]).id
|
||||
# 查询参数
|
||||
params = {'menu_id': menu_id, 'action': action_id, 'model': 'mrp.workorder',
|
||||
@@ -115,12 +115,13 @@ class SFMessageWork(models.Model):
|
||||
item.date_planned_finished.strftime("%Y-%m-%d %H:%M:%S"))
|
||||
date_planned_finished = datetime.strptime(date_planned_finished_str, '%Y-%m-%d %H:%M:%S')
|
||||
twelve_hours_ago = current_time_datetime - timedelta(hours=12)
|
||||
if current_time_datetime >= date_planned_finished:
|
||||
if current_time_datetime >= date_planned_finished and item.delivery_warning not in ['overdue']:
|
||||
logging.info("------overdue-------")
|
||||
logging.info(f"Workorder: {item.production_id.name}, Current Time: {current_time_datetime}, "
|
||||
f"Planned Finish: {date_planned_finished}")
|
||||
item.delivery_warning = 'overdue'
|
||||
elif twelve_hours_ago <= current_time_datetime <= date_planned_finished:
|
||||
elif twelve_hours_ago <= current_time_datetime <= date_planned_finished and item.delivery_warning not in [
|
||||
'warning']:
|
||||
logging.info("------warning-------")
|
||||
logging.info(f"Workorder: {item.production_id.name}, Current Time: {current_time_datetime}, "
|
||||
f"Planned Finish: {date_planned_finished}")
|
||||
@@ -153,6 +154,6 @@ class SFMessageWork(models.Model):
|
||||
getattr(item, queue_method_name)(*args)
|
||||
|
||||
def _recover_time_warning_func(self):
|
||||
workorder_done = self.env['mrp.workorder'].search([("state", "=", "done")])
|
||||
workorder_done = self.env['mrp.workorder'].search([("state", "in", ["done", "rework", "cancel"])])
|
||||
workorder_overdue = workorder_done.filtered(lambda x: x.delivery_warning in ['overdue', 'warning'])
|
||||
workorder_overdue.write({'delivery_warning': 'normal'})
|
||||
|
||||
34
sf_message/views/mrp_workorder_views.xml
Normal file
34
sf_message/views/mrp_workorder_views.xml
Normal file
@@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record model="ir.actions.act_window" id="mrp_workorder_action_notify">
|
||||
<field name="name">工单</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">mrp.workorder</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="view_ids" eval="[(5, 0, 0),
|
||||
(0, 0, {'view_mode': 'tree', 'view_id': ref('mrp.mrp_production_workorder_tree_editable_view')}) ]"/>
|
||||
<!-- (0, 0, {'view_mode': 'kanban', 'view_id': ref('mrp.workcenter_line_kanban')})-->
|
||||
<!-- <field name="target">fullscreen</field>-->
|
||||
<field name="target">current</field>
|
||||
<field name="domain">[('state', '!=', 'cancel'),('schedule_state', '=', '已排')]</field>
|
||||
<field name="context">{'search_default_product': 1, 'search_default_workcenter_id':
|
||||
active_id,'search_default_filter_order_warning':1,'search_default_filter_order_overdue':1,
|
||||
'search_default_ready': 1, 'search_default_progress': 1}
|
||||
</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_workorder">
|
||||
没有工单要做!
|
||||
</p>
|
||||
<p>
|
||||
工作订单是作为制造订单的一部分执行的操作。
|
||||
工序在物料清单中定义或直接添加到制造订单中。
|
||||
</p>
|
||||
<p>
|
||||
使用工作台工作中心控制面板直接登记车间中的操作.
|
||||
平板电脑为您的工人提供工作表,并允许他们报废产品,跟踪时间,
|
||||
发起维护请求,执行质量测试等.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
@@ -219,7 +219,7 @@ class sf_production_plan(models.Model):
|
||||
|
||||
return num
|
||||
|
||||
def do_production_schedule(self, count=1):
|
||||
def do_production_schedule(self):
|
||||
"""
|
||||
排程方法
|
||||
"""
|
||||
@@ -227,29 +227,26 @@ class sf_production_plan(models.Model):
|
||||
if not record.production_line_id:
|
||||
raise ValidationError("未选择生产线")
|
||||
else:
|
||||
is_schedule = self.deal_processing_schedule(record.date_planned_start, count)
|
||||
if not is_schedule:
|
||||
raise ValidationError("排程失败")
|
||||
workorder_id_list = record.production_id.workorder_ids.ids
|
||||
if record.production_id:
|
||||
if record.production_id.workorder_ids:
|
||||
for item in record.production_id.workorder_ids:
|
||||
if item.name == 'CNC加工':
|
||||
item.date_planned_finished = datetime.now() + timedelta(days=100)
|
||||
item.date_planned_start = self.date_planned_start if self.date_planned_start else datetime.now()
|
||||
record.sudo().production_id.plan_start_processing_time = item.date_planned_start
|
||||
item.date_planned_finished = item.date_planned_start + timedelta(
|
||||
minutes=record.env['mrp.routing.workcenter'].sudo().search(
|
||||
[('name', '=', 'CNC加工')]).time_cycle)
|
||||
item.duration_expected = record.env['mrp.routing.workcenter'].sudo().search(
|
||||
[('name', '=', 'CNC加工')]).time_cycle
|
||||
record.calculate_plan_time_before(item, workorder_id_list)
|
||||
record.calculate_plan_time_after(item, workorder_id_list)
|
||||
record.date_planned_start, record.date_planned_finished = \
|
||||
item.date_planned_start, item.date_planned_finished
|
||||
if record.production_id.workorder_ids:
|
||||
last_cnc_start = record.date_planned_start if record.date_planned_start else datetime.now()
|
||||
for item in record.production_id.workorder_ids:
|
||||
if item.name == 'CNC加工':
|
||||
# 将同一个面的所有工单筛选出来
|
||||
workorder_list = record.production_id.workorder_ids.filtered(lambda x: x.processing_panel == item.processing_panel)
|
||||
routing_workcenter = record.env['mrp.routing.workcenter'].sudo().search(
|
||||
[('name', '=', 'CNC加工')], limit=1)
|
||||
# 设置一个小的开始时间
|
||||
item.date_planned_start = datetime.now() - timedelta(days=100)
|
||||
item.date_planned_finished = last_cnc_start + timedelta(
|
||||
minutes=routing_workcenter.time_cycle)
|
||||
item.date_planned_start = last_cnc_start
|
||||
record.sudo().production_id.plan_start_processing_time = item.date_planned_start
|
||||
item.duration_expected = routing_workcenter.time_cycle
|
||||
pre_duration , next_duration = record.calculate_plan_time(item, workorder_list)
|
||||
record.date_planned_finished = item.date_planned_finished
|
||||
# 计算下一个cnc工单的开始时间
|
||||
last_cnc_start = workorder_list[-1].date_planned_finished + timedelta(minutes=pre_duration)
|
||||
record.state = 'done'
|
||||
record.date_planned_finished = record.date_planned_start + timedelta(
|
||||
minutes=60) if not record.date_planned_finished else record.date_planned_finished
|
||||
# record.production_id.schedule_state = '已排'
|
||||
record.sudo().production_id.schedule_state = '已排'
|
||||
record.sudo().production_id.process_state = '待装夹'
|
||||
@@ -282,54 +279,66 @@ class sf_production_plan(models.Model):
|
||||
}
|
||||
|
||||
# 处理是否可排程
|
||||
def deal_processing_schedule(self, date_planned_start, count):
|
||||
for record in self:
|
||||
workcenter_ids = record.production_line_id.mrp_workcenter_ids
|
||||
if not workcenter_ids:
|
||||
raise UserError('生产线没有配置工作中心')
|
||||
production_lines = workcenter_ids.filtered(lambda b: "自动生产线" in b.name)
|
||||
if not production_lines: # 判断是否配置了自动生产线
|
||||
raise UserError('生产线没有配置自动生产线')
|
||||
if date_planned_start < datetime.now(): # 判断计划开始时间是否小于当前时间
|
||||
raise UserError('计划开始时间不能小于当前时间')
|
||||
if all(not production_line.deal_with_workcenter_calendar(date_planned_start) for production_line in
|
||||
production_lines): # 判断计划开始时间是否在配置的工作中心的工作日历内
|
||||
raise UserError('当前计划开始时间不能预约排程,请在工作时间内排程')
|
||||
if not production_lines.deal_available_default_capacity(date_planned_start): # 判断生产线是否可排程
|
||||
raise UserError('当前计划开始时间不能预约排程,生产线今日没有可排程的资源')
|
||||
if not production_lines.deal_available_single_machine_capacity(date_planned_start, count): # 判断生产线是否可排程
|
||||
raise UserError('当前计划开始时间不能预约排程,生产线该时间段没有可排程的资源')
|
||||
def deal_processing_schedule(self, date_planned_start,):
|
||||
count = len(self)
|
||||
workcenter_ids = self.production_line_id.mrp_workcenter_ids
|
||||
if not workcenter_ids:
|
||||
raise UserError('生产线没有配置工作中心')
|
||||
production_lines = workcenter_ids.filtered(lambda b: "自动生产线" in b.name)
|
||||
if not production_lines: # 判断是否配置了自动生产线
|
||||
raise UserError('生产线没有配置自动生产线')
|
||||
if date_planned_start < datetime.now(): # 判断计划开始时间是否小于当前时间
|
||||
raise UserError('计划开始时间不能小于当前时间')
|
||||
if all(not production_line.deal_with_workcenter_calendar(date_planned_start) for production_line in
|
||||
production_lines): # 判断计划开始时间是否在配置的工作中心的工作日历内
|
||||
raise UserError('当前计划开始时间不能预约排程,请在工作时间内排程')
|
||||
if not production_lines.deal_available_default_capacity(date_planned_start): # 判断生产线是否可排程
|
||||
raise UserError('当前计划开始时间不能预约排程,生产线今日没有可排程的资源')
|
||||
if not production_lines.deal_available_single_machine_capacity(date_planned_start, count): # 判断生产线是否可排程
|
||||
raise UserError('当前计划开始时间不能预约排程,生产线该时间段没有可排程的资源')
|
||||
return True
|
||||
|
||||
def calculate_plan_time_before(self, item, workorder_id_list):
|
||||
def calculate_plan_time(self, item, workorder_list):
|
||||
"""
|
||||
根据CNC工单的时间去计算之前的其他工单的开始结束时间
|
||||
"""
|
||||
sequence = workorder_id_list.index(item.id) - 1
|
||||
# 计算CNC加工之前工单的开始结束时间
|
||||
for i in range(1 if sequence == 0 else sequence):
|
||||
current_workorder_id = (item.id - (i + 1))
|
||||
current_workorder_obj = self.env['mrp.workorder'].sudo().search(
|
||||
[('id', '=', current_workorder_id)])
|
||||
old_workorder_obj = self.env['mrp.workorder'].sudo().search(
|
||||
[('id', '=', (current_workorder_id + 1))])
|
||||
work_order = self.env['mrp.workorder'].sudo().search(
|
||||
[('production_id', '=', self.production_id.id), ('id', '=', current_workorder_id)])
|
||||
work_order.date_planned_finished = datetime.now() + timedelta(days=100)
|
||||
work_order.date_planned_start = old_workorder_obj.date_planned_start - timedelta(
|
||||
minutes=self.env['mrp.routing.workcenter'].sudo().search(
|
||||
[('name', '=', current_workorder_obj.name)]).time_cycle)
|
||||
work_order.date_planned_finished = old_workorder_obj.date_planned_start
|
||||
work_order.duration_expected = self.env['mrp.routing.workcenter'].sudo().search(
|
||||
[('name', '=', current_workorder_obj.name)]).time_cycle
|
||||
first_workorder = self.env['mrp.workorder'].sudo().search([('id', '=', workorder_id_list[0])])
|
||||
second_workorder = self.env['mrp.workorder'].sudo().search([('id', '=', workorder_id_list[1])])
|
||||
if second_workorder.date_planned_start < first_workorder.date_planned_finished:
|
||||
item.date_planned_start += timedelta(minutes=60)
|
||||
item.date_planned_finished += timedelta(minutes=60)
|
||||
item.duration_expected = self.env['mrp.routing.workcenter'].sudo().search(
|
||||
[('name', '=', 'CNC加工')]).time_cycle
|
||||
self.calculate_plan_time_before(item, workorder_id_list)
|
||||
item_position = 0
|
||||
for index, workorder in enumerate(workorder_list):
|
||||
if workorder.id == item.id:
|
||||
item_position = index
|
||||
break
|
||||
routing_workcenters = self.env['mrp.routing.workcenter'].sudo().search([])
|
||||
# 记录所有前序工序时长
|
||||
previous_workorder_duration = 0
|
||||
for i in range(item_position, -1, -1):
|
||||
if i < 1:
|
||||
break
|
||||
current_workorder = workorder_list[i]
|
||||
next_workorder = workorder_list[i - 1]
|
||||
routing_workcenter = routing_workcenters.filtered(lambda x: x.name == next_workorder.name)[0]
|
||||
# 设置一个小的开始时间
|
||||
next_workorder.date_planned_start = datetime.now() - timedelta(days=100)
|
||||
next_workorder.date_planned_finished = current_workorder.date_planned_start
|
||||
next_workorder.date_planned_start = next_workorder.date_planned_finished - timedelta(
|
||||
minutes=routing_workcenter.time_cycle)
|
||||
next_workorder.duration_expected = routing_workcenter.time_cycle
|
||||
previous_workorder_duration += routing_workcenter.time_cycle
|
||||
# 记录所有后续工序时长
|
||||
next_workorder_duration = 0
|
||||
for i in range(item_position, len(workorder_list) - 1):
|
||||
if i > len(workorder_list) - 1:
|
||||
break
|
||||
current_workorder = workorder_list[i]
|
||||
next_workorder = workorder_list[i + 1]
|
||||
routing_workcenter = routing_workcenters.filtered(lambda x: x.name == next_workorder.name)[0]
|
||||
# 设置一个小的开始时间
|
||||
next_workorder.date_planned_start = datetime.now() - timedelta(days=100)
|
||||
next_workorder.date_planned_finished = current_workorder.date_planned_finished + timedelta(
|
||||
minutes=routing_workcenter.time_cycle)
|
||||
next_workorder.date_planned_start = current_workorder.date_planned_finished
|
||||
next_workorder.duration_expected = routing_workcenter.time_cycle
|
||||
next_workorder_duration += routing_workcenter.time_cycle
|
||||
return previous_workorder_duration, next_workorder_duration
|
||||
|
||||
def calculate_plan_time_after(self, item, workorder_id_list):
|
||||
"""
|
||||
|
||||
@@ -31,22 +31,24 @@ class Action_Plan_All_Wizard(models.TransientModel):
|
||||
# 确认排程按钮
|
||||
def action_plan_all(self):
|
||||
# 使用传递过来的计划ID
|
||||
temp_plan_ids = self.plan_ids
|
||||
self.plan_ids.production_line_id = self.production_line_id.id
|
||||
self.plan_ids.date_planned_start = self.date_planned_start
|
||||
# 在这里添加您的逻辑来处理这些ID
|
||||
count = len(temp_plan_ids) + 1
|
||||
for plan in temp_plan_ids:
|
||||
count = count - 1
|
||||
# 处理每个计划
|
||||
# 比如更新计划状态、分配资源等
|
||||
# 示例:plan.state = 'scheduled'
|
||||
print('处理计划:', plan.id)
|
||||
# 拿到计划对象
|
||||
plan_obj = self.env['sf.production.plan'].browse(plan.id)
|
||||
plan_obj.production_line_id = self.production_line_id.id
|
||||
plan.date_planned_start = self.date_planned_start
|
||||
plan_obj.do_production_schedule(count)
|
||||
# 判断能否排成
|
||||
self.plan_ids.deal_processing_schedule(self.date_planned_start)
|
||||
self.plan_ids.do_production_schedule()
|
||||
# for plan in temp_plan_ids:
|
||||
# # 处理每个计划
|
||||
# # 比如更新计划状态、分配资源等
|
||||
# # 示例:plan.state = 'scheduled'
|
||||
# print('处理计划:', plan.id)
|
||||
# # 拿到计划对象
|
||||
# plan_obj = self.env['sf.production.plan'].browse(plan.id)
|
||||
# plan_obj.production_line_id = self.production_line_id.id
|
||||
# plan.date_planned_start = self.date_planned_start
|
||||
# plan_obj.do_production_schedule()
|
||||
# plan_obj.state = 'done'
|
||||
print('处理计划:', plan.id, '完成')
|
||||
_logger.info('处理计划: %s 完成', self.plan_ids.ids)
|
||||
|
||||
# # 获取当前生产线
|
||||
# production_line_id = self.production_line_id
|
||||
@@ -73,4 +75,14 @@ class Action_Plan_All_Wizard(models.TransientModel):
|
||||
# if all(production_order_plan_state == '已取消' for production_order_plan_state in production_order_plan_state_list):
|
||||
# raise UserError('当前生产线的所有生产订单都已取消,请勿重复排程!')
|
||||
# # 如果当前生产线的所有生产订单的排程状态都是已暂停,则报错
|
||||
# if all(production_order_plan_state == '已暂停' for production_order_plan_state in production
|
||||
# if all(production_order_plan_state == '已暂停' for production_order_plan_state in production_order_plan_state_list):
|
||||
# raise UserError('当前生产线的所有生产订单都已暂停,请勿重复排程!')
|
||||
# # 如果当前生产线的所有生产订单的排程状态都是已完成,则报错
|
||||
# if all(production_order_plan_state == '已完成' for production_order_plan_state in production_order_plan_state_list):
|
||||
# raise UserError('当前生产线的所有生产订单都已完成,请勿重复排程!')
|
||||
# # 如果当前生产线的所有生产订单的排程状态都是已取消,则报错
|
||||
# if all(production_order_plan_state == '已取消' for production_order_plan_state in production_order_plan_state_list):
|
||||
# raise UserError('当前生产线的所有生产订单都已取消,请勿重复排程!')
|
||||
# # 如果当前生产线的所有生产订单的排程状态都是已暂停,则报错
|
||||
# if all(production_order_plan_state == '已暂停' for production_order_plan_state in production_order_plan_state_list):
|
||||
# raise UserError('当前生产线的所有生产订单都已暂停,请勿重复排程!')
|
||||
|
||||
@@ -31,8 +31,8 @@ class SfQualityCncTest(models.Model):
|
||||
("technology", "工艺"), ("customer redrawing", "客户改图")], string="原因")
|
||||
detailed_reason = fields.Text('详细原因')
|
||||
|
||||
# machining_drawings = fields.Binary(related='workorder_id.machining_drawings', string='2D加工图纸', readonly=True)
|
||||
# quality_standard = fields.Binary(related='workorder_id.quality_standard', string='质检标准', readonly=True)
|
||||
machining_drawings = fields.Binary('2D加工图纸', related='workorder_id.machining_drawings', readonly=True)
|
||||
quality_standard = fields.Binary('质检标准', related='workorder_id.quality_standard', readonly=True)
|
||||
|
||||
def submit_pass(self):
|
||||
if self.test_results in ['返工', '报废']:
|
||||
|
||||
@@ -107,10 +107,10 @@
|
||||
</group>
|
||||
</page>
|
||||
<page string="2D图纸">
|
||||
<!-- <field name="machining_drawings" string="" widget="pdf_viewer"/>-->
|
||||
<field name="machining_drawings" string="" widget="adaptive_viewer"/>
|
||||
</page>
|
||||
<page string="客户质量标准">
|
||||
<!-- <field name="quality_standard" string=""/>-->
|
||||
<field name="quality_standard" string="" widget="adaptive_viewer"/>
|
||||
</page>
|
||||
<page string="其他" attrs="{'invisible': [('state','=', 'waiting')]}">
|
||||
<group>
|
||||
|
||||
@@ -57,7 +57,6 @@ class ReSaleOrder(models.Model):
|
||||
|
||||
delivery_warning = fields.Selection([('normal', '正常'), ('warning', '告警'), ('overdue', '逾期')], string='时效')
|
||||
|
||||
|
||||
# 业务平台分配工厂后在智能工厂先创建销售订单
|
||||
def sale_order_create(self, company_id, delivery_name, delivery_telephone, delivery_address,
|
||||
deadline_of_delivery, payments_way, pay_way):
|
||||
@@ -128,7 +127,9 @@ class ReSaleOrder(models.Model):
|
||||
'price_unit': product.list_price,
|
||||
'product_uom_qty': item['number'],
|
||||
'model_glb_file': base64.b64decode(item['model_file']),
|
||||
'remark': item.get('remark')
|
||||
'remark': item.get('remark'),
|
||||
'is_incoming_material': item.get('is_incoming_material'),
|
||||
'incoming_size': item.get('incoming_size'),
|
||||
}
|
||||
return self.env['sale.order.line'].with_context(skip_procurement=True).create(vals)
|
||||
|
||||
@@ -169,6 +170,9 @@ class ResaleOrderLine(models.Model):
|
||||
check_status = fields.Selection(related='order_id.check_status')
|
||||
remark = fields.Char('备注')
|
||||
|
||||
is_incoming_material = fields.Boolean('是否带料', default=False)
|
||||
incoming_size = fields.Char('带料尺寸')
|
||||
|
||||
@api.depends('product_template_id')
|
||||
def _compute_model_glb_file(self):
|
||||
for line in self:
|
||||
|
||||
@@ -95,7 +95,7 @@
|
||||
<attribute name="attrs">{'readonly': [('state', 'in', ['cancel','sale'])]}</attribute>
|
||||
</field>
|
||||
<field name="payment_term_id" position="after">
|
||||
<field name="deadline_of_delivery" readonly="1"/>
|
||||
<field name="deadline_of_delivery" readonly="0"/>
|
||||
<field name="payments_way" attrs="{'readonly': [('state', 'in', ('sale','cancel'))]}"/>
|
||||
<field name="pay_way" attrs="{'readonly': [('state', 'in', ('sale','cancel'))]}"/>
|
||||
<!-- <field name="schedule_status" readonly="1"/> -->
|
||||
@@ -118,6 +118,8 @@
|
||||
<xpath expr="//field[@name='order_line']/tree/field[@name='name']" position="replace">
|
||||
<field name="name" widget="section_and_note_text" optional="show"
|
||||
string="参数说明(长/宽/高/体积/精度/材质)"/>
|
||||
<field name="is_incoming_material" optional="hide"/>
|
||||
<field name="incoming_size" optional="hide"/>
|
||||
</xpath>
|
||||
<field name="user_id" position="attributes">
|
||||
<attribute name="attrs">{'readonly': [('state', 'in', ['cancel','sale'])]}</attribute>
|
||||
@@ -162,6 +164,11 @@
|
||||
<xpath expr="//button[@name='action_cancel']" position="attributes">
|
||||
<attribute name="string">拒绝接单</attribute>
|
||||
</xpath>
|
||||
<!--新增带料字段-->
|
||||
<xpath expr="//field[@name='order_line']/form//group//group//field[@name='analytic_distribution']" position="after">
|
||||
<field name="is_incoming_material"/>
|
||||
<field name="incoming_size" attrs="{'invisible': [('is_incoming_material', '=', False)],'readonly':1}"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -265,7 +272,6 @@
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="sale.product_template_action" model="ir.actions.act_window">
|
||||
<field name="context">{"search_default_categ_id":1,
|
||||
"search_default_filter_to_sell":1,"sale_multi_pricelist_product_template": 1}
|
||||
|
||||
@@ -51,6 +51,8 @@ class FunctionalCuttingToolEntity(models.Model):
|
||||
string='位置', compute='_compute_current_location_id', store=True)
|
||||
image = fields.Binary('图片', readonly=True)
|
||||
|
||||
stock_num = fields.Integer('库存变更次数', default=0)
|
||||
|
||||
safe_inventory_id = fields.Many2one('sf.real.time.distribution.of.functional.tools',
|
||||
string='功能刀具安全库存', readonly=True)
|
||||
|
||||
@@ -71,7 +73,7 @@ class FunctionalCuttingToolEntity(models.Model):
|
||||
})
|
||||
|
||||
@api.depends('barcode_id.quant_ids', 'barcode_id.quant_ids.location_id', 'functional_tool_status',
|
||||
'current_shelf_location_id')
|
||||
'current_shelf_location_id', 'stock_num')
|
||||
def _compute_current_location_id(self):
|
||||
for record in self:
|
||||
if record.functional_tool_status == '已拆除':
|
||||
|
||||
@@ -175,7 +175,9 @@ class MrpProduction(models.Model):
|
||||
cnc_ids = self.env['sf.cnc.processing'].sudo().search(
|
||||
[('workorder_id', 'in', workorder_ids.ids), ('cutting_tool_name', 'in', invalid_tool)])
|
||||
if cnc_ids:
|
||||
cnc_ids.write({'tool_state': '2'})
|
||||
for cnc_id in cnc_ids:
|
||||
cnc_id.tool_state = '2'
|
||||
# cnc_ids.write({'tool_state': '2'})
|
||||
# 创建制造订单无效刀检测结果记录
|
||||
for production_id in self:
|
||||
for processing_panel in list(set(invalid_tool_processing_panel)):
|
||||
@@ -204,7 +206,9 @@ class MrpProduction(models.Model):
|
||||
cnc_ids = self.env['sf.cnc.processing'].sudo().search(
|
||||
[('workorder_id', 'in', workorder_ids.ids), ('cutting_tool_name', 'in', missing_tool_1)])
|
||||
if cnc_ids:
|
||||
cnc_ids.write({'tool_state': '1'})
|
||||
for cnc_id in cnc_ids:
|
||||
cnc_id.tool_state = '1'
|
||||
# cnc_ids.write({'tool_state': '1'})
|
||||
if missing_tool_2 and invalid_tool == []:
|
||||
logging.info(f'库存缺刀:{missing_tool_2}')
|
||||
# 调用CAM工单程序用刀计划创建方法
|
||||
|
||||
@@ -53,6 +53,13 @@ class StockMoveLine(models.Model):
|
||||
[('barcode_id', '=', line_id.lot_id.id),
|
||||
('functional_tool_status', '=', '正常')]).cnc_function_tool_use_verify()
|
||||
|
||||
for move_line in move_lines:
|
||||
if move_line.lot_id:
|
||||
tool_id = self.env['sf.functional.cutting.tool.entity'].sudo().search(
|
||||
[('barcode_id', '=', move_line.lot_id.id),
|
||||
('functional_tool_status', '=', '正常')])
|
||||
tool_id.stock_num += tool_id.stock_num
|
||||
|
||||
|
||||
class StockPicking(models.Model):
|
||||
_inherit = 'stock.picking'
|
||||
|
||||
Reference in New Issue
Block a user