diff --git a/jikimo_frontend/static/src/js/custom_form_status_indicator.js b/jikimo_frontend/static/src/js/custom_form_status_indicator.js index 306142d4..912c8efa 100644 --- a/jikimo_frontend/static/src/js/custom_form_status_indicator.js +++ b/jikimo_frontend/static/src/js/custom_form_status_indicator.js @@ -5,7 +5,7 @@ import {patch} from '@web/core/utils/patch'; import {_t} from "@web/core/l10n/translation"; import {FormStatusIndicator} from "@web/views/form/form_status_indicator/form_status_indicator"; import {ListRenderer} from "@web/views/list/list_renderer"; -import {StatusBarField} from "@web/views/fields/statusbar/statusbar_field"; +// import {StatusBarField} from "@web/views/fields/statusbar/statusbar_field"; import {Field} from "@web/views/fields/field"; @@ -153,34 +153,34 @@ patch(ListRenderer.prototype, 'jikimo_frontend.ListRenderer', { // 根据进度条设置水印 -const statusbar_params = { - '已完工': 'bg-primary', - '完成': 'bg-primary', - '采购订单': 'bg-primary', - '作废': 'bg-danger', - '封存(报废)': 'bg-danger', -} -patch(StatusBarField.prototype, 'jikimo_frontend.StatusBarField', { - setup() { - owl.onMounted(this.ribbons); - return this._super(...arguments); - }, - ribbons() { - try { - const dom = $('.o_form_sheet.position-relative') - const status = statusbar_params[this.currentName] - if(status && dom.length) { - dom.prepend(`
-
- ${this.currentName} -
-
`) - } - } catch (e) { - console.log(e) - } - } -}) +// const statusbar_params = { +// '已完工': 'bg-primary', +// '完成': 'bg-primary', +// '采购订单': 'bg-primary', +// '作废': 'bg-danger', +// '封存(报废)': 'bg-danger', +// } +// patch(StatusBarField.prototype, 'jikimo_frontend.StatusBarField', { +// setup() { +// owl.onMounted(this.ribbons); +// return this._super(...arguments); +// }, +// ribbons() { +// try { +// const dom = $('.o_form_sheet.position-relative') +// const status = statusbar_params[this.currentName] +// if(status && dom.length) { +// dom.prepend(`
+//
+// ${this.currentName} +//
+//
`) +// } +// } catch (e) { +// console.log(e) +// } +// } +// }) $(function () { document.addEventListener('click', function () { diff --git a/jikimo_frontend/static/src/scss/custom_style.scss b/jikimo_frontend/static/src/scss/custom_style.scss index 0b6fb8bb..d7e6414c 100644 --- a/jikimo_frontend/static/src/scss/custom_style.scss +++ b/jikimo_frontend/static/src/scss/custom_style.scss @@ -530,4 +530,5 @@ div:has(.o_required_modifier) > label::before { // 修复表格内容覆盖表头bug .o_list_renderer .o_list_table tbody th { position: unset; -} \ No newline at end of file +} + diff --git a/jikimo_system_order/__init__.py b/jikimo_system_order/__init__.py new file mode 100644 index 00000000..bbc55806 --- /dev/null +++ b/jikimo_system_order/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- + +from . import controllers +from . import models +from . import wizard diff --git a/jikimo_system_order/__manifest__.py b/jikimo_system_order/__manifest__.py new file mode 100644 index 00000000..7f85ed57 --- /dev/null +++ b/jikimo_system_order/__manifest__.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +{ + 'name': "jikimo_system_order", + + 'summary': """ + 系统工单""", + + 'description': """ + 用于处理针对系统的工作任务; + 员工可以通过系统工单发起申请,由维护人员处理以后,填写处理结果。 + """, + + 'author': "机企猫", + 'website': "http://www.jikimo.com", + + # Categories can be used to filter modules in modules listing + # Check https://github.com/odoo/odoo/blob/master/odoo/addons/base/module/module_data.xml + # for the full list + 'category': 'Uncategorized', + 'version': '0.1', + + # any module necessary for this one to work correctly + 'depends': ['base','mail'], + + # always loaded + 'data': [ + 'security/account_security.xml', + 'security/ir.model.access.csv', + 'wizard/order_wizard.xml', + 'views/notice_user_config.xml', + 'views/yizuo_system_order_view.xml', + 'views/work_order_number.xml', + 'views/res_config_settings_views.xml', + ], + # only loaded in demonstration mode + 'demo': [ + 'demo/demo.xml', + ], +} \ No newline at end of file diff --git a/jikimo_system_order/controllers/__init__.py b/jikimo_system_order/controllers/__init__.py new file mode 100644 index 00000000..457bae27 --- /dev/null +++ b/jikimo_system_order/controllers/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import controllers \ No newline at end of file diff --git a/jikimo_system_order/controllers/controllers.py b/jikimo_system_order/controllers/controllers.py new file mode 100644 index 00000000..cfcad7d0 --- /dev/null +++ b/jikimo_system_order/controllers/controllers.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +from odoo import http + +# class TopSystemOrder(http.Controller): +# @http.route('/jikimo_system_order/jikimo_system_order/', auth='public') +# def index(self, **kw): +# return "Hello, world" + +# @http.route('/jikimo_system_order/jikimo_system_order/objects/', auth='public') +# def list(self, **kw): +# return http.request.render('jikimo_system_order.listing', { +# 'root': '/jikimo_system_order/jikimo_system_order', +# 'objects': http.request.env['jikimo_system_order.jikimo_system_order'].search([]), +# }) + +# @http.route('/jikimo_system_order/jikimo_system_order/objects//', auth='public') +# def object(self, obj, **kw): +# return http.request.render('jikimo_system_order.object', { +# 'object': obj +# }) \ No newline at end of file diff --git a/jikimo_system_order/demo/demo.xml b/jikimo_system_order/demo/demo.xml new file mode 100644 index 00000000..b167f9be --- /dev/null +++ b/jikimo_system_order/demo/demo.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/jikimo_system_order/models/__init__.py b/jikimo_system_order/models/__init__.py new file mode 100644 index 00000000..87b51a43 --- /dev/null +++ b/jikimo_system_order/models/__init__.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- + +from . import constant +from . import order_classify +from . import system_work_order +from . import work_order_template +from . import res_config_setting diff --git a/jikimo_system_order/models/constant.py b/jikimo_system_order/models/constant.py new file mode 100644 index 00000000..414371b9 --- /dev/null +++ b/jikimo_system_order/models/constant.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- + +# 工单状态 +STATE_SELECTION = [('draft', u'草稿'), ('unconfirmed', u'待确认'), ('pending', u'待处理'), + ('processed', u'已处理待评分'), ('completed', u'已完成'), ('closed', u'已关闭')] + +GRADE = [('1', '1非常不满意'), ('2', '2不满意'), ('3', '3一般'), ('4', '4满意'), ('5', '5非常满意')] diff --git a/jikimo_system_order/models/order_classify.py b/jikimo_system_order/models/order_classify.py new file mode 100644 index 00000000..d143487d --- /dev/null +++ b/jikimo_system_order/models/order_classify.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- + +from odoo import models, fields, api +from odoo.exceptions import ValidationError + + +class OrderClassify(models.Model): + _name = 'order.classify' + _order = 'sequence, name' + + + @api.constrains('name') + def check_base_name(self): + """类型名称唯一""" + name_obj = self.env['order.classify'].search([('name', '=', self.name)]) + if len(name_obj) >= 2: + raise ValidationError(u'该类型已存在') + + # 名称 + name = fields.Char(string=u'名称', size=20) + # 排序 + sequence = fields.Integer(default=10) + # 是否有效 + state = fields.Boolean(default=True, string='是否有效') + diff --git a/jikimo_system_order/models/res_config_setting.py b/jikimo_system_order/models/res_config_setting.py new file mode 100644 index 00000000..44a9a1d7 --- /dev/null +++ b/jikimo_system_order/models/res_config_setting.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- + +import logging +from odoo import api, fields, models, _ + +_logger = logging.getLogger(__name__) + + +class ResModelWeConfigSettings(models.TransientModel): + _inherit = 'res.config.settings' + + lost_agent_id = fields.Char('企微通知应用ID') + + @api.model + def get_values(self): + """ + 重载获取参数的方法,参数都存在系统参数中 + :return: + """ + values = super(ResModelWeConfigSettings, self).get_values() + config = self.env['ir.config_parameter'].sudo() + lost_agent_id = config.get_param('lost_agent_id', default='') + values.update( + lost_agent_id=lost_agent_id, + ) + return values + + def set_values(self): + super(ResModelWeConfigSettings, self).set_values() + ir_config = self.env['ir.config_parameter'].sudo() + ir_config.set_param("lost_agent_id", self.lost_agent_id or "") + diff --git a/jikimo_system_order/models/system_work_order.py b/jikimo_system_order/models/system_work_order.py new file mode 100644 index 00000000..1a7bff5e --- /dev/null +++ b/jikimo_system_order/models/system_work_order.py @@ -0,0 +1,183 @@ +# -*- coding: utf-8 -*- + +from odoo import models, fields, api +from odoo.exceptions import ValidationError +from odoo import exceptions +from .constant import STATE_SELECTION, GRADE +import datetime +import logging + + +class SystemWorkOrder(models.Model): + _name = 'system.work.order' + _inherit = ['mail.thread', 'mail.activity.mixin'] + _order = 'date desc' + _description = u'系统工单' + _rec_name = 'order_number' + + def get_is_technicist(self): + self._cr.execute( + "select u.id from res_users u left join res_groups_users_rel r on r.uid = u.id where r.gid in (select g.id from res_groups g where g.name = '技术员权限') and u.id ='%s'", + (self.env.user.id,)) + hr = self._cr.dictfetchall() + if len(hr) > 0: + return True + else: + return False + + # def get_user_department_id(self): + # """根据用户id系统员工id""" + # employee = self.env['hr.employee'].sudo().search([('user_id', '=', self.env.uid)], limit=1) + # if employee: + # if len(employee) > 0: + # if not employee.department_id: + # raise exceptions.Warning(u'您当前使用的用户没有所属部门') + # return employee.department_id + # else: + # return False + # else: + # raise exceptions.Warning(u'您当前使用的用户没有关联员工') + + @api.onchange('order_template_id') + def get_title(self): + """选择模板自动填充""" + if self.order_template_id: + self.title = self.order_template_id.title_template + self.text = self.order_template_id.text_template + + # 工单编号 + order_number = fields.Char(string=u'工单编号', default='/') + # 紧急程度 + urgency_degree = fields.Selection([('0', u'0星'), ('1', u'一星'), ('2', u'二星'), ('3', u'三星'), ('4', u'四星'), + ('5', u'五星')], string=u'紧急程度', help='五星为最紧急!', default='5') + # 工单分类(可以配置,并调整优先级) + order_type = fields.Many2one('order.classify', string=u'工单分类', domain=[('state', '=', True)]) + # 发起人所属公司(res.company) + initiator_company_id = fields.Many2one('res.company', string=u'发起人所属公司', default=lambda self: self.env.user.company_id) + # 发起人部门(hr.department) + # initiator_department_id = fields.Many2one('hr.department', string=u'发起人部门', default=get_user_department_id) + # 发起人(hr.employee) + initiator_id = fields.Many2one('res.users', string=u'发起人', default=lambda self: self.env.user) + # 发起时间 + date = fields.Datetime(string=u'发起时间', default=lambda self: fields.datetime.now()) + # 确认人 + confirm_id = fields.Many2one('res.users', string=u'确认人') + # 确认日期 + confirmation_date = fields.Datetime(string=u'确认时间') + # 模板 + order_template_id = fields.Many2one('work.order.template', string=u'模板', domain=[('state', '=', True)]) + # 标题 + title = fields.Char(string=u'标题') + # 正文 + text = fields.Html(string=u'正文') + # 状态[草稿\待确认\待处理\已处理\已关闭] + state = fields.Selection(STATE_SELECTION, default='draft', string=u'状态') + # 关闭原因 + close_cause = fields.Text(string=u'关闭问题原因') + # 关闭时间 + close_time = fields.Datetime(string=u'关闭问题时间') + # 关闭人 + close_user_id = fields.Many2one('res.users', string=u'关闭人') + # 解决人 + solve_people_id = fields.Many2one('res.users', string=u'解决人') + # 用户实际问题 + users_problem = fields.Text(string=u'用户实际问题') + # 最终解决方案 + solution = fields.Text(string=u'最终解决方案') + # 判断是否为技术人员 + # is_technicist = fields.Boolean(string=u'是否为技术人员', default=get_is_technicist) + # 打分 + grade = fields.Selection(GRADE, string=u'评分') + # 评价按钮的显示 + is_display = fields.Boolean('控制显示评价按钮', compute='compute_is_display') + + def compute_is_display(self): + for item in self: + if item.state == 'processed' and self.env.user.id == item.initiator_id.id: + item.is_display = True + else: + item.is_display = False + + @api.onchange('order_type') + def _onchange_order_type(self): + self.order_template_id = None + self.title = None + self.text = None + + @api.model + def create(self, vals): + # 创建编号 + if vals.get('order_number', '/') == '/': + vals['order_number'] = self.env['ir.sequence'].get('system.work.order') or '/' + return super(SystemWorkOrder, self).create(vals) + + def do_draft(self, order=None): + """状态草稿""" + bill = self + if order: + bill = order + if bill.state == 'unconfirmed': + state_remark = u'待确认 --> 草稿' + # bill.message_post(u'操作人:%s,操作时间:%s,状态变更过程:%s' % (self.env.user.name, + # (datetime.datetime.now() + datetime.timedelta(hours=8)).strftime('%Y-%m-%d %H:%M:%S'), state_remark)) + bill.state = 'draft' + + def do_unconfirmed(self): + """状态待确认""" + if self.state == 'draft': + state_remark = u'草稿 --> 待确认' + # self.message_post(u'操作人:%s,操作时间:%s,状态变更过程:%s' % ( + # self.env.user.name, + # (datetime.datetime.now() + datetime.timedelta(hours=8)).strftime('%Y-%m-%d %H:%M:%S'), state_remark)) + self.state = 'unconfirmed' + # 获取通知人 + objs = self.env['system.order.notice'].search([]) + user_ids = objs.notice_user_ids.filtered(lambda item: item.we_employee_id not in ['', False]) + we_employee_ids = user_ids.mapped('we_employee_id') + lost_agent_id = self.env['ir.config_parameter'].sudo().get_param('lost_agent_id') + wechat = self.env['we.config'].sudo().get_wechat(agent_id=lost_agent_id) + # agent_id, user_ids, content + content = """您有一张工单待处理:**工单标题:{2}** + >创建人:{1} + >提交时间:{3} + >紧急程度:{0}星 + 请查看工单消息,并及时处理! + """.format(self.urgency_degree, + self.initiator_id.name, self.title, (self.date + datetime.timedelta(hours=8)).strftime('%Y-%m-%d %H:%M')) + for we_employee_id in we_employee_ids: + try: + wechat.message.send_markdown(agent_id=lost_agent_id, user_ids=we_employee_id, content=content) + except Exception as e: + logging.error('工单处理发送消息异常%s' % str(e)) + + return True + + def do_pending(self): + """状态待处理""" + if self.state == 'unconfirmed': + state_remark = u'待确认 --> 待处理' + # self.message_post(u'操作人:%s,操作时间:%s,状态变更过程:%s' % ( + # self.env.user.name, + # (datetime.datetime.now() + datetime.timedelta(hours=8)).strftime('%Y-%m-%d %H:%M:%S'), state_remark)) + self.state = 'pending' + self.confirm_id = self.env.user + self.confirmation_date = fields.datetime.now() + return True + + def urned_off(self): + """状态关闭""" + if self.close_cause: + self.state = 'closed' + self.close_time = fields.datetime.now() + else: + raise ValidationError(u'请注明关闭原因') + return True + + def unlink(self): + for item in self: + if item.state != "draft": + raise ValidationError(u'只能删除状态为【草稿】的工单。') + elif item.env.uid != item.initiator_id.id: + raise ValidationError(u'非本人不能删除') + else: + super(SystemWorkOrder, item).unlink() diff --git a/jikimo_system_order/models/work_order_template.py b/jikimo_system_order/models/work_order_template.py new file mode 100644 index 00000000..f50fd42d --- /dev/null +++ b/jikimo_system_order/models/work_order_template.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- + +from odoo import models, fields, api + + +class WorkOrderTemplate(models.Model): + _name = 'work.order.template' + _order = 'num' + + # 编号 + num = fields.Char(string=u'编号', default='/') + # 名称 + name = fields.Char(string=u'模板名称', required="1") + # 分类 + work_order_type = fields.Many2one('order.classify', string=u'系统工单分类', domain=[('state', '=', True)]) + # 模板标题 + title_template = fields.Char(string=u'模板标题') + # 模板正文 + text_template = fields.Html(string=u'模板正文') + # 模板说明 + template_explain = fields.Text(string=u'模板说明') + # 是否有效 + state = fields.Boolean(default=True, string=u'是否有效') + + @api.model + def create(self, vals): + # 创建编号 + if vals.get('num', '/') == '/': + vals['num'] = self.env['ir.sequence'].get('work.order.template') or '/' + return super(WorkOrderTemplate, self).create(vals) + + +class SystemOrderNotice(models.Model): + _name = 'system.order.notice' + _description = '工单处理人设置' + + notice_user_ids = fields.Many2many('res.users', string='工单处理人') + diff --git a/jikimo_system_order/security/account_security.xml b/jikimo_system_order/security/account_security.xml new file mode 100644 index 00000000..8356964e --- /dev/null +++ b/jikimo_system_order/security/account_security.xml @@ -0,0 +1,24 @@ + + + + + + 运维权限 + + + + 用户访问工单信息 + + + [('initiator_id', '=', user.id)] + + + + 运维访问工单信息 + + + [(1, '=', 1)] + + + + diff --git a/jikimo_system_order/security/ir.model.access.csv b/jikimo_system_order/security/ir.model.access.csv new file mode 100644 index 00000000..5c76352d --- /dev/null +++ b/jikimo_system_order/security/ir.model.access.csv @@ -0,0 +1,16 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink + +inside_system_order_classify_r,jikimo_system_order.order_classify,model_order_classify,,1,1,1,1 +inside_system_work_order_rc,jikimo_system_order.system_work_order,model_system_work_order,,1,1,1,1 +inside_work_order_template_r,jikimo_system_order.work_order_template,model_work_order_template,,1,1,1,1 + +inside_system_order_classify_rwc,jikimo_system_order.order_classify,model_order_classify,group_operations_permissions_rwc,1,1,1,0 +inside_system_work_order_rwc,jikimo_system_order.system_work_order,model_system_work_order,group_operations_permissions_rwc,1,1,1,0 +inside_work_order_template_rwc,jikimo_system_order.work_order_template,model_work_order_template,group_operations_permissions_rwc,1,1,1,0 + +order_close_wizard_group_user,jikimo_system_order.order_close_wizard,model_order_close_wizard,base.group_user,1,1,1,1 +order_other_wizard_group_user,jikimo_system_order.order_other_wizard,model_order_other_wizard,base.group_user,1,1,1,1 +order_technician_wizard_group_user,jikimo_system_order.order_technician_wizard,model_order_technician_wizard,base.group_user,1,1,1,1 +system_work_order_wizard_group_user,jikimo_system_order.system_work_order_wizard,model_system_work_order_wizard,base.group_user,1,1,1,1 + +system_order_notice_group_user,jikimo_system_order.system_order_notice,model_system_order_notice,base.group_user,1,1,1,1 \ No newline at end of file diff --git a/jikimo_system_order/static/description/icon.png b/jikimo_system_order/static/description/icon.png new file mode 100644 index 00000000..24aa8fc8 Binary files /dev/null and b/jikimo_system_order/static/description/icon.png differ diff --git a/jikimo_system_order/views/notice_user_config.xml b/jikimo_system_order/views/notice_user_config.xml new file mode 100644 index 00000000..353afb96 --- /dev/null +++ b/jikimo_system_order/views/notice_user_config.xml @@ -0,0 +1,58 @@ + + + + # ---------- 工单通知处理人设置 ------------ + + + tree.system.order.notice + system.order.notice + + + + + + + + + + search.system.order.notice + system.order.notice + + + + + + + + + + + + + + 工单处理人 + system.order.notice + tree + [] + {} + +

+ [工单处理人] 还没有哦!点左上角的[创建]按钮,沙发归你了! +

+

+

+
+
+ + + + + + + + + + +
+
\ No newline at end of file diff --git a/jikimo_system_order/views/res_config_settings_views.xml b/jikimo_system_order/views/res_config_settings_views.xml new file mode 100644 index 00000000..40cdc443 --- /dev/null +++ b/jikimo_system_order/views/res_config_settings_views.xml @@ -0,0 +1,28 @@ + + + + + res.config.settings.we.view.form.inherit.bpm + res.config.settings + + + +
+

企微通知应用ID

+
+
+
+
+
+
+
+
+
+
+ + + + + diff --git a/jikimo_system_order/views/work_order_number.xml b/jikimo_system_order/views/work_order_number.xml new file mode 100644 index 00000000..63f0d5a3 --- /dev/null +++ b/jikimo_system_order/views/work_order_number.xml @@ -0,0 +1,23 @@ + + + + + + + seq_work_order + + system.work.order + SO%(year)s%(month)s%(day)s + 1 + + + + + seq_order_template + + work.order.template + TL + 1 + + + \ No newline at end of file diff --git a/jikimo_system_order/views/yizuo_system_order_view.xml b/jikimo_system_order/views/yizuo_system_order_view.xml new file mode 100644 index 00000000..006a085d --- /dev/null +++ b/jikimo_system_order/views/yizuo_system_order_view.xml @@ -0,0 +1,243 @@ + + + + + + + 工单信息 + system.work.order + + + + + + + + + + + + + + 新建系统工单 + system.work.order + +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + + 搜索工单 + system.work.order + + + + + + + + + + + + + + + + + + + + + + + + + + + 工单图表 + system.work.order + + + + + + + + + + + + 工单 + system.work.order + tree,form,search,graph,pivot + + + + + 工单模板信息 + work.order.template + + + + + + + + + + + + + + + 新建系统工单模板 + work.order.template + +
+ + + + + + + + + + + +
+
+
+ + + + 工单模板 + work.order.template + tree,form + + + + + 工单分类信息 + order.classify + + + + + + + + + + + + 新建系统分类信息 + order.classify + +
+ + + + + + + +
+
+
+ + + + 工单分类 + order.classify + tree,form + + + + + + + + +
+
\ No newline at end of file diff --git a/jikimo_system_order/wizard/__init__.py b/jikimo_system_order/wizard/__init__.py new file mode 100644 index 00000000..8cf586ce --- /dev/null +++ b/jikimo_system_order/wizard/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- + +from . import order_other_wizard +from . import order_technician_wizard +from . import order_close_wizard +from . import system_work_order_wizard diff --git a/jikimo_system_order/wizard/order_close_wizard.py b/jikimo_system_order/wizard/order_close_wizard.py new file mode 100644 index 00000000..ca10ae7c --- /dev/null +++ b/jikimo_system_order/wizard/order_close_wizard.py @@ -0,0 +1,79 @@ +# -*- coding: utf-8 -*- + +from odoo import models, fields, api +from odoo.addons.jikimo_system_order.models.constant import STATE_SELECTION +from odoo.exceptions import ValidationError +import datetime, logging + + +class OrderCloseWizard(models.TransientModel): + _name = 'order.close.wizard' + + + def get_context(self): + if self._context.get('active_id'): + obj = self.env['system.work.order'].browse(self._context.get('active_id')) + if obj.initiator_id.id != self.env.user.id: + raise ValidationError(u'非本人无法操作') + return obj + + order_id = fields.Many2one('system.work.order', string=u'工单ID', + default=lambda self: self.get_context().id) + # 关闭原因 + close_cause = fields.Text(string=u'关闭问题原因', default=lambda self: self.get_context().close_cause) + # 关闭时间 + close_time = fields.Datetime(string=u'关闭问题时间', default=fields.datetime.now()) + # 状态 + state = fields.Selection(STATE_SELECTION, default='closed', string=u'状态') + # 关闭人 + close_user_id = fields.Many2one('res.users', string=u'关闭人', default=lambda self: self.env.user) + + + def sure(self): + self.order_id.close_cause = self.close_cause + self.order_id.close_time = self.close_time + if self.order_id.state == 'unconfirmed': + state_remark = u'待确认 --> 已关闭' + if self.order_id.state == 'pending': + state_remark = u'待处理 --> 已关闭' + if self.order_id.state == 'processed': + state_remark = u'已处理待评分 --> 已关闭' + # self.order_id.message_post(u'操作人:%s,操作时间:%s,状态变更过程:%s' % ( + # self.env.user.name, + # (datetime.datetime.now() + datetime.timedelta(hours=8)).strftime('%Y-%m-%d %H:%M:%S'), state_remark)) + self.order_id.state = self.state + self.order_id.close_user_id = self.close_user_id + we_employee_ids = [] + if self.order_id.initiator_id.we_employee_id: + we_employee_ids.append(self.order_id.initiator_id.we_employee_id) + lost_agent_id = self.env['ir.config_parameter'].sudo().get_param('lost_agent_id') + wechat = self.env['we.config'].sudo().get_wechat(agent_id=lost_agent_id) + # agent_id, user_ids, content + content = """您提交的工单-**工单标题:{0}**-**已关闭** + >提交时间:{1} + >处理时间:{2} + >处理人:{3} + 如有问题,请联系系统管理员! + """.format(self.order_id.title, + (self.order_id.date + datetime.timedelta(hours=8)).strftime( + '%Y-%m-%d %H:%M'), (datetime.datetime.now() + datetime.timedelta( + hours=8)).strftime('%Y-%m-%d %H:%M'), self.env.user.name or '') + # wechat.message.send_markdown(agent_id=lost_agent_id, user_ids=we_employee_ids, content=content) + for we_employee_id in we_employee_ids: + try: + wechat.message.send_markdown(agent_id=lost_agent_id, user_ids=we_employee_id, content=content) + except Exception as e: + logging.error('工单关闭发送消息异常%s' % str(e)) + return {} + + + + + + + + + + + + diff --git a/jikimo_system_order/wizard/order_other_wizard.py b/jikimo_system_order/wizard/order_other_wizard.py new file mode 100644 index 00000000..3f6c8710 --- /dev/null +++ b/jikimo_system_order/wizard/order_other_wizard.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- + +from odoo import models, fields, api +from odoo.exceptions import ValidationError +from odoo.addons.jikimo_system_order.models.constant import STATE_SELECTION, GRADE +import datetime + + +class OrderOtherWizard(models.TransientModel): + _name = 'order.other.wizard' + + + def get_context(self): + if self._context.get('active_id'): + obj = self.env['system.work.order'].browse(self._context.get('active_id')) + if obj.initiator_id.id != self.env.user.id: + raise ValidationError(u'非本人无法操作') + return obj + + order_id = fields.Many2one('system.work.order', string=u'工单ID', + default=lambda self: self.get_context().id) + # 关闭时间 + close_time = fields.Datetime(string=u'关闭时间', default=fields.datetime.now()) + # 状态 + state = fields.Selection(STATE_SELECTION, default='completed', string=u'状态') + # 打分 + grade = fields.Selection(GRADE, string=u'评分') + # 关闭人 + close_user_id = fields.Many2one('res.users', string=u'关闭人', default=lambda self: self.env.user) + + + def sure(self): + self.order_id.close_time = self.close_time + self.order_id.grade = self.grade + if self.order_id.state == 'processed': + state_remark = u'已处理待评分 --> 已完成' + # self.order_id.message_post(u'操作人:%s,操作时间:%s,状态变更过程:%s' % ( + # self.env.user.name, + # (datetime.datetime.now() + datetime.timedelta(hours=8)).strftime('%Y-%m-%d %H:%M:%S'), state_remark)) + self.order_id.state = self.state + self.order_id.close_user_id = self.close_user_id + return {} diff --git a/jikimo_system_order/wizard/order_technician_wizard.py b/jikimo_system_order/wizard/order_technician_wizard.py new file mode 100644 index 00000000..78458038 --- /dev/null +++ b/jikimo_system_order/wizard/order_technician_wizard.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- + +from odoo import models, fields, api +from odoo.addons.jikimo_system_order.models.constant import STATE_SELECTION +import datetime +import logging + + +class OrderTechnicianWizard(models.TransientModel): + _name = 'order.technician.wizard' + + order_id = fields.Many2one('system.work.order', string=u'工单ID', + default=lambda self: self.env.context.get('active_id')) + # 解决人 + solve_people_id = fields.Many2one('res.users', string=u'解决人', default=lambda self: self.env.user) + # 用户实际问题 + users_problem = fields.Text(string=u'用户实际问题') + # 最终解决方案 + solution = fields.Text(string=u'最终解决方案') + # 状态 + state = fields.Selection(STATE_SELECTION, default='processed', string=u'状态') + + def sure(self): + self.order_id.solve_people_id = self.solve_people_id + self.order_id.users_problem = self.users_problem + self.order_id.solution = self.solution + if self.order_id.state == 'pending': + state_remark = u'待处理 --> 已处理待评分' + # self.order_id.message_post(u'操作人:%s,操作时间:%s,状态变更过程:%s' % ( + # self.env.user.name, + # (datetime.datetime.now() + datetime.timedelta(hours=8)).strftime('%Y-%m-%d %H:%M:%S'), state_remark)) + self.order_id.state = self.state + # 获取通知人 + # objs = self.env['system.order.notice'].search([]) + # user_ids = objs.notice_user_ids.filtered(lambda item: item.we_employee_id not in ['', False]) + # we_employee_ids = user_ids.mapped('we_employee_id') + we_employee_ids = [] + if self.order_id.initiator_id.we_employee_id: + we_employee_ids.append(self.order_id.initiator_id.we_employee_id) + print(we_employee_ids) + lost_agent_id = self.env['ir.config_parameter'].sudo().get_param('lost_agent_id') + wechat = self.env['we.config'].sudo().get_wechat(agent_id=lost_agent_id) + # agent_id, user_ids, content + content = """您提交的工单-**工单标题:{0}**-**已处理** + >提交时间:{1} + >处理反馈:{4} + >处理时间:{2} + >处理人:{3} + 如有问题,请联系系统管理员! + """.format(self.order_id.title, + (self.order_id.date + datetime.timedelta(hours=8)).strftime('%Y-%m-%d %H:%M'), (datetime.datetime.now() + datetime.timedelta(hours=8)).strftime('%Y-%m-%d %H:%M'), self.env.user.name or '', self.solution or '') + # wechat.message.send_markdown(agent_id=lost_agent_id, user_ids=we_employee_ids, content=content) + for we_employee_id in we_employee_ids: + try: + wechat.message.send_markdown(agent_id=lost_agent_id, user_ids=we_employee_id, content=content) + except Exception as e: + logging.error('工单处理发送消息异常%s' % str(e)) + + return {} diff --git a/jikimo_system_order/wizard/order_wizard.xml b/jikimo_system_order/wizard/order_wizard.xml new file mode 100644 index 00000000..64c13ccd --- /dev/null +++ b/jikimo_system_order/wizard/order_wizard.xml @@ -0,0 +1,122 @@ + + + + + + 技术员向导 + order.technician.wizard + +
+ + + + + + +
+
+
+
+
+ + + 技术员编辑 + ir.actions.act_window + order.technician.wizard + form + + {'display_default_code':False} + new + + + + + + + 其它向导 + order.other.wizard + +
+ + + + + + +
+
+
+
+
+ + + 其它编辑 + ir.actions.act_window + order.other.wizard + form + + {'display_default_code':False} + new + + + + + 关闭向导 + order.close.wizard + +
+ + + + + + +
+
+
+
+
+ + + 关闭工单 + ir.actions.act_window + order.close.wizard + form + + {'display_default_code':False} + new + + + + system_work_order_wizard_view + system.work.order.wizard + +
+ +
+
+ +
+
+ + + 二次确认 + ir.actions.act_window + system.work.order.wizard + form + new + +
+
diff --git a/jikimo_system_order/wizard/system_work_order_wizard.py b/jikimo_system_order/wizard/system_work_order_wizard.py new file mode 100644 index 00000000..8c8f95dc --- /dev/null +++ b/jikimo_system_order/wizard/system_work_order_wizard.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# @Time : 2017/12/12 9:46 +# @Author : GuoXiang +# @Site : +# @File : system_work_order_wizard.py +# @Software: PyCharm +# @Desc : +# @license : Copyright©2018 www.dasmaster.com All Rights Reserved. +# @Contact : xg1230205321@163.com +from odoo import models, api, fields +from odoo.exceptions import ValidationError + + +class SystemWorkOrderWizard(models.TransientModel): + _name = "system.work.order.wizard" + _description = u"追回确认" + + + def _get_explain(self): + if self._context.get('object_id'): + obj = self.env['system.work.order'].browse(self._context.get('object_id')) + if obj.initiator_id.id != self.env.user.id: + raise ValidationError(u'非本人无法操作') + if self._context.get('explain'): + return self._context["explain"] + + explain = fields.Char(default=_get_explain) + + + def sure(self): + """ + 确认 + :return: + """ + if self._context.get('object_id') and self._context.get('object_name') and self._context.get( + 'explain') and self._context.get('function_name'): + work_sheet_obj = self.env[self._context["object_name"]].search([('id', '=', int(self._context["object_id"]))]) + class_name = self._context.get('object_name') # 获得对象类名 + method_name = self._context.get('function_name') # 获得对象的方法 + obj_function = getattr(self.env[class_name], method_name) + obj_function(work_sheet_obj) diff --git a/sf_base/models/common.py b/sf_base/models/common.py index 6fd7d814..65dfe13d 100644 --- a/sf_base/models/common.py +++ b/sf_base/models/common.py @@ -61,12 +61,10 @@ class MrsMaterialModel(models.Model): supplier_ids = fields.One2many('sf.supplier.sort', 'materials_model_id', string='供应商') active = fields.Boolean('有效', default=True) - @api.onchange('gain_way') - def _check_gain_way(self): - if not self.gain_way: - raise UserError("请选择获取方式") - if self.gain_way in ['外协', '采购']: - if not self.supplier_ids: + @api.constrains("gain_way") + def _check_supplier_ids(self): + for item in self: + if item.gain_way in ('外协', '采购') and not item.supplier_ids: raise UserError("请添加供应商") @@ -94,8 +92,10 @@ class MrsProductionProcess(models.Model): partner_process_ids = fields.Many2many('res.partner', 'process_ids', '加工工厂') active = fields.Boolean('有效', default=True) parameter_ids = fields.One2many('sf.production.process.parameter', 'process_id', string='可选参数') - category_id = fields.Many2one('sf.production.process.category') + category_id = fields.Many2one('sf.production.process.category', string='表面工艺类别') # workcenter_ids = fields.Many2many('mrp.workcenter', 'rel_workcenter_process', required=True) + processing_day = fields.Float('加工天数/d') + travel_day = fields.Float('路途天数/d') # class MrsProcessingTechnology(models.Model): @@ -143,7 +143,10 @@ class MrsProductionProcessParameter(models.Model): is_check = fields.Boolean(default=False) # price = fields.Float('单价') process_id = fields.Many2one('sf.production.process', string='表面工艺') + process_description = fields.Char(string='工艺描述') materials_model_ids = fields.Many2many('sf.materials.model', 'applicable_material', string='适用材料') + processing_day = fields.Float('加工天数/d') + travel_day = fields.Float('路途天数/d') active = fields.Boolean('有效', default=True) def name_get(self): diff --git a/sf_base/views/common_view.xml b/sf_base/views/common_view.xml index 74e916a8..533a3e04 100644 --- a/sf_base/views/common_view.xml +++ b/sf_base/views/common_view.xml @@ -27,9 +27,13 @@ + - + + + + @@ -179,41 +183,46 @@
- + + + + + + - - - - - - - - - -
- - - - - - - - - - - - - - - - - -
-
-
- -
+ + + + + + + + + +
+ + + + + + + + + + + + + + + + + +
+
+
+ +
@@ -251,7 +260,7 @@ - + @@ -270,9 +279,9 @@ - + - + diff --git a/sf_bf_connect/models/jd_eclp.py b/sf_bf_connect/models/jd_eclp.py index 7879938e..ec14f7e1 100644 --- a/sf_bf_connect/models/jd_eclp.py +++ b/sf_bf_connect/models/jd_eclp.py @@ -161,6 +161,9 @@ class JdEclp(models.Model): url2 = config['bfm_url'] + '/api/get/jd/no' response = requests.post(url2, json=json2, data=None) # _logger.info('调用成功2', response.json()['result']['wbNo']) + tem_ret = response.json().get('result') + if not tem_ret: + raise ValidationError('京东物流返回异常,请联系管理员') self.carrier_tracking_ref = response.json()['result'].get('wbNo') if not self.carrier_tracking_ref: raise ValidationError('物流下单未成功,请联系管理员') diff --git a/sf_dlm_management/data/stock_data.xml b/sf_dlm_management/data/stock_data.xml index 3d2c4527..be2a36e0 100644 --- a/sf_dlm_management/data/stock_data.xml +++ b/sf_dlm_management/data/stock_data.xml @@ -22,6 +22,16 @@
+ + 拆解 + + internal + DJCJ + true + true + + + 刀具组装入库 diff --git a/sf_machine_connect/__init__.py b/sf_machine_connect/__init__.py index 9b429614..6bac603e 100644 --- a/sf_machine_connect/__init__.py +++ b/sf_machine_connect/__init__.py @@ -1,2 +1,3 @@ from . import models from . import wizard +from . import controllers diff --git a/sf_machine_connect/controllers/__init__.py b/sf_machine_connect/controllers/__init__.py new file mode 100644 index 00000000..e046e49f --- /dev/null +++ b/sf_machine_connect/controllers/__init__.py @@ -0,0 +1 @@ +from . import controllers diff --git a/sf_machine_connect/controllers/controllers.py b/sf_machine_connect/controllers/controllers.py new file mode 100644 index 00000000..ba8139d0 --- /dev/null +++ b/sf_machine_connect/controllers/controllers.py @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- +import ast +import json +import logging +from odoo import http +from odoo.http import request + + +class Sf_Dashboard_Connect(http.Controller): + + @http.route('/api/get_machine_datas/list', type='http', auth='public', methods=['GET', 'POST'], csrf=False, + cors="*") + def get_machine_datas_list(self, **kw): + """ + 拿到机床数据返回给大屏展示 + :param kw: + :return: + """ + res = {'status': 1, 'message': '成功', 'data': []} + logging.info('前端请求机床数据的参数为:%s' % kw) + # 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", + # "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" + # ] + try: + equipment_obj = request.env['maintenance.equipment'].sudo() + # 获取请求的机床数据 + machine_list = ast.literal_eval(kw['machine_list']) + for item in machine_list: + machine_data = equipment_obj.search([('code', '=', item)]) + if machine_data: + res['data'].append({ + 'id': machine_data.id, + 'name': machine_data.name, + 'code': machine_data.code, + 'status': machine_data.status, + 'run_status': machine_data.run_status, + 'run_time': machine_data.run_time, + 'system_date': machine_data.system_date, + 'system_time': machine_data.system_time, + 'cut_time': machine_data.cut_time, + 'cut_status': machine_data.cut_status, + 'program': machine_data.program, + 'program_name': machine_data.program_name, + 'program_status': machine_data.program_status, + 'tool_num': machine_data.tool_num, + 'machine_power_on_time': machine_data.machine_power_on_time, + 'product_counts': machine_data.product_counts, + 'mode': machine_data.mode, + 'start_time': machine_data.start_time, + 'end_time': machine_data.end_time, + 'program_start_time': machine_data.program_start_time, + 'program_end_time': machine_data.program_end_time, + 'standby_start_time': machine_data.standby_start_time, + 'standby_end_time': machine_data.standby_end_time, + 'offline_start_time': machine_data.offline_start_time, + 'offline_end_time': machine_data.offline_end_time, + 'emg_status': machine_data.emg_status, + 'current_program': machine_data.current_program, + 'current_program_seq': machine_data.current_program_seq, + 'x_abs_pos': machine_data.x_abs_pos, + 'y_abs_pos': machine_data.y_abs_pos, + 'z_abs_pos': machine_data.z_abs_pos, + 'feed_speed_set': machine_data.feed_speed_set, + 'act_feed_speed': machine_data.act_feed_speed, + 'spindle_speed_set': machine_data.spindle_speed_set, + 'act_spindle_speed': machine_data.act_spindle_speed, + 'spindle_load': machine_data.spindle_load, + 'x_axis_load': machine_data.x_axis_load, + 'y_axis_load': machine_data.y_axis_load, + 'z_axis_load': machine_data.z_axis_load, + 'rapid_feed': machine_data.rapid_feed, + 'feed_rate': machine_data.feed_rate, + 'x_mach_coord': machine_data.x_mach_coord, + 'y_mach_coord': machine_data.y_mach_coord, + 'z_mach_coord': machine_data.z_mach_coord, + 'x_rel_coord': machine_data.x_rel_coord, + 'y_rel_coord': machine_data.y_rel_coord, + 'z_rel_coord': machine_data.z_rel_coord, + 'x_dis_coord': machine_data.x_dis_coord, + 'y_dis_coord': machine_data.y_dis_coord, + 'z_dis_coord': machine_data.z_dis_coord, + 'alarm_time': machine_data.alarm_time, + 'alarm_msg': machine_data.alarm_msg, + 'clear_time': machine_data.clear_time, + }) + + 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) diff --git a/sf_machine_connect/models/ftp_client.py b/sf_machine_connect/models/ftp_client.py index 2cc1ee5d..1e09f665 100644 --- a/sf_machine_connect/models/ftp_client.py +++ b/sf_machine_connect/models/ftp_client.py @@ -123,128 +123,159 @@ class Machine_ftp(models.Model): # workorder_ids = fields.One2many('mrp.workorder', 'machine_tool_id', string='工单') - # 机床配置项目 - # ftp相关 - ftp_num = fields.Char('ftp账号') - ftp_pwd = fields.Char('ftp密码') - ftp_host = fields.Char('ftp地址') - ftp_port = fields.Integer('ftp端口') - ftp_remote_path = fields.Char('机床ftp路径') - # 补偿值写入相关 - x_compensation_node = fields.Char('x补偿值节点') - y_compensation_node = fields.Char('y补偿值节点') - # 数采配置相关 - machine_ip = fields.Char('机床IP') - machine_signed = fields.Char('机床刷新间隔') - machine_status = fields.Char('机床在线状态') - machine_cnc_type = fields.Char('机床CNC型号') - machine_axis_count = fields.Char('机床轴总数') - machine_run_status = fields.Char('机床运行状态') - machine_emg_status = fields.Char('机床急停状态') - machine_cut_status = fields.Char('机床当前切削状态') - machine_mode = fields.Char('机床当前操作模式') - machine_spindle_load = fields.Char('机床主轴负载') - machine_x_mach = fields.Char('机床X轴机械坐标') - machine_x_abs_mach = fields.Char('机床X轴当前位置') - machine_x_rel_mach = fields.Char('机床X轴相对工件坐标') - machine_x_dis_mach = fields.Char('机床X轴目标距离') - machine_x_axis_load = fields.Char('机床X轴伺服轴负载') - machine_y_mach = fields.Char('机床Y轴机械坐标') - machine_y_abs_mach = fields.Char('机床Y轴当前位置') - machine_y_rel_mach = fields.Char('机床Y轴相对工件坐标') - machine_y_dis_mach = fields.Char('机床Y轴目标距离') - machine_y_axis_load = fields.Char('机床Y轴伺服轴负载') - machine_z_mach = fields.Char('机床Z轴机械坐标') - machine_z_abs_mach = fields.Char('机床Z轴当前位置') - machine_z_rel_mach = fields.Char('机床Z轴相对工件坐标') - machine_z_dis_mach = fields.Char('机床Z轴目标距离') - machine_z_axis_load = fields.Char('机床Z轴伺服轴负载') - machine_tool_num = fields.Char('机床当前刀位号') - machine_program = fields.Char('机床主程序名称') - machine_current_prg = fields.Char('机床当前执行指令') - machine_prg_seq = fields.Char('机床当前执行语句号') - machine_spindle_speed_set = fields.Char('机床设定主轴速度') - machine_act_spindle_speed = fields.Char('机床实际主轴转速') - machine_feed_speed_set = fields.Char('机床设定进给速度') - machine_act_feed_speed = fields.Char('机床实际进给速度') - machine_spindle_feed = fields.Char('机床主轴倍率') - machine_feed_rate = fields.Char('机床进给倍率') - machine_rapid_feed = fields.Char('机床快速移动倍率') - machine_run_time = fields.Char('机床运行时间') - machine_cut_time = fields.Char('机床切削时间') - machine_keep_alive_time = fields.Char('机床上电时间') - machine_circle_time = fields.Char('机床循环时间') - machine_product_counts = fields.Char('机床加工件数') - machine_system_date = fields.Char('机床系统日期') - machine_system_time = fields.Char('机床系统时间') - machine_alarm_msg = fields.Char('机床系统报警') + # # 机床配置项目 + # # ftp相关 + # ftp_num = fields.Char('ftp账号') + # ftp_pwd = fields.Char('ftp密码') + # ftp_host = fields.Char('ftp地址') + # ftp_port = fields.Integer('ftp端口') + # ftp_remote_path = fields.Char('机床ftp路径') + # # 补偿值写入相关 + # x_compensation_node = fields.Char('x补偿值节点') + # y_compensation_node = fields.Char('y补偿值节点') + # # 数采配置相关 + # machine_ip = fields.Char('机床IP') + # machine_signed = fields.Char('机床刷新间隔') + # machine_status = fields.Char('机床在线状态') + # machine_cnc_type = fields.Char('机床CNC型号') + # machine_axis_count = fields.Char('机床轴总数') + # machine_run_status = fields.Char('机床运行状态') + # machine_emg_status = fields.Char('机床急停状态') + # machine_cut_status = fields.Char('机床当前切削状态') + # machine_mode = fields.Char('机床当前操作模式') + # machine_spindle_load = fields.Char('机床主轴负载') + # machine_x_mach = fields.Char('机床X轴机械坐标') + # machine_x_abs_mach = fields.Char('机床X轴当前位置') + # machine_x_rel_mach = fields.Char('机床X轴相对工件坐标') + # machine_x_dis_mach = fields.Char('机床X轴目标距离') + # machine_x_axis_load = fields.Char('机床X轴伺服轴负载') + # machine_y_mach = fields.Char('机床Y轴机械坐标') + # machine_y_abs_mach = fields.Char('机床Y轴当前位置') + # machine_y_rel_mach = fields.Char('机床Y轴相对工件坐标') + # machine_y_dis_mach = fields.Char('机床Y轴目标距离') + # machine_y_axis_load = fields.Char('机床Y轴伺服轴负载') + # machine_z_mach = fields.Char('机床Z轴机械坐标') + # machine_z_abs_mach = fields.Char('机床Z轴当前位置') + # machine_z_rel_mach = fields.Char('机床Z轴相对工件坐标') + # machine_z_dis_mach = fields.Char('机床Z轴目标距离') + # machine_z_axis_load = fields.Char('机床Z轴伺服轴负载') + # machine_tool_num = fields.Char('机床当前刀位号') + # machine_program = fields.Char('机床主程序名称') + # machine_current_prg = fields.Char('机床当前执行指令') + # machine_prg_seq = fields.Char('机床当前执行语句号') + # machine_spindle_speed_set = fields.Char('机床设定主轴速度') + # machine_act_spindle_speed = fields.Char('机床实际主轴转速') + # machine_feed_speed_set = fields.Char('机床设定进给速度') + # machine_act_feed_speed = fields.Char('机床实际进给速度') + # machine_spindle_feed = fields.Char('机床主轴倍率') + # machine_feed_rate = fields.Char('机床进给倍率') + # machine_rapid_feed = fields.Char('机床快速移动倍率') + # machine_run_time = fields.Char('机床运行时间') + # machine_cut_time = fields.Char('机床切削时间') + # machine_keep_alive_time = fields.Char('机床上电时间') + # machine_circle_time = fields.Char('机床循环时间') + # machine_product_counts = fields.Char('机床加工件数') + # machine_system_date = fields.Char('机床系统日期') + # machine_system_time = fields.Char('机床系统时间') + # machine_alarm_msg = fields.Char('机床系统报警') - # 刀位配置 - tool_num1 = fields.Char('刀位1') - tool_num2 = fields.Char('刀位2') - tool_num3 = fields.Char('刀位3') - tool_num4 = fields.Char('刀位4') - tool_num5 = fields.Char('刀位5') - tool_num6 = fields.Char('刀位6') - tool_num7 = fields.Char('刀位7') - tool_num8 = fields.Char('刀位8') - tool_num9 = fields.Char('刀位9') - tool_num10 = fields.Char('刀位10') - tool_num11 = fields.Char('刀位11') - tool_num12 = fields.Char('刀位12') - tool_num13 = fields.Char('刀位13') - tool_num14 = fields.Char('刀位14') - tool_num15 = fields.Char('刀位15') - tool_num16 = fields.Char('刀位16') - tool_num17 = fields.Char('刀位17') - tool_num18 = fields.Char('刀位18') - tool_num19 = fields.Char('刀位19') - tool_num20 = fields.Char('刀位20') - tool_num21 = fields.Char('刀位21') - tool_num22 = fields.Char('刀位22') - tool_num23 = fields.Char('刀位23') - tool_num24 = fields.Char('刀位24') + # # 刀位配置 + # tool_num1 = fields.Char('刀位1') + # tool_num2 = fields.Char('刀位2') + # tool_num3 = fields.Char('刀位3') + # tool_num4 = fields.Char('刀位4') + # tool_num5 = fields.Char('刀位5') + # tool_num6 = fields.Char('刀位6') + # tool_num7 = fields.Char('刀位7') + # tool_num8 = fields.Char('刀位8') + # tool_num9 = fields.Char('刀位9') + # tool_num10 = fields.Char('刀位10') + # tool_num11 = fields.Char('刀位11') + # tool_num12 = fields.Char('刀位12') + # tool_num13 = fields.Char('刀位13') + # tool_num14 = fields.Char('刀位14') + # tool_num15 = fields.Char('刀位15') + # tool_num16 = fields.Char('刀位16') + # tool_num17 = fields.Char('刀位17') + # tool_num18 = fields.Char('刀位18') + # tool_num19 = fields.Char('刀位19') + # tool_num20 = fields.Char('刀位20') + # tool_num21 = fields.Char('刀位21') + # tool_num22 = fields.Char('刀位22') + # tool_num23 = fields.Char('刀位23') + # tool_num24 = fields.Char('刀位24') # 机床采集项目 timestamp = fields.Datetime('时间戳', readonly=True) - signed = fields.Integer('刷新间隔', readonly=True) - status = fields.Boolean('在线状态', readonly=True) - time_on = fields.Char('总在线时长', readonly=True) - time_on_now = fields.Char('本次在线时长', readonly=True) - tool_num = fields.Integer('当前刀具', readonly=True) - program = fields.Char('当前程序', readonly=True) - run_status = fields.Selection([('0', '空闲中'), ('1', '加工中'), ('2', '加工中'), ('3', '加工中')], string='运行状态', - readonly=True, default='0') - run_time = fields.Char('总运行时长', readonly=True) - cut_time = fields.Char('总切削时长', readonly=True) - cut_status = fields.Selection([('0', '未切削'), ('1', '切削中'), ('2', '切削中'), ('3', '切削中')], string='切削状态', - readonly=True, default='0') - spindle_speed = fields.Char('主轴转速', readonly=True) + status = fields.Boolean('机床在线状态', readonly=True) + # run_status = fields.Selection([('0', '空闲中'), ('1', '加工中'), ('2', '加工中'), ('3', '加工中')], string='机床运行状态', + # readonly=True, default='0') + run_status = fields.Char('机床运行状态', readonly=True) + run_time = fields.Char('机床累计运行时长', readonly=True) + # 机床系统日期 + system_date = fields.Char('机床系统日期', readonly=True) + # 机床系统时间 + system_time = fields.Char('机床系统时间', readonly=True) + cut_time = fields.Char('机床累计切削时间', readonly=True) + # cut_status = fields.Selection([('0', '未切削'), ('1', '切削中'), ('2', '切削中'), ('3', '切削中')], string='机床当前切削状态', + # readonly=True, default='0') + cut_status = fields.Char('机床当前切削状态', readonly=True) + # 当前程序名 + program = fields.Char('机床当前程序', readonly=True) + # 当前刀具号 + tool_num = fields.Integer('机床当前刀具号', readonly=True) + # 机床通电开机时间, 机床加工件数, 机床当前操作模式, 开始加工时间, 结束加工时间, 加工程序开始时间, 加工程序结束时间, 待机开始时间, + # 待机结束时间, 机床离线开始时间, 机床离线结束时间, 机床急停状态, 机床主程序名称, 程序运行的状态, 机床当前执行指令, 机床当前执行语句号 + # 机床X轴当前位置, 机床Y轴当前位置, 机床Z轴当前位置 + machine_power_on_time = fields.Char('机床通电开机时间', readonly=True) + product_counts = fields.Char('机床加工件数', readonly=True) + mode = fields.Char('机床当前操作模式', readonly=True) + start_time = fields.Char('开始加工时间', readonly=True) + end_time = fields.Char('结束加工时间', readonly=True) + program_start_time = fields.Char('加工程序开始时间', readonly=True) + program_end_time = fields.Char('加工程序结束时间', readonly=True) + standby_start_time = fields.Char('待机开始时间', readonly=True) + standby_end_time = fields.Char('待机结束时间', readonly=True) + offline_start_time = fields.Char('机床离线开始时间', readonly=True) + offline_end_time = fields.Char('机床离线结束时间', readonly=True) + emg_status = fields.Char('机床急停状态', readonly=True) + program_name = fields.Char('机床主程序名称', readonly=True) + program_status = fields.Char('程序运行状态', readonly=True) + current_program = fields.Char('机床当前执行指令', readonly=True) + current_program_seq = fields.Char('机床当前执行语句号', readonly=True) + x_abs_pos = fields.Char('机床X轴当前位置(mm)', readonly=True) + y_abs_pos = fields.Char('机床Y轴当前位置(mm)', readonly=True) + z_abs_pos = fields.Char('机床Z轴当前位置(mm)', readonly=True) - tool_num_process_time1 = fields.Char('刀位1', readonly=True, default='0') - tool_num_process_time2 = fields.Char('刀位2', readonly=True, default='0') - tool_num_process_time3 = fields.Char('刀位3', readonly=True, default='0') - tool_num_process_time4 = fields.Char('刀位4', readonly=True, default='0') - tool_num_process_time5 = fields.Char('刀位5', readonly=True, default='0') - tool_num_process_time6 = fields.Char('刀位6', readonly=True, default='0') - tool_num_process_time7 = fields.Char('刀位7', readonly=True, default='0') - tool_num_process_time8 = fields.Char('刀位8', readonly=True, default='0') - tool_num_process_time9 = fields.Char('刀位9', readonly=True, default='0') - tool_num_process_time10 = fields.Char('刀位10', readonly=True, default='0') - tool_num_process_time11 = fields.Char('刀位11', readonly=True, default='0') - tool_num_process_time12 = fields.Char('刀位12', readonly=True, default='0') - tool_num_process_time13 = fields.Char('刀位13', readonly=True, default='0') - tool_num_process_time14 = fields.Char('刀位14', readonly=True, default='0') - tool_num_process_time15 = fields.Char('刀位15', readonly=True, default='0') - tool_num_process_time16 = fields.Char('刀位16', readonly=True, default='0') - tool_num_process_time17 = fields.Char('刀位17', readonly=True, default='0') - tool_num_process_time18 = fields.Char('刀位18', readonly=True, default='0') - tool_num_process_time19 = fields.Char('刀位19', readonly=True, default='0') - tool_num_process_time20 = fields.Char('刀位20', readonly=True, default='0') - tool_num_process_time21 = fields.Char('刀位21', readonly=True, default='0') - tool_num_process_time22 = fields.Char('刀位22', readonly=True, default='0') - tool_num_process_time23 = fields.Char('刀位23', readonly=True, default='0') - tool_num_process_time24 = fields.Char('刀位24', readonly=True, default='0') + # 机床设定进给速度, 机床实际进给速度, 机床设定主轴转速, 机床实际主轴转速, 机床主轴负载, 机床X轴伺服轴负载, 机床Y轴伺服轴负载 + # 机床Z轴伺服轴负载, 机床快速移动倍率, 机床进给倍率, 机床X轴机械坐标, 机床Y轴机械坐标, 机床Z轴机械坐标, 机床X轴相对工件坐标 + # 机床Y轴相对工件坐标, 机床Z轴相对工件坐标, 机床X轴目标距离, 机床Y轴目标距离, 机床Z轴目标距离 + feed_speed_set = fields.Char('机床设定进给速度(mm/min)', readonly=True) + act_feed_speed = fields.Char('机床实际进给速度(mm/min)', readonly=True) + spindle_speed_set = fields.Char('机床设定主轴转速(r/min)', readonly=True) + act_spindle_speed = fields.Char('机床实际主轴转速(r/min)', readonly=True) + spindle_load = fields.Char('机床主轴负载(%)', readonly=True) + x_axis_load = fields.Char('机床X轴伺服轴负载(%)', readonly=True) + y_axis_load = fields.Char('机床Y轴伺服轴负载(%)', readonly=True) + z_axis_load = fields.Char('机床Z轴伺服轴负载(%)', readonly=True) + rapid_feed = fields.Char('机床快速移动倍率(%)', readonly=True) + feed_rate = fields.Char('机床进给倍率(%)', readonly=True) + x_mach_coord = fields.Char('机床X轴机械坐标(mm)', readonly=True) + y_mach_coord = fields.Char('机床Y轴机械坐标(mm)', readonly=True) + z_mach_coord = fields.Char('机床Z轴机械坐标(mm)', readonly=True) + x_rel_coord = fields.Char('机床X轴相对工件坐标(mm)', readonly=True) + y_rel_coord = fields.Char('机床Y轴相对工件坐标(mm)', readonly=True) + z_rel_coord = fields.Char('机床Z轴相对工件坐标(mm)', readonly=True) + x_dis_coord = fields.Char('机床X轴目标距离(mm)', readonly=True) + y_dis_coord = fields.Char('机床Y轴目标距离(mm)', readonly=True) + z_dis_coord = fields.Char('机床Z轴目标距离(mm)', readonly=True) + + # 故障报警时间, 故障报警信息, 故障消除时间(复原时间) + alarm_time = fields.Char('故障报警时间', readonly=True) + alarm_msg = fields.Char('故障报警信息', readonly=True) + clear_time = fields.Char('故障消除时间(复原时间)', readonly=True) + + # 当前程序名, 机床累计运行时间, 机床系统日期, 机床系统时间, 当前刀具号, 机床循环时间 class WorkCenterBarcode(models.Model): @@ -259,39 +290,59 @@ class WorkCenterBarcode(models.Model): button_compensation_state = fields.Boolean(string='是否已经补偿', readonly=True) button_up_all_state = fields.Boolean(string='是否已经全部下发', readonly=True) machine_tool_id = fields.Many2one('sf.machine_tool.type', string='机床') - machine_tool_name = fields.Char(string='机床名称', default='未知机床', compute='_run_info', readonly=True) - machine_tool_type_id = fields.Char(string='机床型号', default='未知型号', compute='_run_info', readonly=True) - machine_tool_status = fields.Boolean(string='在线状态', compute='_run_info', readonly=True) + # machine_tool_name = fields.Char(string='机床名称', default='未知机床', compute='_run_info', readonly=True) + # machine_tool_type_id = fields.Char(string='机床型号', default='未知型号', compute='_run_info', readonly=True) + # machine_tool_status = fields.Boolean(string='在线状态', compute='_run_info', readonly=True) + # machine_tool_run_status = fields.Selection([('0', '关机中'), ('1', '加工中'), ('2', '加工中'), ('3', '加工中')], + # string='运行状态', compute='_run_info', readonly=True, default='0') + # machine_tool_timestamp = fields.Datetime('时间戳', compute='_run_info', readonly=True) + # machine_tool_time_on = fields.Char('总在线时长', compute='_run_info', readonly=True) + # machine_tool_time_on_now = fields.Char('本次在线时长', compute='_run_info', readonly=True) + # machine_tool_tool_num = fields.Integer('当前刀具', compute='_run_info', readonly=True) + # machine_tool_program = fields.Char('当前程序', compute='_run_info', readonly=True) + # machine_tool_machine_ip = fields.Char('机床IP', compute='_run_info', readonly=True) + # machine_tool_cut_status = fields.Selection([('0', '未切削'), ('1', '切削中'), ('2', '切削中'), ('3', '切削中')], + # string='切削状态', compute='_run_info', readonly=True, default='0') + # machine_tool_compensation_value_x = fields.Char('x补偿值', compute='_run_info', readonly=True) + # machine_tool_compensation_value_y = fields.Char('y补偿值', compute='_run_info', readonly=True) + + machine_tool_name = fields.Char(string='机床名称', default='未知机床', readonly=True) + machine_tool_type_id = fields.Char(string='机床型号', default='未知型号', readonly=True) + machine_tool_status = fields.Boolean(string='在线状态', readonly=True) machine_tool_run_status = fields.Selection([('0', '关机中'), ('1', '加工中'), ('2', '加工中'), ('3', '加工中')], - string='运行状态', compute='_run_info', readonly=True, default='0') - machine_tool_timestamp = fields.Datetime('时间戳', compute='_run_info', readonly=True) - machine_tool_time_on = fields.Char('总在线时长', compute='_run_info', readonly=True) - machine_tool_time_on_now = fields.Char('本次在线时长', compute='_run_info', readonly=True) - machine_tool_tool_num = fields.Integer('当前刀具', compute='_run_info', readonly=True) - machine_tool_program = fields.Char('当前程序', compute='_run_info', readonly=True) - machine_tool_machine_ip = fields.Char('机床IP', compute='_run_info', readonly=True) + string='运行状态', readonly=True, default='0') + machine_tool_timestamp = fields.Datetime('时间戳', readonly=True) + machine_tool_time_on = fields.Char('总在线时长', readonly=True) + machine_tool_time_on_now = fields.Char('本次在线时长', readonly=True) + machine_tool_tool_num = fields.Integer('当前刀具', readonly=True) + machine_tool_program = fields.Char('当前程序', readonly=True) + machine_tool_machine_ip = fields.Char('机床IP', readonly=True) machine_tool_cut_status = fields.Selection([('0', '未切削'), ('1', '切削中'), ('2', '切削中'), ('3', '切削中')], - string='切削状态', compute='_run_info', readonly=True, default='0') - machine_tool_compensation_value_x = fields.Char('x补偿值', compute='_run_info', readonly=True) - machine_tool_compensation_value_y = fields.Char('y补偿值', compute='_run_info', readonly=True) + string='切削状态', readonly=True, default='0') + machine_tool_compensation_value_x = fields.Char('x补偿值', readonly=True) + machine_tool_compensation_value_y = fields.Char('y补偿值', readonly=True) + + # 工单状态 + delivery_records = fields.One2many('delivery.record', 'workorder_id', string="下发记录") @api.depends('equipment_id.timestamp') def _run_info(self): # self.machine_tool_name = '1号机床' - self.machine_tool_name = self.equipment_id.name - self.machine_tool_type_id = self.equipment_id.type_id.name - self.machine_tool_status = self.equipment_id.status - self.machine_tool_run_status = self.equipment_id.run_status - self.machine_tool_timestamp = self.equipment_id.timestamp - self.machine_tool_time_on = self.equipment_id.time_on - self.machine_tool_time_on_now = self.equipment_id.time_on_now - self.machine_tool_tool_num = self.equipment_id.tool_num - self.machine_tool_program = self.equipment_id.program - self.machine_tool_machine_ip = self.equipment_id.machine_ip - self.machine_tool_cut_status = self.equipment_id.cut_status - self.machine_tool_compensation_value_x = self.compensation_value_x - self.machine_tool_compensation_value_y = self.compensation_value_y + # self.machine_tool_name = self.equipment_id.name + # self.machine_tool_type_id = self.equipment_id.type_id.name + # self.machine_tool_status = self.equipment_id.status + # self.machine_tool_run_status = self.equipment_id.run_status + # self.machine_tool_timestamp = self.equipment_id.timestamp + # self.machine_tool_time_on = self.equipment_id.time_on + # self.machine_tool_time_on_now = self.equipment_id.time_on_now + # self.machine_tool_tool_num = self.equipment_id.tool_num + # self.machine_tool_program = self.equipment_id.program + # self.machine_tool_machine_ip = self.equipment_id.machine_ip + # self.machine_tool_cut_status = self.equipment_id.cut_status + # self.machine_tool_compensation_value_x = self.compensation_value_x + # self.machine_tool_compensation_value_y = self.compensation_value_y + pass def compensation(self): diff --git a/sf_machine_connect/views/default_delivery.xml b/sf_machine_connect/views/default_delivery.xml index 0238c736..c9f3c07c 100644 --- a/sf_machine_connect/views/default_delivery.xml +++ b/sf_machine_connect/views/default_delivery.xml @@ -11,6 +11,7 @@ + diff --git a/sf_machine_connect/views/machine_monitor.xml b/sf_machine_connect/views/machine_monitor.xml index d52bead8..c6964659 100644 --- a/sf_machine_connect/views/machine_monitor.xml +++ b/sf_machine_connect/views/machine_monitor.xml @@ -13,294 +13,138 @@ - - + - - - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + - - - - - - - + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - - - - - - - - - - - - - - + + + - - - - - - - - - - - - - - - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/sf_maintenance/models/sf_maintenance_logs.py b/sf_maintenance/models/sf_maintenance_logs.py index 2dc26f7e..7f80e163 100644 --- a/sf_maintenance/models/sf_maintenance_logs.py +++ b/sf_maintenance/models/sf_maintenance_logs.py @@ -10,21 +10,21 @@ class SfMaintenanceLogs(models.Model): name = fields.Char(string='名称') type = fields.Selection([('type1', '类型1'), ('type2', '类型2')], string='类型') brand = fields.Many2one('sf.machine.brand', related='maintenance_equipment_id.brand_id', string='品牌') - maintenance_equipment_id = fields.Many2one('maintenance.equipment', string='设备') + maintenance_equipment_id = fields.Many2one('maintenance.equipment', string='机台号') maintenance_equipment_oee_id = fields.Many2one('maintenance.equipment.oee', string='设备oee') code_location = fields.Char(string='编码位置') fault_type = fields.Selection( [('电气类', '电气类'), ('机械类', '机械类'), ('程序类', '程序类'), ('系统类', '系统类')], string='故障类型') fault_code = fields.Char(string='故障代码') - fault_alarm_info = fields.Char(string='故障报警信息') + fault_alarm_info = fields.Text(string='故障报警信息') alarm_level = fields.Selection([('一级', '一级(严重)'), ('二级', '二级(中等)'), ('三级', '三级(轻微)')], string='报警级别') - alarm_time = fields.Datetime(string='报警时间') + alarm_time = fields.Datetime(string='故障报警时间') alarm_way = fields.Selection([('文本提示报警', '文本提示报警'), ('声光报警', '声光报警'), ('图文报警', '图文报警')], string='报警方式') fault_process = fields.Text(string='故障处理方法') operator = fields.Many2one('res.users', string='处理人') - recovery_time = fields.Datetime(string='复原时间') + recovery_time = fields.Datetime(string='故障消除时间') fault_duration = fields.Float(string='故障时长') note = fields.Text(string='备注') active = fields.Boolean('Active', default=True) diff --git a/sf_maintenance/models/sf_maintenance_oee.py b/sf_maintenance/models/sf_maintenance_oee.py index 6d76ffbe..41301410 100644 --- a/sf_maintenance/models/sf_maintenance_oee.py +++ b/sf_maintenance/models/sf_maintenance_oee.py @@ -7,20 +7,23 @@ class SfMaintenanceEquipmentOEE(models.Model): _description = '设备OEE' name = fields.Char('设备oee') - equipment_id = fields.Many2one('maintenance.equipment', '设备', + equipment_id = fields.Many2one('maintenance.equipment', '机台号', domain="[('category_id.equipment_type', '=', '机床'),('state_zc', '=', '已注册')]") type_id = fields.Many2one('sf.machine_tool.type', '型号', related='equipment_id.type_id') - machine_tool_picture = fields.Binary('设备图片', related='equipment_id.machine_tool_picture') + machine_tool_picture = fields.Binary('设备图片', related='equipment_id.machine_tool_picture') state = fields.Selection( [("正常", "正常"), ("故障停机", "故障停机"), ("计划维保", "计划维保"), ("空闲", "空闲"), ("封存(报废)", "封存(报废)")], default='正常', string="机床状态", related='equipment_id.state') - run_time = fields.Float('正常运行总时长(h)') - equipment_time = fields.Float('总时长(h)') - done_nums = fields.Integer('累计加工总件数') - utilization_rate = fields.Char('开动率') - fault_time = fields.Float('故障停机总时长(h)') + run_time = fields.Float('加工时长(h)') + equipment_time = fields.Float('开机时长(h)') + done_nums = fields.Integer('加工件数') + utilization_rate = fields.Char('可用率') + fault_time = fields.Float('故障时长') fault_nums = fields.Integer('故障次数') + # 故障率 + fault_rate = fields.Char('故障率') + # 设备故障日志 sf_maintenance_logs_ids = fields.One2many('sf.maintenance.logs', 'maintenance_equipment_oee_id', '设备故障日志', related='equipment_id.sf_maintenance_logs_ids') oee_logs = fields.One2many('maintenance.equipment.oee.logs', 'equipment_oee_id', string='运行日志') @@ -38,12 +41,52 @@ class SfMaintenanceEquipmentOEELog(models.Model): _name = 'maintenance.equipment.oee.logs' _description = '设备运行日志' - name = fields.Char('运行日志') - run_time = fields.Datetime('时间') - state = fields.Selection([("开机", "开机"), ("关机", "关机"), ("等待", "等待"), ("开始加工", "开始加工"), - ("结束加工", "结束加工"), ("故障", "故障"), - ("检修", "检修"), ("保养", "保养")], default="", string="事件/状态") - workorder_id = fields.Char('加工订单') - time = fields.Char('持续时长') - color = fields.Char('颜色', default=1) + equipment_id = fields.Many2one('maintenance.equipment', '机台号') + equipment_code = fields.Char('设备编码') + name = fields.Char('设备名称', readonly='True') + machine_tool_picture = fields.Binary('设备图片') + type_id = fields.Many2one('sf.machine_tool.type', '品牌型号') + state = fields.Selection([("加工", "加工"), ("关机", "关机"), ("待机", "待机"), ("故障", "故障"), + ("检修", "检修"), ("保养", "保养")], default="", string="实时状态") + online_time = fields.Char('开机时长') + + offline_time = fields.Char('关机时长') + offline_nums = fields.Integer('关机次数') + # 待机时长 + + idle_time = fields.Char('待机时长') + + # 待机率 + idle_rate = fields.Char('待机率') + + work_time = fields.Char('加工时长') + work_rate = fields.Char('可用率') + fault_time = fields.Char('故障时长') + fault_rate = fields.Char('故障率') + fault_nums = fields.Integer('故障次数') + + detail_ids = fields.One2many('maintenance.equipment.oee.log.detail', 'log_id', string='日志详情') + + # maintenance_time = fields.Char('维保时长') + # work_nums = fields.Integer('加工件数') equipment_oee_id = fields.Many2one('maintenance.equipment.oee', '设备OEE') + + @api.onchange('equipment_id') + def get_name(self): + self.name = self.equipment_id.name + self.equipment_code = self.equipment_id.code + + +# 设备运行日志详情 +class SfMaintenanceEquipmentOEELogDetail(models.Model): + _name = 'maintenance.equipment.oee.log.detail' + _description = '设备运行日志详情' + + sequence = fields.Integer('序号') + time = fields.Datetime('时间') + state = fields.Selection([("加工", "加工"), ("关机", "关机"), ("待机", "待机"), ("故障", "故障"), + ("检修", "检修"), ("保养", "保养")], default="", string="事件/状态") + production_id = fields.Many2one('mrp.production', '加工工单') + + log_id = fields.Many2one('maintenance.equipment.oee.logs', '日志') + diff --git a/sf_maintenance/security/ir.model.access.csv b/sf_maintenance/security/ir.model.access.csv index abbd4878..ae18b36f 100644 --- a/sf_maintenance/security/ir.model.access.csv +++ b/sf_maintenance/security/ir.model.access.csv @@ -67,3 +67,6 @@ access_sf_cutting_tool_type_admin_sf_group_equipment_user,sf_cutting_tool_type_a access_sf_cutting_tool_type_group_purchase_director_sf_group_equipment_user,sf_cutting_tool_type_group_purchase_director,sf_base.model_sf_cutting_tool_type,sf_maintenance.sf_group_equipment_user,1,0,0,0 access_sf_cutting_tool_type_group_sale_director_sf_group_equipment_user,sf_cutting_tool_type_group_sale_director,sf_base.model_sf_cutting_tool_type,sf_maintenance.sf_group_equipment_user,1,0,0,0 access_sf_cutting_tool_type_group_plan_director_sf_group_equipment_user,sf_cutting_tool_type_group_plan_director,sf_base.model_sf_cutting_tool_type,sf_maintenance.sf_group_equipment_user,1,0,0,0 + +access_maintenance_equipment_oee_logs,maintenance_equipment_oee_logs,model_maintenance_equipment_oee_logs,sf_maintenance.sf_group_equipment_manager,1,1,1,1 +access_maintenance_equipment_oee_log_detail,maintenance_equipment_oee_log_detail,model_maintenance_equipment_oee_log_detail,sf_maintenance.sf_group_equipment_manager,1,1,1,1 \ No newline at end of file diff --git a/sf_maintenance/views/maintenance_equipment_oee_views.xml b/sf_maintenance/views/maintenance_equipment_oee_views.xml index 091e3c47..9370fbbc 100644 --- a/sf_maintenance/views/maintenance_equipment_oee_views.xml +++ b/sf_maintenance/views/maintenance_equipment_oee_views.xml @@ -14,6 +14,7 @@ + @@ -49,19 +50,19 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/sf_maintenance/views/maintenance_logs_views.xml b/sf_maintenance/views/maintenance_logs_views.xml index 087f6ffb..0d172285 100644 --- a/sf_maintenance/views/maintenance_logs_views.xml +++ b/sf_maintenance/views/maintenance_logs_views.xml @@ -7,22 +7,20 @@ sf.maintenance.logs - - + - - - + + - + @@ -45,25 +43,32 @@ - - - + + - - - - - - - + - - - - - + + + + + + + + + + + + + + + + + + + @@ -100,6 +105,246 @@ + + + maintenance.logs.run.tree + maintenance.equipment.oee.logs + + + + + + + + + maintenance.logs.run.form + maintenance.equipment.oee.logs + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+

+ +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + maintenance.logs.run.detail.tree + maintenance.equipment.oee.log.detail + + + + + + + + + + + + maintenance.logs.run.detail.form + maintenance.equipment.oee.log.detail + +
+ + + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 设备运行日志 + ir.actions.act_window + maintenance.equipment.oee.logs + + tree,form + + + +

+ 设备运行日志 +

+
+
+ + + + diff --git a/sf_manufacturing/__manifest__.py b/sf_manufacturing/__manifest__.py index 54538a7d..39da4482 100644 --- a/sf_manufacturing/__manifest__.py +++ b/sf_manufacturing/__manifest__.py @@ -14,9 +14,12 @@ 'data': [ 'data/stock_data.xml', 'data/empty_racks_data.xml', + 'data/panel_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/stock_lot_views.xml', 'views/mrp_production_addional_change.xml', diff --git a/sf_manufacturing/controllers/controllers.py b/sf_manufacturing/controllers/controllers.py index 9f76f065..f5a55db2 100644 --- a/sf_manufacturing/controllers/controllers.py +++ b/sf_manufacturing/controllers/controllers.py @@ -8,7 +8,7 @@ from odoo.http import request class Manufacturing_Connect(http.Controller): - @http.route('/AutoDeviceApi/GetWoInfo', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False, + @http.route('/AutoDeviceApi/GetWoInfo', type='json', auth='none', methods=['GET', 'POST'], csrf=False, cors="*") def get_Work_Info(self, **kw): """ @@ -25,7 +25,8 @@ class Manufacturing_Connect(http.Controller): {'content': ret, 'name': 'AutoDeviceApi/GetWoInfo'}) logging.info('RfidCode:%s' % ret['RfidCode']) if 'RfidCode' in ret: - workorder = request.env['mrp.workorder'].sudo().search([('rfid_code', '=', ret['RfidCode'])]) + workorder = request.env['mrp.workorder'].sudo().search( + [('rfid_code', '=', ret['RfidCode']), ('state', '!=', 'rework')]) if workorder: for item in workorder: res['Datas'].append({ @@ -102,7 +103,7 @@ class Manufacturing_Connect(http.Controller): logging.info('get_ShiftPlan error:%s' % e) return json.JSONEncoder().encode(res) - @http.route('/AutoDeviceApi/QcCheck', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False, + @http.route('/AutoDeviceApi/QcCheck', type='json', auth='none', methods=['GET', 'POST'], csrf=False, cors="*") def get_qcCheck(self, **kw): """ @@ -122,7 +123,8 @@ class Manufacturing_Connect(http.Controller): logging.info('RfidCode:%s' % ret['RfidCode']) if 'RfidCode' in ret: workorder = request.env['mrp.workorder'].sudo().search( - [('routing_type', '=', '装夹预调'), ('rfid_code', '=', ret['RfidCode'])], limit=1, order='id asc') + [('routing_type', '=', '装夹预调'), ('rfid_code', '=', ret['RfidCode']), ('state', '!=', 'rework')], + limit=1, order='id asc') if workorder: for item in workorder: if item.material_center_point: @@ -143,7 +145,7 @@ class Manufacturing_Connect(http.Controller): logging.info('get_qcCheck error:%s' % e) return json.JSONEncoder().encode(res) - @http.route('/AutoDeviceApi/FeedBackStart', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False, + @http.route('/AutoDeviceApi/FeedBackStart', type='json', auth='none', methods=['GET', 'POST'], csrf=False, cors="*") def button_Work_START(self, **kw): """ @@ -163,7 +165,7 @@ class Manufacturing_Connect(http.Controller): equipment_id = ret["DeviceId"] workorder = request.env['mrp.workorder'].sudo().search( [('production_id', '=', production_id), ('routing_type', '=', routing_type), - ('rfid_code', '!=', False)], limit=1) + ('rfid_code', '!=', False), ('state', '!=', 'rework')], limit=1) if not workorder: res = {'Succeed': False, 'ErrorCode': 202, 'Error': '该工单不存在'} return json.JSONEncoder().encode(res) @@ -191,7 +193,7 @@ class Manufacturing_Connect(http.Controller): logging.info('button_Work_START error:%s' % e) return json.JSONEncoder().encode(res) - @http.route('/AutoDeviceApi/FeedBackEnd', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False, + @http.route('/AutoDeviceApi/FeedBackEnd', type='json', auth='none', methods=['GET', 'POST'], csrf=False, cors="*") def button_Work_End(self, **kw): """ @@ -211,7 +213,7 @@ class Manufacturing_Connect(http.Controller): routing_type = ret['CraftId'] workorder = request.env['mrp.workorder'].sudo().search( [('production_id', '=', production_id), ('routing_type', '=', routing_type), - ('rfid_code', '!=', False)], limit=1) + ('rfid_code', '!=', False), ('state', '!=', 'rework')], limit=1) if not workorder: res = {'Succeed': False, 'ErrorCode': 202, 'Error': '该工单不存在'} return json.JSONEncoder().encode(res) @@ -220,28 +222,29 @@ class Manufacturing_Connect(http.Controller): return json.JSONEncoder().encode(res) # workorder.write({'date_finished': datetime.now()}) if ret['IsComplete'] is True: - workorder.button_finish() - # workorder.process_state = '待解除装夹' - # workorder.sudo().production_id.process_state = '待解除装夹' + workorder.write({'date_finished': datetime.now()}) - # 根据工单的实际结束时间修改排程单的结束时间、状态,同时修改销售订单的状态 - # if workorder.date_finished: - # request.env['sf.production.plan'].sudo().search([('production_id', '=', production_id)]).write( - # {'actual_end_time': workorder.date_finished, - # 'state': 'finished'}) - # production_obj = request.env['mrp.production'].sudo().search([('name', '=', production_id)]) - # if production_obj: - # production_obj.sudo().work_order_state = '已完成' - # production_obj.write({'state': 'done'}) - # request.env['sale.order'].sudo().search( - # [('name', '=', production_obj.origin)]).write({'schedule_status': 'to deliver'}) + # workorder.process_state = '待解除装夹' + # workorder.sudo().production_id.process_state = '待解除装夹' + + # 根据工单的实际结束时间修改排程单的结束时间、状态,同时修改销售订单的状态 + # if workorder.date_finished: + # request.env['sf.production.plan'].sudo().search([('production_id', '=', production_id)]).write( + # {'actual_end_time': workorder.date_finished, + # 'state': 'finished'}) + # production_obj = request.env['mrp.production'].sudo().search([('name', '=', production_id)]) + # if production_obj: + # production_obj.sudo().work_order_state = '已完成' + # production_obj.write({'state': 'done'}) + # request.env['sale.order'].sudo().search( + # [('name', '=', production_obj.origin)]).write({'schedule_status': 'to deliver'}) except Exception as e: res = {'Succeed': False, 'ErrorCode': 202, 'Error': e} logging.info('button_Work_End error:%s' % e) return json.JSONEncoder().encode(res) - @http.route('/AutoDeviceApi/PartQualityInspect', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False, + @http.route('/AutoDeviceApi/PartQualityInspect', type='json', auth='none', methods=['GET', 'POST'], csrf=False, cors="*") def PartQualityInspect(self, **kw): """ @@ -259,7 +262,8 @@ class Manufacturing_Connect(http.Controller): production_id = ret['BillId'] routing_type = ret['CraftId'] workorder = request.env['mrp.workorder'].sudo().search( - [('production_id', '=', production_id), ('routing_type', '=', routing_type)], limit=1) + [('production_id', '=', production_id), ('routing_type', '=', routing_type), ('state', '!=', 'rework')], + limit=1) if workorder: # workorder.test_results = ret['Quality'] logging.info('制造订单:%s' % workorder.production_id.name) @@ -299,7 +303,7 @@ class Manufacturing_Connect(http.Controller): logging.info('PartQualityInspect error:%s' % e) return json.JSONEncoder().encode(res) - @http.route('/AutoDeviceApi/CMMProgDolod', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False, + @http.route('/AutoDeviceApi/CMMProgDolod', type='json', auth='none', methods=['GET', 'POST'], csrf=False, cors="*") def CMMProgDolod(self, **kw): """ @@ -317,7 +321,7 @@ class Manufacturing_Connect(http.Controller): if 'RfidCode' in ret: logging.info('RfidCode:%s' % ret['RfidCode']) workorder = request.env['mrp.workorder'].sudo().search( - [('rfid_code', '=', ret['RfidCode']), ('routing_type', '=', 'CNC加工')]) + [('rfid_code', '=', ret['RfidCode']), ('routing_type', '=', 'CNC加工'), ('state', '!=', 'rework')]) if workorder: for item in workorder.cmm_ids: if item.program_create_date is not False: @@ -339,7 +343,7 @@ class Manufacturing_Connect(http.Controller): logging.info('CMMProgDolod error:%s' % e) return json.JSONEncoder().encode(res) - @http.route('/AutoDeviceApi/NCProgDolod', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False, + @http.route('/AutoDeviceApi/NCProgDolod', type='json', auth='none', methods=['GET', 'POST'], csrf=False, cors="*") def NCProgDolod(self, **kw): """ @@ -357,7 +361,7 @@ class Manufacturing_Connect(http.Controller): if 'RfidCode' in ret: logging.info('RfidCode:%s' % ret['RfidCode']) workorder = request.env['mrp.workorder'].sudo().search( - [('rfid_code', '=', ret['RfidCode']), ('routing_type', '=', 'CNC加工')]) + [('rfid_code', '=', ret['RfidCode']), ('routing_type', '=', 'CNC加工'), ('state', '!=', 'rework')]) if workorder: for item in workorder.cnc_ids: res['Datas'].append({ @@ -380,7 +384,7 @@ class Manufacturing_Connect(http.Controller): logging.info('NCProgDolod error:%s' % e) return json.JSONEncoder().encode(res) - @http.route('/AutoDeviceApi/LocationChange', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False, + @http.route('/AutoDeviceApi/LocationChange', type='json', auth='none', methods=['GET', 'POST'], csrf=False, cors="*") def LocationChange(self, **kw): """ @@ -438,7 +442,7 @@ class Manufacturing_Connect(http.Controller): logging.info('LocationChange error:%s' % e) return json.JSONEncoder().encode(res) - @http.route('/AutoDeviceApi/AGVToProduct', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False, + @http.route('/AutoDeviceApi/AGVToProduct', type='json', auth='none', methods=['GET', 'POST'], csrf=False, cors="*") def AGVToProduct(self, **kw): """ @@ -466,7 +470,7 @@ class Manufacturing_Connect(http.Controller): if rfid_code is not None: domain = [ ('rfid_code', '=', rfid_code), - ('routing_type', '=', 'CNC加工') + ('routing_type', '=', 'CNC加工'), ('state', '!=', 'rework') ] workorder = request.env['mrp.workorder'].sudo().search(domain, order='id asc') if workorder: @@ -475,14 +479,16 @@ class Manufacturing_Connect(http.Controller): logging.info( '工单产线状态:%s' % order.production_line_state) panel_workorder = request.env['mrp.workorder'].sudo().search( - [('rfid_code', '=', rfid_code), + [('rfid_code', '=', rfid_code), ('state', '!=', 'rework'), ('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)]) + ('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: @@ -497,7 +503,7 @@ class Manufacturing_Connect(http.Controller): logging.info('AGVToProduct error:%s' % e) return json.JSONEncoder().encode(res) - @http.route('/AutoDeviceApi/AGVDownProduct', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False, + @http.route('/AutoDeviceApi/AGVDownProduct', type='json', auth='none', methods=['GET', 'POST'], csrf=False, cors="*") def AGVDownProduct(self, **kw): """ @@ -527,7 +533,7 @@ class Manufacturing_Connect(http.Controller): if rfid_code is not None: domain = [ ('rfid_code', '=', rfid_code), - ('routing_type', '=', 'CNC加工') + ('routing_type', '=', 'CNC加工'), ('state', '!=', 'rework') ] workorder = request.env['mrp.workorder'].sudo().search(domain, order='id asc') if workorder: @@ -536,15 +542,19 @@ class Manufacturing_Connect(http.Controller): logging.info( '工单产线状态:%s' % order.production_line_state) panel_workorder = request.env['mrp.workorder'].sudo().search( - [('rfid_code', '=', rfid_code), + [('rfid_code', '=', rfid_code), ('state', '!=', 'rework'), ('processing_panel', '=', order.processing_panel)]) 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)]) - delivery_Arr.append(workpiece_delivery.id) + ('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']} diff --git a/sf_manufacturing/data/panel_data.xml b/sf_manufacturing/data/panel_data.xml new file mode 100644 index 00000000..0bdf4aa3 --- /dev/null +++ b/sf_manufacturing/data/panel_data.xml @@ -0,0 +1,24 @@ + + + + + ZM + + + + FM + + + YC + + + ZC + + + QC + + + HC + + + \ No newline at end of file diff --git a/sf_manufacturing/data/stock_data.xml b/sf_manufacturing/data/stock_data.xml index 07d59708..9220d827 100644 --- a/sf_manufacturing/data/stock_data.xml +++ b/sf_manufacturing/data/stock_data.xml @@ -1,6 +1,16 @@ + + 返工且编程中的制造订单定时获取Cloud编程单状态 + + code + model._cron_get_programming_state() + 3 + minutes + -1 + + 工序编码规则 mrp.routing.workcenter diff --git a/sf_manufacturing/models/mrp_production.py b/sf_manufacturing/models/mrp_production.py index 73782a9c..e6668ecf 100644 --- a/sf_manufacturing/models/mrp_production.py +++ b/sf_manufacturing/models/mrp_production.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- import base64 import logging +import json +import os import re import requests from itertools import groupby @@ -24,6 +26,46 @@ class MrpProduction(models.Model): work_order_state = fields.Selection([('未排', '未排'), ('已排', '已排'), ('已完成', '已完成')], string='工单状态', default='未排') + detection_result_ids = fields.One2many('sf.detection.result', 'production_id', '检测报告') + tool_state = fields.Selection([('0', '正常'), ('1', '缺刀'), ('2', '无效刀')], string='功能刀具状态', default='0', + store=True, compute='_compute_tool_state') + tool_state_remark = fields.Text(string='功能刀具状态备注(缺刀)', compute='_compute_tool_state_remark', store=True) + tool_state_remark2 = fields.Text(string='功能刀具状态备注(无效刀)', readonly=True) + + @api.depends('workorder_ids.tool_state_remark') + def _compute_tool_state_remark(self): + for item in self: + if item.workorder_ids: + workorder_ids = item.workorder_ids.filtered(lambda a: a.state not in ('rework', '返工')) + if workorder_ids.filtered(lambda a: a.tool_state_remark): + work_ids = workorder_ids.filtered(lambda a: a.tool_state == '1' and a.state not in ['rework']) + tool_state_remark = '' + for work_id in work_ids: + if tool_state_remark == '': + tool_state_remark = f"{work_id.tool_state_remark}" + else: + tool_state_remark = f"{tool_state_remark}\n{work_id.tool_state_remark}" + item.tool_state_remark = tool_state_remark + else: + item.tool_state_remark = False + + @api.depends('workorder_ids.tool_state') + def _compute_tool_state(self): + for item in self: + if item.workorder_ids: + tool_state = item.tool_state + workorder_ids = item.workorder_ids.filtered(lambda a: a.state not in ('rework', '返工')) + if workorder_ids.filtered(lambda a: a.tool_state == '2'): + item.tool_state = '2' + elif workorder_ids.filtered(lambda a: a.tool_state == '1'): + item.tool_state = '1' + else: + item.tool_state = '0' + if tool_state == '2' and item.tool_state != '2': + item.detection_result_ids.filtered( + lambda a: a.detailed_reason == '无效功能刀具' and a.handle_result == '待处理').write( + {'handle_result': '已处理'}) + # state = fields.Selection(selection_add=[ # ('pending_scheduling', '待排程'), # ('pending_processing', '待加工'), @@ -34,9 +76,10 @@ class MrpProduction(models.Model): ('confirmed', '待排程'), ('pending_cam', '待加工'), ('progress', '加工中'), + ('rework', '返工'), ('to_close', 'To Close'), ('done', 'Done'), - ('cancel', 'Cancelled')], 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" @@ -51,10 +94,13 @@ class MrpProduction(models.Model): programming_no = fields.Char('编程单号') work_state = fields.Char('业务状态') programming_state = fields.Selection( - [('编程中', '编程中'), ('已编程', '已编程')], string='编程状态', tracking=True) + [('编程中', '编程中'), ('已编程', '已编程'), ('已编程未下发', '已编程未下发'), ('已下发', '已下发')], + string='编程状态', + tracking=True) glb_file = fields.Binary("glb模型文件") production_line_id = fields.Many2one('sf.production.line', string='生产线', tracking=True) plan_start_processing_time = fields.Datetime('计划开始加工时间') + is_rework = fields.Boolean(string='是否返工', default=False) # production_line_state = fields.Selection( # [('待上产线', '待上产线'), ('已上产线', '已上产线'), ('已下产线', '已下产线')], # string='上/下产线', default='待上产线', tracking=True) @@ -75,10 +121,10 @@ class MrpProduction(models.Model): part_drawing = fields.Binary('零件图纸') manual_quotation = fields.Boolean('人工编程', default=False, readonly=True) - rework_production = fields.Many2one('mrp.production', string='返工的制造订单') + is_scrap = fields.Boolean('是否报废', default=False) @api.depends( - 'move_raw_ids.state', 'move_raw_ids.quantity_done', 'move_finished_ids.state', + 'move_raw_ids.state', 'move_raw_ids.quantity_done', 'move_finished_ids.state', 'tool_state', 'workorder_ids.state', 'product_qty', 'qty_producing', 'schedule_state') def _compute_state(self): for production in self: @@ -114,12 +160,33 @@ class MrpProduction(models.Model): if ( production.state == 'to_close' or production.state == 'progress') and production.schedule_state == '未排': production.state = 'confirmed' + elif production.state == 'pending_cam' and production.schedule_state == '未排': + production.state = 'confirmed' elif production.state == 'to_close' and production.schedule_state == '已排': production.state = 'pending_cam' if production.state == 'progress': - if all(wo_state not in ('progress', 'done') for wo_state in production.workorder_ids.mapped('state')): + if all(wo_state not in ('progress', 'done', 'rework') for wo_state in + production.workorder_ids.mapped('state')): production.state = 'pending_cam' + if production.is_rework is True: + production.state = 'rework' + # if production.state == 'pending_cam': + # if all(wo_state in 'done' for wo_state in production.workorder_ids.mapped('state')): + # production.state = 'done' + if any( + ( + wo.test_results == '返工' and wo.state == 'done' and production.programming_state in [ + '已编程']) or ( + wo.state == 'rework' and production.programming_state == '编程中') or ( + wo.is_rework is True and wo.state == 'done' and production.programming_state in ['编程中', + '已编程']) + for wo in + production.workorder_ids): + production.state = 'rework' + # 如果制造订单的功能刀具为【无效刀】则制造订单状态改为返工 + if production.tool_state == '2': + production.state = 'rework' def action_check(self): """ @@ -150,27 +217,66 @@ class MrpProduction(models.Model): for production in self: production.maintenance_count = len(production.request_ids) - # 制造订单报废:编程单更新 - def updateCNC(self): + # 获取cloud编程单的状态 + def _cron_get_programming_state(self): try: - res = {'production_no': self.name, 'programming_no': self.programming_no, - 'order_no': self.origin} + if not self: + reproduction = self.env['mrp.production'].search( + [('state', '=', 'rework'), ('programming_state', '=', '编程中'), ('is_rework', '=', True)]) + else: + reproduction = self + if reproduction: + programming_no_set = set([str(item.programming_no) for item in reproduction]) + programming_no = list(programming_no_set) + programming_no_str = ','.join(programming_no) + res = {'programming_no': programming_no_str} + logging.info('res=%s:' % res) + configsettings = self.env['res.config.settings'].get_values() + config_header = Common.get_headers(self, configsettings['token'], configsettings['sf_secret_key']) + url = '/api/intelligent_programming/get_state' + config_url = configsettings['sf_url'] + url + ret = requests.post(config_url, json=res, data=None, headers=config_header) + ret = ret.json() + result = json.loads(ret['result']) + logging.info('cron_get_programming_state-ret:%s' % result) + if result['status'] == 1: + for item in result['programming_list']: + if not self: + for rp in reproduction: + if rp.programming_no == item['programming_no']: + rp.write({'programming_state': '已编程未下发' if item[ + 'programming_state'] == '已编程' else '编程中'}) + logging.info('rp:%s' % rp.name) + + else: + return item + + else: + raise UserError(ret['message']) + except Exception as e: + logging.info('cron_get_programming_state error:%s' % e) + + # 编程单更新 + def update_programming_state(self): + try: + res = {'programming_no': self.programming_no, + 'manufacturing_type': 'rework' if self.is_scrap is False else 'scrap'} logging.info('res=%s:' % res) configsettings = self.env['res.config.settings'].get_values() config_header = Common.get_headers(self, configsettings['token'], configsettings['sf_secret_key']) - url = '/api/intelligent_programming/update_intelligent_programmings' + url = '/api/intelligent_programming/reset_state_again' config_url = configsettings['sf_url'] + url - res['token'] = configsettings['token'] - ret = requests.post(config_url, json={}, data=res, headers=config_header) + ret = requests.post(config_url, json=res, data=None, headers=config_header) ret = ret.json() - logging.info('updateCNC-ret:%s' % ret) - if ret['status'] == 1: - self.write({'work_state': '已编程'}) + result = json.loads(ret['result']) + logging.info('update_programming_state-ret:%s' % result) + if result['status'] == 1: + self.write({'is_rework': True}) else: raise UserError(ret['message']) except Exception as e: - logging.info('updateCNC error:%s' % e) - raise UserError("更新程单失败,请联系管理员") + logging.info('update_programming_state error:%s' % e) + raise UserError("更新编程单状态失败,请联系管理员") # cnc程序获取 def fetchCNC(self, production_names): @@ -196,7 +302,7 @@ class MrpProduction(models.Model): 'material_type_code': self.env['sf.materials.model'].search( [('id', '=', cnc.product_id.materials_type_id.id)]).materials_no, 'machining_processing_panel': cnc.product_id.model_processing_panel, - 'machining_precision': cnc.product_id.model_machining_precision, + 'machining_precision': '', 'embryo_long': cnc.product_id.bom_ids.bom_line_ids.product_id.length, 'embryo_height': cnc.product_id.bom_ids.bom_line_ids.product_id.height, 'embryo_width': cnc.product_id.bom_ids.bom_line_ids.product_id.width, @@ -429,28 +535,6 @@ class MrpProduction(models.Model): for workorder in production.workorder_ids: workorder.duration_expected = workorder._get_duration_expected() - # 在之前的销售单上重新生成制造订单 - def create_production1_values(self, production): - production_values_str = {'origin': production.origin, - 'product_id': production.product_id.id, - 'product_description_variants': production.product_description_variants, - 'product_qty': production.product_qty, - 'product_uom_id': production.product_uom_id.id, - 'location_src_id': production.location_src_id.id, - 'location_dest_id': production.location_dest_id.id, - 'bom_id': production.bom_id.id, - 'date_deadline': production.date_deadline, - 'date_planned_start': production.date_planned_start, - 'date_planned_finished': production.date_planned_finished, - 'procurement_group_id': False, - 'propagate_cancel': production.propagate_cancel, - 'orderpoint_id': production.orderpoint_id.id, - 'picking_type_id': production.picking_type_id.id, - 'company_id': production.company_id.id, - 'move_dest_ids': production.move_dest_ids.ids, - 'user_id': production.user_id.id} - return production_values_str - # 工单排序 def _reset_work_order_sequence1(self, k): for rec in self: @@ -519,70 +603,90 @@ class MrpProduction(models.Model): def _reset_work_order_sequence(self): for rec in self: - sequence_list = {} + workorder_ids = rec.workorder_ids.filtered(lambda item: item.state in ('返工', 'rework')) # 产品模型类型 model_type_id = rec.product_id.product_model_type_id # 产品加工面板 model_processing_panel = rec.product_id.model_processing_panel - if model_type_id: - if model_processing_panel: - tmpl_num = 1 - panel_list = model_processing_panel.split(',') - for panel in panel_list: - panel_sequence_list = {} - # 成品工序 - product_routing_tmpl_ids = model_type_id.product_routing_tmpl_ids - if product_routing_tmpl_ids: - for tmpl_id in product_routing_tmpl_ids: - panel_sequence_list.update({tmpl_id.route_workcenter_id.name: tmpl_num}) - tmpl_num += 1 - sequence_list.update({panel: panel_sequence_list}) - # 表面工艺工序 - # 模型类型的表面工艺工序模版 - surface_tmpl_ids = model_type_id.surface_technics_routing_tmpl_ids - # 产品选择的表面工艺 - model_process_parameters_ids = rec.product_id.model_process_parameters_ids - process_dict = {} - if model_process_parameters_ids: - for process_parameters_id in model_process_parameters_ids: - process_id = process_parameters_id.process_id - for surface_tmpl_id in surface_tmpl_ids: - if process_id == surface_tmpl_id.route_workcenter_id.surface_technics_id: - surface_tmpl_name = surface_tmpl_id.route_workcenter_id.name - process_dict.update({int(process_id.category_id.code): '%s-%s' % ( - surface_tmpl_name, process_parameters_id.name)}) - process_list = sorted(process_dict.keys()) - for process_num in process_list: - sequence_list.update({process_dict.get(process_num): tmpl_num}) - tmpl_num += 1 - # 坯料工序 - tmpl_num = 1 - embryo_routing_tmpl_ids = model_type_id.embryo_routing_tmpl_ids - if embryo_routing_tmpl_ids: - for tmpl_id in embryo_routing_tmpl_ids: - sequence_list.update({tmpl_id.route_workcenter_id.name: tmpl_num}) + if not workorder_ids: + sequence_list = {} + if model_type_id: + if model_processing_panel: + tmpl_num = 1 + panel_list = model_processing_panel.split(',') + for panel in panel_list: + panel_sequence_list = {} + # 成品工序 + product_routing_tmpl_ids = model_type_id.product_routing_tmpl_ids + if product_routing_tmpl_ids: + for tmpl_id in product_routing_tmpl_ids: + panel_sequence_list.update({tmpl_id.route_workcenter_id.name: tmpl_num}) + tmpl_num += 1 + sequence_list.update({panel: panel_sequence_list}) + # 表面工艺工序 + # 模型类型的表面工艺工序模版 + surface_tmpl_ids = model_type_id.surface_technics_routing_tmpl_ids + # 产品选择的表面工艺 + model_process_parameters_ids = rec.product_id.model_process_parameters_ids + process_dict = {} + if model_process_parameters_ids: + for process_parameters_id in model_process_parameters_ids: + process_id = process_parameters_id.process_id + for surface_tmpl_id in surface_tmpl_ids: + if process_id == surface_tmpl_id.route_workcenter_id.surface_technics_id: + surface_tmpl_name = surface_tmpl_id.route_workcenter_id.name + process_dict.update({int(process_id.category_id.code): '%s-%s' % ( + surface_tmpl_name, process_parameters_id.name)}) + process_list = sorted(process_dict.keys()) + for process_num in process_list: + sequence_list.update({process_dict.get(process_num): tmpl_num}) tmpl_num += 1 + # 坯料工序 + tmpl_num = 1 + embryo_routing_tmpl_ids = model_type_id.embryo_routing_tmpl_ids + if embryo_routing_tmpl_ids: + for tmpl_id in embryo_routing_tmpl_ids: + sequence_list.update({tmpl_id.route_workcenter_id.name: tmpl_num}) + tmpl_num += 1 + else: + raise ValidationError('该产品【加工面板】为空!') else: - raise ValidationError('该产品【加工面板】为空!') + raise ValidationError('该产品没有选择【模版类型】!') - else: - raise ValidationError('该产品没有选择【模版类型】!') - - for work in rec.workorder_ids: - if sequence_list.get(work.name): - work.sequence = sequence_list[work.name] - elif sequence_list.get(work.processing_panel): - processing_panel = sequence_list.get(work.processing_panel) - if processing_panel.get(work.name): - work.sequence = processing_panel[work.name] + for work in rec.workorder_ids: + if sequence_list.get(work.name): + work.sequence = sequence_list[work.name] + elif sequence_list.get(work.processing_panel): + processing_panel = sequence_list.get(work.processing_panel) + if processing_panel.get(work.name): + work.sequence = processing_panel[work.name] + else: + raise ValidationError('工序【%s】在产品选择的模版类型中不存在!' % work.name) else: raise ValidationError('工序【%s】在产品选择的模版类型中不存在!' % work.name) - else: - raise ValidationError('工序【%s】在产品选择的模版类型中不存在!' % work.name) - # if work.name == '获取CNC加工程序': - # work.button_start() - # #work.fetchCNC() - # work.button_finish() + # 当单个面触发返工时,将新生成的工单插入到返工工单下方,并且后面的所以工单工序重排 + elif rec.workorder_ids.filtered(lambda item: item.sequence == 0): + # 获取新增的返工工单 + work_ids = rec.workorder_ids.filtered(lambda item: item.sequence == 0) + # 获取当前返工面最后一个工单工序 + sequence_max = sorted( + rec.workorder_ids.filtered(lambda item: item.processing_panel == work_ids[0].processing_panel), + key=lambda item: item.sequence, reverse=True)[0].sequence + # 对当前返工工单之后的工单工序进行重排 + work_order_ids = rec.workorder_ids.filtered(lambda item: item.sequence > sequence_max) + for work_id in work_order_ids: + work_id.sequence = work_id.sequence + 3 + # 生成新增的返工工单的工序 + # 成品工序 + panel_sequence_list = {} + product_routing_tmpl_ids = model_type_id.product_routing_tmpl_ids + if product_routing_tmpl_ids: + for tmpl_id in product_routing_tmpl_ids: + sequence_max += 1 + panel_sequence_list.update({tmpl_id.route_workcenter_id.name: sequence_max}) + for work_id in work_ids: + if panel_sequence_list.get(work_id.name): + work_id.sequence = panel_sequence_list[work_id.name] # 创建工单并进行排序 def _create_workorder(self, item): @@ -631,9 +735,11 @@ class MrpProduction(models.Model): for production in self: logging.info('qty_produced:%s' % production.qty_produced) + if production.qty_produced == 0.0: + production.qty_produced = 1.0 production.write({ 'date_finished': fields.Datetime.now(), - 'product_qty': production.product_qty if production.qty_produced < 1.0 else production.qty_produced, + 'product_qty': production.qty_produced, 'priority': '0', 'is_locked': True, 'state': 'done', @@ -683,3 +789,317 @@ class MrpProduction(models.Model): 'view_mode': 'tree,form', }) return action + + # 返工 + def button_rework(self): + cloud_programming = None + if self.programming_state in ['已编程']: + cloud_programming = self._cron_get_programming_state() + logging.info('cloud_programming_state:%s' % cloud_programming['programming_state']) + logging.info('programming_state:%s' % self.programming_state) + return { + 'name': _('返工'), + 'type': 'ir.actions.act_window', + 'view_mode': 'form', + 'res_model': 'sf.rework.wizard', + 'target': 'new', + 'context': { + 'default_production_id': self.id, + 'default_reprogramming_num': cloud_programming['reprogramming_num'], + 'default_programming_state': cloud_programming['programming_state'], + 'default_is_reprogramming': True if cloud_programming['programming_state'] in ['已下发'] else False + } + } + + # 更新程序 + def do_update_program(self): + program_production = self + if len(program_production) >= 1: + same_product_id = None + is_not_same_product = 0 + for item in program_production: + if same_product_id is None: + same_product_id = item.product_id + if item.product_id != same_product_id: + is_not_same_product += 1 + if item.state != "rework" and item.programming_state != "已编程未下发": + raise UserError("请选择状态为返工且已编程未下发的制造订单") + if is_not_same_product >= 1: + raise UserError("您选择的记录中含有其他产品的制造订单,请选择同一产品的制造订单") + grouped_program_ids = {k: list(g) for k, g in groupby(program_production, key=lambda x: x.programming_no)} + program_to_production_names = {} + for programming_no, program_production in grouped_program_ids.items(): + program_to_production_names[programming_no] = [production.name for production in program_production] + for production in self: + if production.programming_no in program_to_production_names: + productions_not_delivered = self.env['mrp.production'].search( + [('programming_no', '=', production.programming_no), ('programming_state', '=', '已编程未下发')]) + rework_workorder = production.workorder_ids.filtered(lambda m: m.state == 'rework') + if rework_workorder: + for rework_item in rework_workorder: + pending_workorder = production.workorder_ids.filtered( + lambda m1: m1.state in [ + 'pending'] and m1.processing_panel == rework_item.processing_panel and m1.routing_type == 'CNC加工') + if not pending_workorder.cnc_ids: + production.get_new_program(rework_item.processing_panel) + # production.write({'state': 'progress', 'programming_state': '已编程', 'is_rework': False}) + productions_not_delivered.write( + {'state': 'progress', 'programming_state': '已编程', 'is_rework': False}) + + # 从cloud获取重新编程过的最新程序 + def get_new_program(self, processing_panel): + try: + res = {'programming_no': self.programming_no, 'processing_panel': processing_panel} + configsettings = self.env['res.config.settings'].get_values() + config_header = Common.get_headers(self, configsettings['token'], configsettings['sf_secret_key']) + url = '/api/intelligent_programming/get_new_program' + config_url = configsettings['sf_url'] + url + r = requests.post(config_url, json=res, data=None, headers=config_header) + r = r.json() + result = json.loads(r['result']) + if result['status'] == 1: + program_path_tmp_panel = os.path.join('/tmp', result['folder_name'], 'return', processing_panel) + if os.path.exists(program_path_tmp_panel): + files_r = os.listdir(program_path_tmp_panel) + if files_r: + for file_name in files_r: + file_path = os.path.join(program_path_tmp_panel, file_name) + os.remove(file_path) + download_state = self.env['sf.cnc.processing'].download_file_tmp(result['folder_name'], + processing_panel) + if download_state is False: + raise UserError('编程单号为%s的CNC程序文件从FTP拉取失败' % (self.programming_no)) + productions = self.env['mrp.production'].search( + [('programming_no', '=', self.programming_no), ('state', 'not in', ('cancel', 'done'))]) + if productions: + for production in productions: + panel_workorder = production.workorder_ids.filtered(lambda + pw: pw.processing_panel == processing_panel and pw.routing_type == 'CNC加工' and pw.state not in ( + 'rework', 'done')) + if panel_workorder: + if panel_workorder.cmm_ids: + panel_workorder.cmm_ids.sudo().unlink() + if panel_workorder.cnc_ids: + panel_workorder.cnc_ids.sudo().unlink() + self.env['sf.cam.work.order.program.knife.plan'].sudo().unlink_cam_plan( + production) + # program_path_tmp_panel = os.path.join('C://Users//43484//Desktop//fsdownload//test', + # processing_panel) + logging.info('program_path_tmp_panel:%s' % program_path_tmp_panel) + files_panel = os.listdir(program_path_tmp_panel) + if files_panel: + for file in files_panel: + file_extension = os.path.splitext(file)[1] + if file_extension.lower() == '.pdf': + panel_file_path = os.path.join(program_path_tmp_panel, file) + logging.info('panel_file_path:%s' % panel_file_path) + panel_workorder.write( + {'cnc_ids': panel_workorder.cnc_ids.sudo()._json_cnc_processing(processing_panel, + result), + 'cmm_ids': panel_workorder.cmm_ids.sudo()._json_cmm_program(processing_panel, result), + 'cnc_worksheet': base64.b64encode(open(panel_file_path, 'rb').read())}) + logging.info('len(cnc_worksheet):%s' % len(panel_workorder.cnc_worksheet)) + pre_workorder = production.workorder_ids.filtered(lambda + ap: ap.routing_type == '装夹预调' and ap.processing_panel == processing_panel and ap.state not in ( + 'rework', 'done')) + if pre_workorder: + pre_workorder.write( + {'processing_drawing': base64.b64encode(open(panel_file_path, 'rb').read())}) + # if production.state == 'rework' and production.programming_state == '已编程未下发': + # production.write( + # {'state': 'progress', 'programming_state': '已编程', 'is_rework': False}) + # logging.info('返工含有已编程未下发的程序更新完成:%s' % production.name) + logging.info('更新程序完成:%s' % production.name) + + else: + raise UserError(result['message']) + except Exception as e: + logging.info('get_new_program error:%s' % e) + raise UserError("从云平台获取最新程序失败,请联系管理员") + + def recreateManufacturing(self): + """ + 重新生成制造订单 + """ + 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: + 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) + 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', + }) + + # 在之前的销售单上重新生成制造订单 + def create_production1_values(self, production, sale_order): + production_values_str = {'origin': production.origin, + 'product_id': production.product_id.id, + 'product_description_variants': production.product_description_variants, + 'product_qty': production.product_qty, + 'product_uom_id': production.product_uom_id.id, + 'location_src_id': production.location_src_id.id, + 'location_dest_id': production.location_dest_id.id, + 'bom_id': production.bom_id.id, + '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, + 'propagate_cancel': production.propagate_cancel, + 'orderpoint_id': production.orderpoint_id.id, + 'picking_type_id': production.picking_type_id.id, + 'company_id': production.company_id.id, + 'move_dest_ids': production.move_dest_ids.ids, + 'user_id': production.user_id.id} + return production_values_str + + +class sf_detection_result(models.Model): + _name = 'sf.detection.result' + _description = "检测结果" + + production_id = fields.Many2one('mrp.production') + processing_panel = fields.Char('加工面') + routing_type = fields.Selection([ + ('装夹预调', '装夹预调'), + ('CNC加工', 'CNC加工')], string="工序类型") + + rework_reason = fields.Selection( + [("programming", "编程"), ("cutter", "刀具"), ("clamping", "装夹"), + ("operate computer", "操机"), + ("technology", "工艺"), ("customer redrawing", "客户改图")], string="原因", tracking=True) + detailed_reason = fields.Text('详细原因') + test_results = fields.Selection([("合格", "合格"), ("返工", "返工"), ("报废", "报废")], + string="检测结果", tracking=True) + test_report = fields.Binary('检测报告', readonly=True) + handle_result = fields.Selection([("待处理", "待处理"), ("已处理", "已处理")], default='', string="处理结果", + tracking=True) + + # 查看检测报告 + def button_look_test_report(self): + return { + 'res_model': 'sf.detection.result', + 'type': 'ir.actions.act_window', + 'res_id': self.id, + 'views': [(self.env.ref('sf_manufacturing.sf_test_report_form').id, 'form')], + # 'view_mode': 'form', + # 'context': { + # 'default_id': self.id + # }, + 'target': 'new' + } + + +class sf_processing_panel(models.Model): + _name = 'sf.processing.panel' + _description = "加工面" + + name = fields.Char('加工面') + active = fields.Boolean('有效', default=True) + + # @api.model + # def name_search(self, name, args=None, operator='ilike', limit=100, name_get_uid=None): + # if self.env.user.has_group('sf_base.group_sf_order_user'): + # if self._context.get('product_id'): + # product = self.env['product.product'].search([('id', '=', self._context.get('product_id'))]) + # if product: + # panel = self.env['sf.processing.panel'].search([('name', 'ilike', 'ZM')]) + # if panel: + # ids = [t.id for t in panel] + # domain = [('id', 'in', ids)] + # else: + # domain = [('id', '=', False)] + # return self._search(domain, limit=limit, access_rights_uid=name_get_uid) + # return super()._name_search(name, args, operator, limit, name_get_uid) diff --git a/sf_manufacturing/models/mrp_workcenter.py b/sf_manufacturing/models/mrp_workcenter.py index 7d70ae5e..64cf2d8d 100644 --- a/sf_manufacturing/models/mrp_workcenter.py +++ b/sf_manufacturing/models/mrp_workcenter.py @@ -5,24 +5,49 @@ from odoo.addons.resource.models.resource import Intervals class ResWorkcenter(models.Model): - _inherit = "mrp.workcenter" + _name = "mrp.workcenter" + _inherit = ['mrp.workcenter', 'mail.thread'] # 生产线显示 production_line_show = fields.Char(string='生产线名称') - equipment_id = fields.Many2one( - 'maintenance.equipment', string="设备", - ) + equipment_id = fields.Many2one('maintenance.equipment', string="设备", tracking=True) production_line_id = fields.Many2one('sf.production.line', string='生产线', related='equipment_id.production_line_id', store=True) - is_process_outsourcing = fields.Boolean('工艺外协') - users_ids = fields.Many2many("res.users", 'users_workcenter') + users_ids = fields.Many2many("res.users", 'users_workcenter', tracking=True) + def write(self, vals): + if 'users_ids' in vals: + old_users = self.users_ids + res = super(ResWorkcenter, self).write(vals) + new_users = self.users_ids + added_users = new_users - old_users + removed_users = old_users - new_users + if added_users or removed_users: + message = "增加 → %s ; 移除 → %s (可操作用户)" % ( + # ','.join(added_users.mapped('name')), ','.join(removed_users.mapped('name'))) + added_users.mapped('name'), removed_users.mapped('name')) + self.message_post(body=message) + return res + return super(ResWorkcenter, self).write(vals) + name = fields.Char('Work Center', related='resource_id.name', store=True, readonly=False, tracking=True) + time_efficiency = fields.Float('Time Efficiency', related='resource_id.time_efficiency', default=100, store=True, + readonly=False, tracking=True) + default_capacity = fields.Float( + 'Capacity', default=1.0, + help="Default number of pieces (in product UoM) that can be produced in parallel (at the same time) at this work center. For example: the capacity is 5 and you need to produce 10 units, then the operation time listed on the BOM will be multiplied by two. However, note that both time before and after production will only be counted once.", + tracking=True) + oee_target = fields.Float( + string='OEE Target', help="Overall Effective Efficiency Target in percentage", default=90, tracking=True) + + time_start = fields.Float('Setup Time', tracking=True) + time_stop = fields.Float('Cleanup Time', tracking=True) + costs_hour = fields.Float(string='Cost per hour', help='Hourly processing cost.', default=0.0, tracking=True) equipment_status = fields.Selection( - [("正常", "正常"), ("故障停机", "故障停机"), ("计划维保", "计划维保"),("空闲", "空闲"),("封存(报废)", "封存(报废)")], + [("正常", "正常"), ("故障停机", "故障停机"), ("计划维保", "计划维保"), ("空闲", "空闲"), ("封存(报废)", "封存(报废)")], string="设备状态", related='equipment_id.state') # @api.depends('equipment_id') diff --git a/sf_manufacturing/models/mrp_workorder.py b/sf_manufacturing/models/mrp_workorder.py index 46bcde47..44c019eb 100644 --- a/sf_manufacturing/models/mrp_workorder.py +++ b/sf_manufacturing/models/mrp_workorder.py @@ -13,13 +13,13 @@ from dateutil.relativedelta import relativedelta # import subprocess from odoo import api, fields, models, SUPERUSER_ID, _ from odoo.addons.sf_base.commons.common import Common -from odoo.exceptions import UserError +from odoo.exceptions import UserError, ValidationError from odoo.addons.sf_mrs_connect.models.ftp_operate import FtpController class ResMrpWorkOrder(models.Model): _inherit = 'mrp.workorder' - _order = 'id' + _order = 'sequence asc' product_tmpl_name = fields.Char('坯料产品名称', related='production_bom_id.bom_line_ids.product_id.name') @@ -47,9 +47,28 @@ class ResMrpWorkOrder(models.Model): ('切割', '切割'), ('表面工艺', '表面工艺') ], string="工序类型") results = fields.Char('结果') + state = fields.Selection([ + ('pending', '等待其他工单'), + ('waiting', '等待组件'), + ('ready', '就绪'), + ('progress', '进行中'), + ('to be detected', "待检测"), + ('done', '已完工'), + ('rework', '返工'), + ('cancel', '取消')], string='Status', + compute='_compute_state', store=True, + default='pending', copy=False, readonly=True, recursive=True, index=True, tracking=True) + # state = fields.Selection(selection_add=[('to be detected', "待检测"), ('rework', '返工')], tracking=True) manual_quotation = fields.Boolean('人工编程', default=False, readonly=True) + def _compute_working_users(self): + super()._compute_working_users() + for item in self: + if item.state == 'to be detected': + if self.env.user.has_group('sf_base.group_sf_equipment_user'): + item.is_user_working = True + @api.onchange('users_ids') def get_user_permissions(self): uid = self.env.uid @@ -158,13 +177,46 @@ class ResMrpWorkOrder(models.Model): # 加工图纸 processing_drawing = fields.Binary(string='加工图纸') + # 功能刀具状态 + tool_state = fields.Selection([('0', '正常'), ('1', '缺刀'), ('2', '无效刀')], string='功能刀具状态', default='0', + store=True, compute='_compute_tool_state') + tool_state_remark = fields.Text(string='功能刀具状态备注(缺刀)', compute='_compute_tool_state_remark', store=True) + + @api.depends('cnc_ids.tool_state') + def _compute_tool_state_remark(self): + for item in self: + if item.cnc_ids: + if item.cnc_ids.filtered(lambda a: a.tool_state == '2'): + item.tool_state_remark = None + elif item.cnc_ids.filtered(lambda a: a.tool_state == '1'): + tool_state_remark = [] + cnc_ids = item.cnc_ids.filtered(lambda a: a.tool_state == '1') + for cnc_id in cnc_ids: + if cnc_id.cutting_tool_name not in tool_state_remark: + tool_state_remark.append(cnc_id.cutting_tool_name) + item.tool_state_remark = f"{item.processing_panel}缺刀:{tool_state_remark}]" + else: + item.tool_state_remark = None + + @api.depends('cnc_ids.tool_state') + def _compute_tool_state(self): + for item in self: + if item.cnc_ids: + if item.cnc_ids.filtered(lambda a: a.tool_state == '2'): + item.tool_state = '2' + elif item.cnc_ids.filtered(lambda a: a.tool_state == '1'): + item.tool_state = '1' + else: + item.tool_state = '0' + @api.depends('production_id') def _compute_save_name(self): """ 保存名称 """ for record in self: - record.save_name = record.production_id.name.replace('/', '_') + tem_name = record.production_id.name.replace('/', '_') + record.save_name = tem_name + '_' + record.processing_panel schedule_state = fields.Selection(related='production_id.schedule_state', store=True) # 工件装夹信息 @@ -196,15 +248,75 @@ class ResMrpWorkOrder(models.Model): production_line_state = fields.Selection( [('待上产线', '待上产线'), ('已上产线', '已上产线'), ('已下产线', '已下产线')], string='上/下产线', default='待上产线', tracking=True) - detection_report = fields.Binary('检测报告', readonly=True) + detection_report = fields.Binary('检测报告', readonly=False) is_remanufacture = fields.Boolean(string='重新生成制造订单', default=False) is_fetchcnc = fields.Boolean(string='重新获取NC程序', default=False) reason = fields.Selection( - [("programming", "编程"), ("clamping", "返工"), ("cutter", "刀具"), ("operate computer", "操机"), - ("technology", "工艺"), ("customer redrawing", "客户改图"), ("other", "其他"), ], string="原因", tracking=True) + [("programming", "编程"), ("cutter", "刀具"), ("clamping", "装夹"), ("operate computer", "操机"), + ("technology", "工艺"), ("customer redrawing", "客户改图")], string="原因", tracking=True) detailed_reason = fields.Text('详细原因') + is_rework = fields.Boolean(string='是否返工', default=False) - # is_send_program_again = fields.Boolean(string='是否重新下发NC程序', default=False) + @api.constrains('blocked_by_workorder_ids') + def _check_no_cyclic_dependencies(self): + if self.production_id.state not in ['rework'] and self.state not in ['rework']: + if not self._check_m2m_recursion('blocked_by_workorder_ids'): + raise ValidationError(_("您不能创建周期性的依赖关系.")) + + def _plan_workorder(self, replan=False): + self.ensure_one() + # Plan workorder after its predecessors + start_date = max(self.production_id.date_planned_start, datetime.now()) + for workorder in self.blocked_by_workorder_ids: + if workorder.state in ['done', 'cancel', 'rework']: + continue + workorder._plan_workorder(replan) + start_date = max(start_date, workorder.date_planned_finished) + # Plan only suitable workorders + if self.state not in ['pending', 'waiting', 'ready']: + return + if self.leave_id: + if replan: + self.leave_id.unlink() + else: + return + # Consider workcenter and alternatives + workcenters = self.workcenter_id | self.workcenter_id.alternative_workcenter_ids + best_finished_date = datetime.max + vals = {} + for workcenter in workcenters: + # Compute theoretical duration + if self.workcenter_id == workcenter: + duration_expected = self.duration_expected + else: + duration_expected = self._get_duration_expected(alternative_workcenter=workcenter) + from_date, to_date = workcenter._get_first_available_slot(start_date, duration_expected) + # If the workcenter is unavailable, try planning on the next one + if not from_date: + continue + # Check if this workcenter is better than the previous ones + if to_date and to_date < best_finished_date: + best_start_date = from_date + best_finished_date = to_date + best_workcenter = workcenter + vals = { + 'workcenter_id': workcenter.id, + 'duration_expected': duration_expected, + } + # If none of the workcenter are available, raise + if best_finished_date == datetime.max: + raise UserError(_('Impossible to plan the workorder. Please check the workcenter availabilities.')) + # Create leave on chosen workcenter calendar + leave = self.env['resource.calendar.leaves'].create({ + 'name': self.display_name, + 'calendar_id': best_workcenter.resource_calendar_id.id, + 'date_from': best_start_date, + 'date_to': best_finished_date, + 'resource_id': best_workcenter.resource_id.id, + 'time_type': 'other' + }) + vals['leave_id'] = leave.id + self.write(vals) @api.onchange('rfid_code') def _onchange(self): @@ -219,7 +331,7 @@ class ResMrpWorkOrder(models.Model): sql = """ SELECT * FROM mrp_workorder - WHERE + WHERE state!='rework' to_char(date_planned_start::timestamp + '8 hour','YYYY-MM-DD HH:mm:SS')>= %s AND to_char(date_planned_finished::timestamp + '8 hour','YYYY-MM-DD HH:mm:SS')<= %s """ @@ -430,6 +542,7 @@ class ResMrpWorkOrder(models.Model): work.compensation_value_y = eval(self.material_center_point)[1] # work.process_state = '待加工' # self.sudo().production_id.process_state = '待加工' + # self.sudo().production_id.process_state = '待加工' self.date_finished = datetime.now() workorder.button_finish() @@ -460,6 +573,20 @@ class ResMrpWorkOrder(models.Model): else: raise UserError(_("该工单暂未完成,无法进行工件配送")) + def button_rework_pre(self): + return { + 'name': _('返工'), + 'type': 'ir.actions.act_window', + 'view_mode': 'form', + 'res_model': 'sf.rework.wizard', + 'target': 'new', + 'context': { + 'default_workorder_id': self.id, + 'default_production_id': self.production_id.id, + # 'default_programming_state': self.production_id.programming_state, + 'default_routing_type': self.routing_type + }} + # 拼接工单对象属性值 def json_workorder_str(self, k, production, route, item): # 计算预计时长duration_expected @@ -516,12 +643,16 @@ class ResMrpWorkOrder(models.Model): 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, '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, '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}]] + [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}]] # 拼接工单对象属性值(表面工艺) def _json_workorder_surface_process_str(self, production, route, process_parameter, supplier_id): @@ -667,119 +798,6 @@ class ResMrpWorkOrder(models.Model): # 'target':'new' # } - def recreateManufacturingOrWorkerOrder(self): - """ - 重新生成制造订单或者重新生成工单 - """ - if self.test_results in ['返工', '报废']: - 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: - 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) - 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 - - sale_order = self.env['sale.order'].sudo().search([('name', '=', productions.origin)]) - 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 self.test_results == '返工': - # productions = self.production_id - # # self.env['stock.move'].sudo().create(productions._get_moves_raw_values()) - # # self.env['stock.move'].sudo().create(productions._get_moves_finished_values()) - # productions._create_workorder2(self.processing_panel) - # else: - # self.results = '合格' - def json_workorder_str1(self, k, production, route): workorders_values_str = [0, '', { 'product_uom_id': production.product_uom_id.id, @@ -803,28 +821,162 @@ class ResMrpWorkOrder(models.Model): }] return workorders_values_str - # @api.depends('production_availability', 'blocked_by_workorder_ids', 'blocked_by_workorder_ids.state') - # def _compute_state(self): - # super(ResMrpWorkOrder, self)._compute_state() - # for item in self: - # print(item.name) - # print(item.state) - # print(item.is_remanufacture) - # scrap_workorder = self.env['mrp.workorder'].search( - # [('production_id', '=', item.production_id.id), ('routing_type', '=', 'CNC加工'), - # ('state', '=', 'done'), ('test_results', 'in', ['返工', '报废'])]) - # print(scrap_workorder) - # # if item.routing_type == 'CNC加工' and item.state in ['done'] and item.test_results in ['返工', '报废']: - # if item.routing_type == '解除装夹': - # if scrap_workorder and item.state not in ['cancel']: - # item.state = 'cancel' - # elif item.routing_type == '表面工艺': - # if scrap_workorder: - # stock_move = self.env['stock.move'].search( - # [('origin', '=', item.production_id.name)]) - # stock_move.write({'state': 'cancel'}) - # item.picking_ids.write({'state': 'cancel'}) - # item.state = 'cancel' + @api.depends('production_availability', 'blocked_by_workorder_ids', 'blocked_by_workorder_ids.state', + 'production_id.tool_state') + def _compute_state(self): + super()._compute_state() + for workorder in self: + re_work = self.env['mrp.workorder'].search([('production_id', '=', workorder.production_id.id), + ('processing_panel', '=', workorder.processing_panel), + ('is_rework', '=', True), ('state', 'in', ['done', 'rework'])]) + cnc_workorder = self.env['mrp.workorder'].search( + [('production_id', '=', workorder.production_id.id), + ('processing_panel', '=', workorder.processing_panel), + ('routing_type', '=', 'CNC加工'), ('state', 'in', ['done', 'rework']), + ('test_results', '=', '返工')]) + cnc_workorder_pending = self.env['mrp.workorder'].search( + [('production_id', '=', workorder.production_id.id), + ('processing_panel', '=', workorder.processing_panel), + ('routing_type', '=', 'CNC加工'), ('state', 'in', ['pending'])]) + unclamp_workorder = self.env['mrp.workorder'].search( + [('production_id', '=', workorder.production_id.id), + ('sequence', '=', workorder.sequence - 1), + ('state', 'in', ['done'])]) + if workorder.state not in ['cancel', 'progress', 'rework']: + if workorder.production_id.state == 'rework': + logging.info('len(re_work):%s' % len(re_work)) + logging.info('len(cnc_workorder):%s' % len(cnc_workorder)) + logging.info('len(cnc_workorder_pending):%s' % len(cnc_workorder_pending)) + logging.info('工序:%s' % workorder.routing_type) + logging.info('状态:%s' % workorder.state) + logging.info('is_rework:%s' % workorder.is_rework) + logging.info('production_id.is_rework:%s' % workorder.production_id.is_rework) + logging.info('面:%s' % workorder.processing_panel) + logging.info('编程状态:%s' % workorder.production_id.programming_state) + logging.info('制造状态:%s' % workorder.production_id.state) + if workorder.routing_type == '装夹预调' and workorder.state not in ['done', 'rework', + 'cancel']: + # # 有返工工单 + # if re_work: + # 新工单 + if workorder.is_rework is False: + if workorder.production_id.programming_state == '已编程' and workorder.production_id.is_rework is False: + if re_work or cnc_workorder: + workorder.state = 'ready' + else: + if workorder.production_id.is_rework is True: + if re_work or cnc_workorder: + workorder.state = 'waiting' + + elif workorder.routing_type == 'CNC加工' and workorder.state not in ['done', 'rework', 'cancel']: + pre_workorder = self.env['mrp.workorder'].search( + [('production_id', '=', workorder.production_id.id), + ('processing_panel', '=', workorder.processing_panel), + ('routing_type', '=', '装夹预调'), ('state', '=', 'done')]) + if pre_workorder: + if re_work: + workorder.state = 'waiting' + elif workorder.routing_type == '解除装夹' and workorder.state not in ['done', 'rework', 'cancel']: + if cnc_workorder: + if not cnc_workorder_pending: + workorder.state = 'waiting' + # else: + # if workorder.production_id.is_rework is True: + # workorder.state = 'waiting' + elif workorder.production_id.state == 'progress': + logging.info('len(re_work):%s' % len(re_work)) + logging.info('len(cnc_workorder):%s' % len(cnc_workorder)) + logging.info('len(cnc_workorder_pending):%s' % len(cnc_workorder_pending)) + logging.info('工序:%s' % workorder.routing_type) + logging.info('状态:%s' % workorder.state) + logging.info('is_rework:%s' % workorder.is_rework) + logging.info('production_id.is_rework:%s' % workorder.production_id.is_rework) + logging.info('面:%s' % workorder.processing_panel) + logging.info('编程状态:%s' % workorder.production_id.programming_state) + logging.info('制造状态:%s' % workorder.production_id.state) + if workorder.routing_type == '装夹预调' and workorder.production_id.programming_state == '已编程' and \ + workorder.is_rework is False and workorder.state not in [ + 'done', 'rework', + 'cancel']: + if workorder.production_id.is_rework is False: + if re_work or cnc_workorder or unclamp_workorder: + workorder.state = 'ready' + # if (re_work or cnc_workorder) and workorder.production_id.is_rework is False: + # workorder.state = 'ready' + if workorder.routing_type == '表面工艺' and workorder.state not in ['done', 'progress']: + if unclamp_workorder: + workorder.state = 'ready' + # else: + # if workorder.state not in ['cancel', 'rework']: + # workorder.state = 'rework' + if workorder.routing_type == '装夹预调' and workorder.state in ['waiting', 'ready', 'pending']: + # 当工单对应制造订单的功能刀具状态为 【无效刀】时,先对的第一个装夹预调工单状态设置为 【等待组件】 + if workorder.production_id.tool_state in ['1', '2']: + if workorder.state in ['ready']: + workorder.state = 'waiting' + continue + elif workorder.state in ['waiting']: + continue + elif 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.production_id.tool_state in ['0']: + if workorder.production_id.workorder_ids.filtered(lambda a: a.state == 'rework'): + if not workorder.production_id.workorder_ids.filtered( + lambda a: (a.routing_type not in ['装夹预调'] and + a.state not in ['pending', 'done', 'rework', 'cancel'])): + # 查询工序最小的非完工、非返工的装夹预调工单 + work_id = self.search( + [('production_id', '=', workorder.production_id.id), + ('routing_type', '=', '装夹预调'), + ('state', 'not in', ['rework', 'done', 'cancel'])], + limit=1, + order="sequence") + if workorder == work_id: + if workorder.production_id.reservation_state == 'assigned': + workorder.state = 'ready' + elif workorder.production_id.reservation_state != 'assigned': + workorder.state = 'waiting' + continue + + logging.info('工序:%s' % workorder.sequence) + logging.info('工单最终状态:%s' % workorder.state) + + # elif workorder.routing_type == 'CNC加工' and workorder.state not in ['done', 'cancel', 'progress', + # 'rework']: + # per_work = self.env['mrp.workorder'].search( + # [('routing_type', '=', '装夹预调'), ('production_id', '=', workorder.production_id.id), + # ('processing_panel', '=', workorder.processing_panel), ('is_rework', '=', True)]) + # if per_work: + # workorder.state = 'waiting' + # if workorder.routing_type == 'CNC加工' and workorder.state == 'progress': + # workorder.state = 'to be detected' + + # for workorder in self: + # if workorder.is_rework is True and workorder.state == 'done': + # cnc_work = self.env['mrp.workorder'].search([('routing_type','=','CNC加工'),('production_id','=',workorder.production_id.id)]) + # if cnc_work: + # cnc_work.state = 'waiting' + # if workorder.state == 'pending': + # if all([wo.state in ('done', 'cancel') for wo in workorder.blocked_by_workorder_ids]): + # workorder.state = 'ready' if workorder.production_id.reservation_state == 'assigned' else 'waiting' + # continue + # if workorder.state not in ('waiting', 'ready'): + # continue + # if not all([wo.state in ('done', 'cancel') for wo in workorder.blocked_by_workorder_ids]): + # workorder.state = 'pending' + # continue + # if workorder.production_id.reservation_state not in ('waiting', 'confirmed', 'assigned'): + # continue + # if workorder.production_id.reservation_state == 'assigned' and workorder.state == 'waiting': + # workorder.state = 'ready' + # elif workorder.production_id.reservation_state != 'assigned' and workorder.state == 'ready': + # workorder.state = 'waiting' # 重写工单开始按钮方法 def button_start(self): @@ -843,12 +995,14 @@ class ResMrpWorkOrder(models.Model): limit=1, order='id asc') if not cnc_workorder.cnc_ids: raise UserError(_('该制造订单还未下发CNC程序,请稍后再试')) - # else: - # for item in cnc_workorder.cnc_ids: - # functional_cutting_tool = self.env['sf.functional.cutting.tool.entity'].search( - # [('tool_name_id.name', '=', item.cutting_tool_name)]) - # if not functional_cutting_tool: - # raise UserError(_('该制造订单的CNC程序为%s没有对应的功能刀具' % item.cutting_tool_name)) + else: + if self.production_id.tool_state in ['1', '2']: + if self.production_id.tool_state == '1': + state = '缺刀' + else: + state = '无效刀' + raise UserError( + f'制造订单【{self.production_id.name}】功能刀具状态为【{state}】!') if self.routing_type == '解除装夹': ''' 记录开始时间 @@ -932,7 +1086,7 @@ class ResMrpWorkOrder(models.Model): if record.routing_type == '装夹预调': if not record.material_center_point and record.X_deviation_angle > 0: raise UserError("请对前置三元检测定位参数进行计算定位") - if not record.rfid_code: + if not record.rfid_code and record.is_rework is False: raise UserError("请扫RFID码进行绑定") record.process_state = '待加工' # record.write({'process_state': '待加工'}) @@ -941,6 +1095,15 @@ class ResMrpWorkOrder(models.Model): record.process_state = '待解除装夹' # record.write({'process_state': '待加工'}) record.production_id.process_state = '待解除装夹' + 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 '', + 'test_results': record.test_results, + 'test_report': record.detection_report})], + 'is_scrap': True if record.test_results == '报废' else False}) if record.routing_type == '解除装夹': ''' 记录结束时间 @@ -989,34 +1152,38 @@ class ResMrpWorkOrder(models.Model): record.write({ 'date_planned_finished': tem_date_planned_finished # 保持原值 }) - # if record.routing_type == 'CNC加工': - # record.write({ - # 'date_finished': tem_date_finished # 保持原值 - # }) + # if record.routing_type == 'CNC加工' and record.test_results in ['返工', '报废']: # record.production_id.action_cancel() # record.production_id.workorder_ids.write({'rfid_code': False, 'rfid_code_old': record.rfid_code}) # if record.is_remanufacture is True: # record.recreateManufacturingOrWorkerOrder() - is_production_id = True - for workorder in record.production_id.workorder_ids: - if workorder.state != 'done': - is_production_id = False - if record.routing_type == '解除装夹': + is_production_id = False + rework_workorder = record.production_id.workorder_ids.filtered(lambda p: p.state == 'rework') + done_workorder = record.production_id.workorder_ids.filtered(lambda p1: p1.state == 'done') + if (len(rework_workorder) + len(done_workorder) == len(record.production_id.workorder_ids)) or ( + len(done_workorder) == len(record.production_id.workorder_ids)): + is_production_id = True + if record.routing_type in ['解除装夹'] or ( + record.is_rework is True and record.routing_type in ['装夹预调']) or ( + record.test_results in ['返工', '报废'] and record.routing_type in ['CNC加工']): for workorder in record.production_id.workorder_ids: if workorder.processing_panel == record.processing_panel: rfid_code = workorder.rfid_code workorder.write({'rfid_code_old': rfid_code, 'rfid_code': False}) - workorder.rfid_code_old = rfid_code - workorder.rfid_code = False + if workorder.rfid_code: + raise ValidationError(f'【{workorder.name}】工单解绑失败,请重新点击完成按钮!!!') + # workorder.rfid_code_old = rfid_code + # workorder.rfid_code = False + logging.info('workorder.rfid_code:%s' % workorder.rfid_code) if is_production_id is True and record.routing_type in ['解除装夹', '表面工艺']: logging.info('product_qty:%s' % record.production_id.product_qty) for move_raw_id in record.production_id.move_raw_ids: move_raw_id.quantity_done = move_raw_id.product_uom_qty record.process_state = '已完工' record.production_id.process_state = '已完工' - if record.routing_type in ['解除装夹', '表面工艺']: + if record.routing_type in ['表面工艺']: raw_move = self.env['stock.move'].sudo().search( [('origin', '=', record.production_id.name), ('procure_method', 'in', ['make_to_order', 'make_to_stock']), @@ -1066,31 +1233,15 @@ class ResMrpWorkOrder(models.Model): workorder.detection_report = base64.b64encode(open(report_file_path, 'rb').read()) return True - # 重新下发nc程序 - # def button_send_program_again(self): - # try: - # res = {'programming_no': self.production_id.programming_no} - # configsettings = self.env['res.config.settings'].get_values() - # config_header = Common.get_headers(self, configsettings['token'], configsettings['sf_secret_key']) - # url = '/api/intelligent_programming/reset_state_again' - # config_url = configsettings['sf_url'] + url - # r = requests.post(config_url, json=res, data=None, headers=config_header) - # r = r.json() - # result = json.loads(r['result']) - # if result['status'] == 1: - # productions = self.env['mrp.production'].search( - # [('programming_no', '=', self.production_id.programming_no), ('programming_state', '=', '已编程')]) - # if productions: - # workorder = productions.workorder_ids.filtered( - # lambda ap: ap.routing_type in ['装夹预调', 'CNC加工'] and ap.state not in ['done', 'cancel', - # 'progress']) - # if workorder: - # productions.write({'work_state': '编程中', 'programming_state': '编程中'}) - # else: - # raise UserError(result['message']) - # except Exception as e: - # logging.info('button_send_program_again error:%s' % e) - # raise UserError("重新下发nc程序失败,请联系管理员") + def print_method(self): + """ + 解除装夹处调用关联制造订单的关联序列号的打印方法 + """ + if self.production_id: + if self.production_id.lot_producing_id: + self.production_id.lot_producing_id.print_single_method() + else: + raise UserError("无关联制造订单或关联序列号,无法打印。请检查!") class CNCprocessing(models.Model): @@ -1119,6 +1270,8 @@ class CNCprocessing(models.Model): program_path = fields.Char('程序文件路径') program_create_date = fields.Datetime('程序创建日期') + tool_state = fields.Selection([('0', '正常'), ('1', '缺刀'), ('2', '无效刀')], string='刀具状态', default='0') + # mrs下发编程单创建CNC加工 def cnc_processing_create(self, cnc_workorder, ret, program_path, program_path_tmp): cnc_processing = None @@ -1152,24 +1305,25 @@ class CNCprocessing(models.Model): def _json_cnc_processing(self, panel, ret): cnc_processing = [] - for item in ret['programming_list']: - if item['processing_panel'] == panel and item['ftp_path'].find('.dmi') == -1: - cnc_processing.append((0, 0, { - 'sequence_number': item['sequence_number'], - 'program_name': item['program_name'], - 'cutting_tool_name': item['cutting_tool_name'], - 'cutting_tool_no': item['cutting_tool_no'], - 'processing_type': item['processing_type'], - 'margin_x_y': item['margin_x_y'], - 'margin_z': item['margin_z'], - 'depth_of_processing_z': item['depth_of_processing_z'], - 'cutting_tool_extension_length': item['cutting_tool_extension_length'], - 'cutting_tool_handle_type': item['cutting_tool_handle_type'], - 'estimated_processing_time': item['estimated_processing_time'], - 'program_path': item['ftp_path'], - 'program_create_date': datetime.strptime(item['program_create_date'], '%Y-%m-%d %H:%M:%S'), - 'remark': item['remark'] - })) + if ret is not False: + for item in ret['programming_list']: + if item['processing_panel'] == panel and item['ftp_path'].find('.dmi') == -1: + cnc_processing.append((0, 0, { + 'sequence_number': item['sequence_number'], + 'program_name': item['program_name'], + 'cutting_tool_name': item['cutting_tool_name'], + 'cutting_tool_no': item['cutting_tool_no'], + 'processing_type': item['processing_type'], + 'margin_x_y': item['margin_x_y'], + 'margin_z': item['margin_z'], + 'depth_of_processing_z': item['depth_of_processing_z'], + 'cutting_tool_extension_length': item['cutting_tool_extension_length'], + 'cutting_tool_handle_type': item['cutting_tool_handle_type'], + 'estimated_processing_time': item['estimated_processing_time'], + 'program_path': item['ftp_path'], + 'program_create_date': datetime.strptime(item['program_create_date'], '%Y-%m-%d %H:%M:%S'), + 'remark': item['remark'] + })) return cnc_processing # 根据程序名和加工面匹配到ftp里对应的Nc程序名,可优化为根据cnc_processing.program_path进行匹配 @@ -1372,7 +1526,8 @@ class WorkPieceDelivery(models.Model): [('上产线', '上产线'), ('下产线', '下产线'), ('运送空料架', '运送空料架')], string='类型') delivery_duration = fields.Float('配送时长', compute='_compute_delivery_duration') status = fields.Selection( - [('待下发', '待下发'), ('待配送', '待配送'), ('已配送', '已配送')], string='状态', default='待下发', + [('待下发', '待下发'), ('待配送', '待配送'), ('已配送', '已配送'), ('已取消', '已取消')], string='状态', + default='待下发', tracking=True) is_cnc_program_down = fields.Boolean('程序是否下发', default=False, tracking=True) is_manual_work = fields.Boolean('人工操作', default=False) @@ -1625,12 +1780,13 @@ class CMMprogram(models.Model): def _json_cmm_program(self, panel, ret): cmm_program = [] - for item in ret['programming_list']: - if item['processing_panel'] == panel and item['ftp_path'].find('.dmi') != -1: - cmm_program.append((0, 0, { - 'sequence_number': 1, - 'program_name': item['program_name'], - 'program_path': item['ftp_path'], - 'program_create_date': datetime.strptime(item['program_create_date'], '%Y-%m-%d %H:%M:%S'), - })) + if ret is not False: + for item in ret['programming_list']: + if item['processing_panel'] == panel and item['ftp_path'].find('.dmi') != -1: + cmm_program.append((0, 0, { + 'sequence_number': 1, + 'program_name': item['program_name'], + 'program_path': item['ftp_path'], + 'program_create_date': datetime.strptime(item['program_create_date'], '%Y-%m-%d %H:%M:%S'), + })) return cmm_program diff --git a/sf_manufacturing/models/stock.py b/sf_manufacturing/models/stock.py index 9234060f..4bf7cc72 100644 --- a/sf_manufacturing/models/stock.py +++ b/sf_manufacturing/models/stock.py @@ -204,7 +204,8 @@ class StockRule(models.Model): productions = self.env['mrp.production'].with_user(SUPERUSER_ID).sudo().with_company(company_id).create( productions_values) - self.env['stock.move'].sudo().create(productions._get_moves_raw_values()) + # self.env['stock.move'].sudo().create(productions._get_moves_raw_values()) + # self.env['stock.move'].sudo().create(productions._get_moves_finished_values()) ''' 创建工单 @@ -404,6 +405,7 @@ class ProductionLot(models.Model): def print_single_method(self): + print('self.name========== %s' % self.name) self.ensure_one() qr_code_data = self.qr_code_image if not qr_code_data: diff --git a/sf_manufacturing/security/ir.model.access.csv b/sf_manufacturing/security/ir.model.access.csv index ec2d5a76..9b5b5e28 100644 --- a/sf_manufacturing/security/ir.model.access.csv +++ b/sf_manufacturing/security/ir.model.access.csv @@ -29,7 +29,7 @@ access_mrp_workorder_group_sf_mrp_user,mrp_workorder,model_mrp_workorder,sf_base access_mrp_workorder_manager,mrp_workorder,model_mrp_workorder,sf_base.group_sf_mrp_manager,1,1,1,0 access_mrp_workcenter_group_sf_mrp_user,mrp_workcenter,model_mrp_workcenter,sf_base.group_sf_mrp_user,1,0,0,0 access_mrp_workcenter_manager,mrp_workcenter,model_mrp_workcenter,sf_base.group_sf_mrp_manager,1,1,1,0 -access_mrp_workcenter_productivity_group_sf_mrp_user,mrp_workcenter_productivity,model_mrp_workcenter_productivity,sf_base.group_sf_mrp_user,1,0,0,0 +access_mrp_workcenter_productivity_group_sf_equipment_user,mrp_workcenter_productivity,model_mrp_workcenter_productivity,sf_base.group_sf_equipment_user,1,1,1,0 access_mrp_workcenter_productivity_manager,mrp_workcenter_productivity,model_mrp_workcenter_productivity,sf_base.group_sf_mrp_manager,1,1,1,0 access_sf_workpiece_delivery_group_sf_order_user,sf_workpiece_delivery_group_sf_order_user,model_sf_workpiece_delivery,sf_base.group_sf_order_user,1,1,1,0 access_sf_workpiece_delivery_group_sf_equipment_user,sf_workpiece_delivery_group_sf_equipment_user,model_sf_workpiece_delivery,sf_base.group_sf_equipment_user,1,1,0,0 @@ -141,3 +141,14 @@ access_sf_model_type_group_sf_stock_manager,sf_model_type_group_sf_mrp_manager,m access_mrp_bom_byproduct_group_sf_stock_user,mrp_bom_byproduct_group_sf_stock_user,mrp.model_mrp_bom_byproduct,sf_base.group_sf_stock_user,1,0,0,0 access_mrp_bom_byproduct_group_sf_stock_manager,mrp_bom_byproduct_group_sf_mrp_manager,mrp.model_mrp_bom_byproduct,sf_base.group_sf_stock_manager,1,0,0,0 +access_sf_rework_wizard_group_sf_order_user,sf_rework_wizard_group_sf_order_user,model_sf_rework_wizard,sf_base.group_sf_order_user,1,1,1,0 +access_sf_rework_wizard_group_plan_dispatch,sf_rework_wizard_group_plan_dispatch,model_sf_rework_wizard,sf_base.group_plan_dispatch,1,1,1,0 +access_sf_detection_result_group_sf_order_user,sf_detection_result_group_sf_order_user,model_sf_detection_result,sf_base.group_sf_order_user,1,1,1,0 +access_sf_detection_result_group_plan_dispatch,sf_detection_result_group_plan_dispatch,model_sf_detection_result,sf_base.group_plan_dispatch,1,1,1,0 +access_sf_detection_result_group_sf_equipment_user,sf_detection_result_group_sf_equipment_user,model_sf_detection_result,sf_base.group_sf_equipment_user,1,1,1,0 +access_sf_processing_panel_group_sf_order_user,sf_processing_panel_group_sf_order_user,model_sf_processing_panel,sf_base.group_sf_order_user,1,1,1,0 +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 + + + diff --git a/sf_manufacturing/views/mrp_production_addional_change.xml b/sf_manufacturing/views/mrp_production_addional_change.xml index b5e348b9..fdbec99f 100644 --- a/sf_manufacturing/views/mrp_production_addional_change.xml +++ b/sf_manufacturing/views/mrp_production_addional_change.xml @@ -1,11 +1,16 @@ + custom.mrp.production.tree mrp.production + + +

@@ -711,10 +723,14 @@ - - - - + + + + @@ -759,10 +775,25 @@
+
+ + +

@@ -773,12 +804,13 @@ - - + + + @@ -791,8 +823,8 @@ attrs="{'readonly': [('state', '=', '已拆解')]}"/> - + + @@ -879,6 +911,23 @@ + + + + + + + + + + + + + + + diff --git a/sf_tool_management/wizard/wizard.py b/sf_tool_management/wizard/wizard.py index af772016..4edd3617 100644 --- a/sf_tool_management/wizard/wizard.py +++ b/sf_tool_management/wizard/wizard.py @@ -621,26 +621,10 @@ class FunctionalToolAssemblyOrder(models.TransientModel): desc_1 = self.get_desc_1(stock_lot) # 封装功能刀具数据,用于创建功能刀具记录 desc_2 = self.get_desc_2(stock_lot, functional_tool_assembly) - # 创建刀具组装入库单 - self.env['stock.picking'].create_stocking_picking(stock_lot, functional_tool_assembly, self) - # 刀具物料出库 - if self.handle_code_id: - product_id.tool_material_stock_moves(self.handle_code_id, self.assembly_order_code) - if self.integral_product_id: - self.integral_product_id.material_stock_moves(self.integral_freight_barcode_id, - self.integral_freight_lot_id, self.assembly_order_code) - if self.blade_product_id: - self.blade_product_id.material_stock_moves(self.blade_freight_barcode_id, - self.blade_freight_lot_id, self.assembly_order_code) - if self.bar_product_id: - self.bar_product_id.material_stock_moves(self.bar_freight_barcode_id, - self.bar_freight_lot_id, self.assembly_order_code) - if self.pad_product_id: - self.pad_product_id.material_stock_moves(self.pad_freight_barcode_id, - self.pad_freight_lot_id, self.assembly_order_code) - if self.chuck_product_id: - self.chuck_product_id.material_stock_moves(self.chuck_freight_barcode_id, - self.chuck_freight_lot_id, self.assembly_order_code) + # 创建功能刀具组装入库单 + self.env['stock.picking'].create_tool_stocking_picking(stock_lot, functional_tool_assembly, self) + # 创建刀具物料出库单 + self.env['stock.picking'].create_tool_stocking_picking1(self) # ============================创建功能刀具列表、安全库存记录=============================== # 创建功能刀具列表记录 @@ -786,9 +770,9 @@ class FunctionalToolAssemblyOrder(models.TransientModel): class StockPicking(models.Model): _inherit = 'stock.picking' - def create_stocking_picking(self, stock_lot, functional_tool_assembly, obj): + def create_tool_stocking_picking(self, stock_lot, functional_tool_assembly, obj): """ - 创建刀具组装入库单 + 创建功能刀具组装入库单 """ # 获取名称为刀具组装入库的作业类型 picking_type_id = self.env['stock.picking.type'].sudo().search([('name', '=', '刀具组装入库')]) @@ -807,6 +791,7 @@ class StockPicking(models.Model): 'location_id': picking_id.location_id.id, 'location_dest_id': picking_id.location_dest_id.id, 'lot_id': stock_lot.id, + 'install_tool_time': fields.Datetime.now(), 'qty_done': 1, 'functional_tool_name_id': functional_tool_assembly.id, 'functional_tool_type_id': obj.functional_tool_type_id.id, @@ -836,6 +821,101 @@ class StockPicking(models.Model): num = "%03d" % m return name + str(num) + def create_tool_stocking_picking1(self, obj): + """ + 创建刀具物料出库单 + """ + # 获取名称为内部调拨的作业类型 + picking_type_id = self.env['stock.picking.type'].sudo().search([('name', '=', '内部调拨')]) + # 创建刀具物料出库单 + picking_id = self.env['stock.picking'].create({ + 'name': self._get_name_stock1(picking_type_id), + 'picking_type_id': picking_type_id.id, + 'location_id': self.env['stock.location'].search([('name', '=', '刀具房')]).id, + 'location_dest_id': self.env['stock.location'].search([('name', '=', '刀具组装位置')]).id, + 'origin': obj.assembly_order_code + }) + # =============刀具物料出库=================== + stock_move_id = self.env['stock.move'] + datas = {'data': [], 'picking_id': picking_id} + if obj.handle_code_id: + datas['data'].append( + {'current_location_id': self.env['sf.shelf.location'], 'lot_id': obj.handle_code_id}) + if obj.integral_product_id: + datas['data'].append( + {'current_location_id': obj.integral_freight_barcode_id, 'lot_id': obj.integral_freight_lot_id.lot_id}) + if obj.blade_product_id: + datas['data'].append( + {'current_location_id': obj.blade_freight_barcode_id, 'lot_id': obj.blade_freight_lot_id.lot_id}) + if obj.bar_product_id: + datas['data'].append( + {'current_location_id': obj.bar_freight_barcode_id, 'lot_id': obj.bar_freight_lot_id.lot_id}) + if obj.pad_product_id: + datas['data'].append( + {'current_location_id': obj.pad_freight_barcode_id, 'lot_id': obj.pad_freight_lot_id.lot_id}) + if obj.chuck_product_id: + datas['data'].append( + {'current_location_id': obj.chuck_freight_barcode_id, 'lot_id': obj.chuck_freight_lot_id.lot_id}) + # 创建刀具物料出库库存移动记录 + stock_move_id.create_tool_material_stock_moves(datas) + # 将刀具物料出库库单的状态更改为就绪 + picking_id.action_confirm() + # 修改刀具物料出库移动历史记录 + stock_move_id.write_tool_material_stock_move_lines(datas) + # 设置数量,并验证完成 + picking_id.action_set_quantities_to_reservation() + picking_id.button_validate() + + def _get_name_stock1(self, picking_type_id): + name = picking_type_id.sequence_id.prefix + stock_id = self.env['stock.picking'].sudo().search( + [('name', 'like', name), ('picking_type_id', '=', picking_type_id.id)], + limit=1, + order="id desc" + ) + if not stock_id: + num = "%05d" % 1 + else: + m = int(stock_id.name[-3:]) + 1 + num = "%05d" % m + return name + str(num) + + +class StockMove(models.Model): + _inherit = 'stock.move' + + def create_tool_material_stock_moves(self, datas): + picking_id = datas['picking_id'] + data = datas['data'] + stock_move_ids = [] + for res in data: + if res: + # 创建库存移动记录 + stock_move_id = self.env['stock.move'].sudo().create({ + 'name': picking_id.name, + 'picking_id': picking_id.id, + 'product_id': res['lot_id'].product_id.id, + 'location_id': picking_id.location_id.id, + 'location_dest_id': picking_id.location_dest_id.id, + 'product_uom_qty': 1.00, + 'reserved_availability': 1.00 + }) + stock_move_ids.append(stock_move_id) + return stock_move_ids + + def write_tool_material_stock_move_lines(self, datas): + picking_id = datas['picking_id'] + data = datas['data'] + move_line_ids = picking_id.move_line_ids + for move_line_id in move_line_ids: + for res in data: + if move_line_id.lot_id.product_id == res['lot_id'].product_id: + move_line_id.write({ + 'current_location_id': res.get('current_location_id').id, + 'lot_id': res.get('lot_id').id + }) + return True + class ProductProduct(models.Model): _inherit = 'product.product' @@ -853,13 +933,6 @@ class ProductProduct(models.Model): 'product_id': product_id[0].id, 'company_id': self.env.company.id }) - # 获取位置对象 - location_inventory_ids = self.env['stock.location'].search([('name', 'in', ('Production', '生产'))]) - stock_location_id = self.env['stock.location'].search([('name', '=', '组装后')]) - # 创建功能刀具该批次/序列号 库存移动和移动历史 - stock_lot.create_stock_quant(location_inventory_ids[-1], stock_location_id, functional_tool_assembly.id, - obj.assembly_order_code, obj, obj.after_tool_groups_id) - return stock_lot def get_stock_lot_name(self, obj): @@ -877,87 +950,3 @@ class ProductProduct(models.Model): m = int(stock_lot_id.name[-3:]) + 1 num = "%03d" % m return '%s-%s' % (code, num) - - def tool_material_stock_moves(self, tool_material, assembly_order_code): - """ - 对刀具物料进行库存移动到 刀具组装位置 - """ - # 获取位置对象 - location_inventory_id = tool_material.quant_ids.location_id[-1] - stock_location_id = self.env['stock.location'].search([('name', '=', '刀具组装位置')]) - # 创建功能刀具该批次/序列号 库存移动和移动历史 - tool_material.create_stock_quant(location_inventory_id, stock_location_id, None, assembly_order_code, False, - False) - - def material_stock_moves(self, shelf_location_barcode_id, lot_id, assembly_order_code): - # 创建库存移动记录 - stock_move_id = self.env['stock.move'].sudo().create({ - 'name': assembly_order_code, - 'product_id': self.id, - 'location_id': self.env['stock.location'].search([('name', '=', '刀具房')]).id, - 'location_dest_id': self.env['stock.location'].search([('name', '=', '刀具组装位置')]).id, - 'product_uom_qty': 1.00, - 'state': 'done' - }) - - # 创建移动历史记录 - stock_move_line_id = self.env['stock.move.line'].sudo().create({ - 'product_id': self.id, - 'move_id': stock_move_id.id, - 'lot_id': lot_id.lot_id.id, - 'current_location_id': shelf_location_barcode_id.id, - 'install_tool_time': fields.Datetime.now(), - 'qty_done': 1.0, - 'state': 'done', - }) - return stock_move_id, stock_move_line_id - - -class StockLot(models.Model): - _inherit = 'stock.lot' - - def create_stock_quant(self, location_inventory_id, stock_location_id, functional_tool_assembly_id, name, obj, - tool_groups_id): - """ - 对功能刀具组装过程的功能刀具和刀具物料进行库存移动,以及创建移动历史 - """ - - # 创建库存移动记录 - stock_move_id = self.env['stock.move'].sudo().create({ - 'name': name, - 'product_id': self.product_id.id, - 'location_id': location_inventory_id.id, - 'location_dest_id': stock_location_id.id, - 'product_uom_qty': 1.00, - 'state': 'done' - }) - - # 创建移动历史记录 - stock_move_line_id = self.env['stock.move.line'].sudo().create({ - 'product_id': self.product_id.id, - 'functional_tool_name_id': functional_tool_assembly_id, - 'lot_id': self.id, - 'move_id': stock_move_id.id, - 'install_tool_time': fields.Datetime.now(), - 'qty_done': 1.0, - 'state': 'done', - 'functional_tool_type_id': False if not obj else obj.functional_tool_type_id.id, - 'diameter': None if not obj else obj.after_assembly_functional_tool_diameter, - 'knife_tip_r_angle': None if not obj else obj.after_assembly_knife_tip_r_angle, - 'code': '' if not obj else obj.code, - 'rfid': '' if not obj else obj.rfid, - 'functional_tool_name': '' if not obj else obj.after_assembly_functional_tool_name, - 'tool_groups_id': False if not tool_groups_id else tool_groups_id.id - }) - return stock_move_id, stock_move_line_id - -# class StockQuant(models.Model): -# _inherit = 'stock.quant' -# -# @api.model_create_multi -# def create(self, vals_list): -# records = super(StockQuant, self).create(vals_list) -# for record in records: -# if record.lot_id.product_id.categ_id.name == '刀具': -# record.lot_id.enroll_tool_material_stock() -# return records diff --git a/sf_warehouse/models/model.py b/sf_warehouse/models/model.py index 770d2672..5beeebc5 100644 --- a/sf_warehouse/models/model.py +++ b/sf_warehouse/models/model.py @@ -418,19 +418,19 @@ class ShelfLocation(models.Model): host = printer_config.printer_id.ip_address port = printer_config.printer_id.port self.print_qr_code(barcode, host, port) - # 获取当前wizard的视图ID或其他标识信息 - view_id = self.env.context.get('view_id') - # 构造返回wizard页面的action字典 - action = { - 'type': 'ir.actions.act_window', - 'name': '返回 Wizard', - 'res_model': 'sf.shelf', # 替换为你的wizard模型名称 - 'view_mode': 'form', - 'view_id': view_id, # 如果需要基于特定的视图返回 - 'target': 'new', # 如果需要在新的窗口或标签页打开 - 'res_id': self.shelf_id, # 如果你想要返回当前记录的视图 - } - return action + # # 获取当前wizard的视图ID或其他标识信息 + # view_id = self.env.context.get('view_id') + # # 构造返回wizard页面的action字典 + # action = { + # 'type': 'ir.actions.act_window', + # 'name': '返回 Wizard', + # 'res_model': 'sf.shelf', # 替换为你的wizard模型名称 + # 'view_mode': 'form', + # 'view_id': view_id, # 如果需要基于特定的视图返回 + # 'target': 'new', # 如果需要在新的窗口或标签页打开 + # 'res_id': self.shelf_id, # 如果你想要返回当前记录的视图 + # } + # return action # # 仓库类别(selection:库区、库位、货位) # location_type = fields.Selection([ diff --git a/sf_warehouse/views/change_stock_move_views.xml b/sf_warehouse/views/change_stock_move_views.xml index 29d09926..c5df0881 100644 --- a/sf_warehouse/views/change_stock_move_views.xml +++ b/sf_warehouse/views/change_stock_move_views.xml @@ -102,11 +102,11 @@ attrs="{'invisible': ['|', '|', '|', ('picking_type_code', '=', 'incoming'), ('immediate_transfer', '=', True), '&', ('state', '!=', 'assigned'), ('move_type', '!=', 'one'), '&', ('state', 'not in', ('assigned', 'confirmed')), ('move_type', '=', 'one')]}" data-hotkey="w"/> - -