diff --git a/sf_base/__manifest__.py b/sf_base/__manifest__.py index 7e53ef7b..0640922a 100644 --- a/sf_base/__manifest__.py +++ b/sf_base/__manifest__.py @@ -10,7 +10,7 @@ """, 'category': 'sf', 'website': 'https://www.sf.jikimo.com', - 'depends': ['account', 'base', 'mrp_workorder'], + 'depends': ['account', 'base', 'mrp_workorder','sale'], 'data': [ 'security/group_security.xml', 'security/ir.model.access.csv', @@ -18,6 +18,7 @@ 'views/common_view.xml', 'views/fixture_view.xml', 'views/functional_fixture_view.xml', + # 'views/quick_easy_order_view.xml', 'views/menu_view.xml', "views/tool_views.xml", "views/tool_menu.xml", diff --git a/sf_base/models/__init__.py b/sf_base/models/__init__.py index cc906ab8..20bf8c6b 100644 --- a/sf_base/models/__init__.py +++ b/sf_base/models/__init__.py @@ -3,6 +3,8 @@ from . import common from . import tool_base_new from . import fixture from . import functional_fixture +# from . import quick_easy_order + diff --git a/sf_base/models/base.py b/sf_base/models/base.py index ef7a89fb..042b77f4 100644 --- a/sf_base/models/base.py +++ b/sf_base/models/base.py @@ -312,7 +312,8 @@ class MachineToolType(models.Model): machine_tool_type_ids = [] for item in machine_tool_type_code: machine_tool_type = self.search([('code', '=', item)]) - machine_tool_type_ids.append(machine_tool_type.id) + if machine_tool_type: + machine_tool_type_ids.append(machine_tool_type.id) return [(6, 0, machine_tool_type_ids)] diff --git a/sf_base/models/quick_easy_order.py b/sf_base/models/quick_easy_order.py new file mode 100644 index 00000000..c39b3fc5 --- /dev/null +++ b/sf_base/models/quick_easy_order.py @@ -0,0 +1,26 @@ +from odoo import models, fields +import datetime +import base64 + + +class QuickEasyOrder(models.Model): + _name = 'quick.easy.order' + _description = '简易下单' + + name = fields.Char('订单编号', default=lambda self: self.env['ir.sequence'].next_by_code('quick.easy.order')) + machining_precision = fields.Selection([ + ('0.10', '±0.10mm'), + ('0.05', '±0.05mm'), + ('0.03', '±0.03mm'), + ('0.02', '±0.02mm'), + ('0.01', '±0.01mm')], string='加工精度') + material_id = fields.Many2one('sf.production.materials', '材料') + material_model_id = fields.Many2one('sf.materials.model', '型号') + process_id = fields.Many2one('sf.production.process', string='表面工艺') + parameter_ids = fields.One2many('sf.production.process.parameter', 'process_id', string='可选参数') + quantity = fields.Integer('数量') + price = fields.Float('总价') + model_file = fields.Binary('模型文件') + upload_model_file = fields.Many2many('ir.attachment', 'upload_qf_model_file_attachment_ref', string='上传模型文件') + delivery_time = fields.Date('交货日期') + customer_id = fields.Many2one('res.partner', string='客户', default=lambda self: self.env.user.partner_id.id) diff --git a/sf_base/views/quick_easy_order_view.xml b/sf_base/views/quick_easy_order_view.xml new file mode 100644 index 00000000..5126afb1 --- /dev/null +++ b/sf_base/views/quick_easy_order_view.xml @@ -0,0 +1,91 @@ + + + + + 快速订单编码规则 + quick.easy.order + FP-%(year)s-%(month)s%(day)s- + 4 + + + # ---------- 快速订单 ------------ + + + tree.quick.easy.order + quick.easy.order + + + + + + + + + + + + + + + + form.quick.easy.order + quick.easy.order + +
+ + +
+
+
+ + + search.quick.easy.order + quick.easy.order + + + + + + + + + + + 快速订单 + quick.easy.order + tree,form + [] + {} + +

+ [便捷订单] 还没有哦!点左上角的[创建]按钮,沙发归你了! +

+

+

+
+
+ + + + + + +
+
\ No newline at end of file diff --git a/sf_base/views/tool_menu.xml b/sf_base/views/tool_menu.xml index 91d95b37..c426195e 100644 --- a/sf_base/views/tool_menu.xml +++ b/sf_base/views/tool_menu.xml @@ -2,35 +2,35 @@ - + 刀具物料 ir.actions.act_window sf.cutting.tool.material tree - + 刀具类型 ir.actions.act_window sf.cutting.tool.type tree - + 刀具型号 ir.actions.act_window sf.cutting.tool.model tree,form - + 功能刀具 ir.actions.act_window sf.functional.cutting.tool tree,form - + 功能刀具类型 ir.actions.act_window @@ -38,52 +38,53 @@ tree - + - + /> + - + /> + - + /> + - + /> + - + /> + + /> + diff --git a/sf_bf_connect/models/http.py b/sf_bf_connect/models/http.py index eb969eb6..6b0b3ebd 100644 --- a/sf_bf_connect/models/http.py +++ b/sf_bf_connect/models/http.py @@ -35,8 +35,8 @@ class Http(models.AbstractModel): timestamp_str = int(time.time()) # 设置API接口请求时间,不能超过5秒 deltime = datetime.timedelta(seconds=5) - if abs(int(datas['HTTP_TIMESTAMP'])-timestamp_str) > deltime.seconds: - raise AuthenticationError('请求已过期') + # if abs(int(datas['HTTP_TIMESTAMP'])-timestamp_str) > deltime.seconds: + # raise AuthenticationError('请求已过期') # 获得sha1_str加密字符串 post_time = int(datas['HTTP_TIMESTAMP']) check_str = '%s%s%s' % (datas['HTTP_TOKEN'], post_time, factory_secret.sf_secret_key) diff --git a/sf_dlm/models/__init__.py b/sf_dlm/models/__init__.py index 2e6676ee..f6ca84cc 100644 --- a/sf_dlm/models/__init__.py +++ b/sf_dlm/models/__init__.py @@ -1,5 +1,5 @@ -from . import product_template -from. import product_supplierinfo +# from . import product_template +from . import product_supplierinfo diff --git a/sf_dlm/models/product_template.py b/sf_dlm/models/product_template.py index 23576de7..c6719361 100644 --- a/sf_dlm/models/product_template.py +++ b/sf_dlm/models/product_template.py @@ -9,8 +9,8 @@ import hashlib import os -class ResProduct(models.Model): - _inherit = 'product.template' +# class ResProduct(models.Model): +# _inherit = 'product.template' # image_1920 = fields.Image(related='cutting_tool_parameter_image', store=True, # domain=[('cutting_tool_parameter_image', '!=', False)]) @@ -184,194 +184,6 @@ class ResProduct(models.Model): # if self.cutting_tool_parameter_nut <= 0: # raise ValueError('该产品中配对螺母不能为零,请确认并重新输入!') - def _get_volume_uom_id_from_ir_config_parameter(self): - product_length_in_feet_param = self.env['ir.config_parameter'].sudo().get_param('product.volume_in_cubic_feet') - if product_length_in_feet_param == '1': - return self.env.ref('uom.product_uom_cubic_foot') - else: - return self.env.ref('sf_dlm.product_uom_cubic_millimeter') - - # 业务平台分配工厂后在智能工厂先创建销售订单再创建该产品 - def product_create(self, product_id, item, order_id, order_number, i): - copy_product_id = product_id.with_user(self.env.ref("base.user_admin")).copy() - copy_product_id.product_tmpl_id.active = True - model_type = self.env['sf.model.type'].search([], limit=1) - attachment = self.attachment_create(item['model_name'], item['model_data']) - vals = { - 'name': '%s-%s-%s' % ('P', order_id.name, i), - 'model_long': item['model_long'] + model_type.embryo_tolerance, - 'model_width': item['model_width'] + model_type.embryo_tolerance, - 'model_height': item['model_height'] + model_type.embryo_tolerance, - 'model_volume': (item['model_long'] + model_type.embryo_tolerance) * ( - item['model_width'] + model_type.embryo_tolerance) * ( - item['model_height'] + model_type.embryo_tolerance), - 'product_model_type_id': model_type.id, - 'model_processing_panel': 'R', - 'model_machining_precision': item['model_machining_precision'], - 'model_code': item['barcode'], - 'length': item['model_long'], - 'width': item['model_width'], - 'height': item['model_height'], - 'volume': item['model_long'] * item['model_width'] * item['model_height'], - 'model_file': '' if not item['model_file'] else base64.b64decode(item['model_file']), - 'model_name': attachment.name, - 'upload_model_file': [(6, 0, [attachment.id])], - # 'tag_ids': [(6, 0, [t.id for t in account_template.tag_ids])], - # 'single_manufacturing': True, - # 'tracking': 'serial', - 'list_price': item['price'], - # 'categ_id': self.env.ref('sf_dlm.product_category_finished_sf').id, - 'materials_id': self.env['sf.production.materials'].search( - [('materials_no', '=', item['texture_code'])]).id, - 'materials_type_id': self.env['sf.materials.model'].search( - [('materials_no', '=', item['texture_type_code'])]).id, - # 'model_surface_process_ids': self.get_production_process_id(item['surface_process_code']), - 'model_process_parameters_ids': self.get_process_parameters_id(item['process_parameters_code']), - 'model_remark': item['remark'], - 'default_code': '%s-%s' % (order_number, i), - # 'barcode': item['barcode'], - 'active': True, - # 'route_ids': self._get_routes('') - } - copy_product_id.sudo().write(vals) - # product_id.product_tmpl_id.active = False - return copy_product_id - - def _get_ids(self, param): - type_ids = [] - if not param: - return [] - for item in param: - type_ids.append(item.id) - return [(6, 0, type_ids)] - - def get_process_parameters_id(self, process_parameters_code): - process_parameters_ids = [] - for item in process_parameters_code: - process_parameters = self.env['sf.production.process.parameter'].search([('code', '=', item)]) - process_parameters_ids.append(process_parameters.id) - return [(6, 0, process_parameters_ids)] - - def attachment_create(self, name, data): - attachment = self.env['ir.attachment'].create({ - 'datas': base64.b64decode(data), - 'type': 'binary', - 'public': True, - 'description': '模型文件', - 'name': name - }) - return attachment - - # 创建坯料 - def no_bom_product_create(self, product_id, item, order_id, route_type, i): - no_bom_copy_product_id = product_id.with_user(self.env.ref("base.user_admin")).copy() - no_bom_copy_product_id.product_tmpl_id.active = True - materials_id = self.env['sf.production.materials'].search( - [('materials_no', '=', item['texture_code'])]) - materials_type_id = self.env['sf.materials.model'].search( - [('materials_no', '=', item['texture_type_code'])]) - model_type = self.env['sf.model.type'].search([], limit=1) - supplier = self.env['mrp.bom'].get_supplier(materials_type_id) - logging.info('no_bom_copy_product_supplier-vals:%s' % supplier) - vals = { - 'name': '%s-%s-%s [%s %s-%s * %s * %s]' % ('R', - order_id.name, i, materials_id.name, materials_type_id.name, - item['model_long'] + model_type.embryo_tolerance, - item['model_width'] + model_type.embryo_tolerance, - item['model_height'] + model_type.embryo_tolerance), - 'length': item['model_long'] + model_type.embryo_tolerance, - 'width': item['model_width'] + model_type.embryo_tolerance, - 'height': item['model_height'] + model_type.embryo_tolerance, - 'volume': (item['model_long'] + model_type.embryo_tolerance) * ( - item['model_width'] + model_type.embryo_tolerance) * ( - item['model_height'] + model_type.embryo_tolerance), - 'embryo_model_type_id': model_type.id, - 'list_price': item['price'], - 'materials_id': materials_id.id, - 'materials_type_id': materials_type_id.id, - 'is_bfm': True, - # 'route_ids': self._get_routes(route_type), - # 'categ_id': self.env.ref('sf_dlm.product_category_embryo_sf').id, - # 'model_surface_process_id': self.env['sf.production.process'].search( - # [('process_encode', '=', item['surface_process_code'])]).id, - # 'model_process_parameters_id': self.env['sf.processing.technology'].search( - # [('process_encode', '=', item['process_parameters_code'])]).id, - 'active': True - } - # 外协和采购生成的坯料需要根据材料型号绑定供应商 - if route_type == 'subcontract' or route_type == 'purchase': - no_bom_copy_product_id.purchase_ok = True - no_bom_copy_product_id.seller_ids = [ - (0, 0, {'partner_id': supplier.partner_id.id, 'delay': 1.0})] - if route_type == 'subcontract': - partner = self.env['res.partner'].search([('id', '=', supplier.partner_id.id)]) - partner.is_subcontractor = True - no_bom_copy_product_id.write(vals) - logging.info('no_bom_copy_product_id-vals:%s' % vals) - # product_id.product_tmpl_id.active = False - return no_bom_copy_product_id - - @api.model_create_multi - def create(self, vals_list): - for vals in vals_list: - if vals.get('upload_model_file'): - if vals.get('is_bfm') is False and vals.get('categ_type') == '成品': - for item in vals['upload_model_file']: - logging.info('create-attachment:%s' % int(item[2][0])) - attachment = self.env['ir.attachment'].sudo().search([('id', '=', int(item[2][0]))]) - base64_data = base64.b64encode(attachment.datas) - base64_datas = base64_data.decode('utf-8') - model_code = hashlib.sha1(base64_datas.encode('utf-8')).hexdigest() - report_path = attachment._full_path(attachment.store_fname) - vals['model_file'] = self.transition_glb_file(report_path, model_code) - logging.info('create-model_file:%s' % len(vals['model_file'])) - self._sanitize_vals(vals) - templates = super(ResProduct, self).create(vals_list) - if "create_product_product" not in self._context: - templates._create_variant_ids() - - # This is needed to set given values to first variant after creation - for template, vals in zip(templates, vals_list): - related_vals = {} - for field_name in self._get_related_fields_variant_template(): - if vals.get(field_name): - related_vals[field_name] = vals[field_name] - if related_vals: - template.write(related_vals) - return templates - - @api.onchange('upload_model_file') - def onchange_model_file(self): - for item in self: - if item.upload_model_file: - if len(item.upload_model_file) > 1: - raise ValidationError('只允许上传一个文件') - manufacturing_order = self.env['mrp.production'].search([('product_id', '=', self.id)]) - if manufacturing_order: - raise ValidationError('该产品已生成制造订单,无法进行修改') - file_attachment_id = item.upload_model_file[0] - # 附件路径 - report_path = file_attachment_id._full_path(file_attachment_id.store_fname) - base64_data = base64.b64encode(file_attachment_id.datas) - base64_datas = base64_data.decode('utf-8') - model_code = hashlib.sha1(base64_datas.encode('utf-8')).hexdigest() - item.model_file = self.transition_glb_file(report_path, model_code) - - # 将attach的datas内容转为glb文件 - def transition_glb_file(self, report_path, code): - shapes = read_step_file(report_path) - output_file = os.path.join('/tmp', str(code) + '.stl') - write_stl_file(shapes, output_file, 'binary', 0.03, 0.5) - # 转化为glb - output_glb_file = os.path.join('/tmp', str(code) + '.glb') - util_path = get_resource_path('sf_dlm', 'static/util') - cmd = 'python3 %s/stl2gltf.py %s %s -b' % (util_path, output_file, output_glb_file) - os.system(cmd) - # 转base64 - with open(output_glb_file, 'rb') as fileObj: - image_data = fileObj.read() - base64_data = base64.b64encode(image_data) - return base64_data # @api.onchange('cutting_tool_material_id') # def _get_cutting_tool_material_info(self): diff --git a/sf_manufacturing/models/product_template.py b/sf_manufacturing/models/product_template.py index aa9da848..eac02928 100644 --- a/sf_manufacturing/models/product_template.py +++ b/sf_manufacturing/models/product_template.py @@ -1,4 +1,12 @@ from odoo import models, fields, api +from odoo.exceptions import ValidationError +from odoo.modules import get_resource_path +from OCC.Extend.DataExchange import read_step_file +from OCC.Extend.DataExchange import write_stl_file +import logging +import base64 +import hashlib +import os class ResProductMo(models.Model): @@ -318,8 +326,276 @@ class ResProductMo(models.Model): item.cutting_tool_blade_ids = False item.cutting_tool_handle_ids = False + def _get_volume_uom_id_from_ir_config_parameter(self): + product_length_in_feet_param = self.env['ir.config_parameter'].sudo().get_param('product.volume_in_cubic_feet') + if product_length_in_feet_param == '1': + return self.env.ref('uom.product_uom_cubic_foot') + else: + return self.env.ref('sf_dlm.product_uom_cubic_millimeter') + + # 业务平台分配工厂后在智能工厂先创建销售订单再创建该产品 + def product_create(self, product_id, item, order_id, order_number, i): + copy_product_id = product_id.with_user(self.env.ref("base.user_admin")).copy() + copy_product_id.product_tmpl_id.active = True + model_type = self.env['sf.model.type'].search([], limit=1) + attachment = self.attachment_create(item['model_name'], item['model_data']) + vals = { + 'name': '%s-%s-%s' % ('P', order_id.name, i), + 'model_long': item['model_long'] + model_type.embryo_tolerance, + 'model_width': item['model_width'] + model_type.embryo_tolerance, + 'model_height': item['model_height'] + model_type.embryo_tolerance, + 'model_volume': (item['model_long'] + model_type.embryo_tolerance) * ( + item['model_width'] + model_type.embryo_tolerance) * ( + item['model_height'] + model_type.embryo_tolerance), + 'product_model_type_id': model_type.id, + 'model_processing_panel': 'R', + 'model_machining_precision': item['model_machining_precision'], + 'model_code': item['barcode'], + 'length': item['model_long'], + 'width': item['model_width'], + 'height': item['model_height'], + 'volume': item['model_long'] * item['model_width'] * item['model_height'], + 'model_file': '' if not item['model_file'] else base64.b64decode(item['model_file']), + 'model_name': attachment.name, + 'upload_model_file': [(6, 0, [attachment.id])], + # 'tag_ids': [(6, 0, [t.id for t in account_template.tag_ids])], + # 'single_manufacturing': True, + # 'tracking': 'serial', + 'list_price': item['price'], + # 'categ_id': self.env.ref('sf_dlm.product_category_finished_sf').id, + 'materials_id': self.env['sf.production.materials'].search( + [('materials_no', '=', item['texture_code'])]).id, + 'materials_type_id': self.env['sf.materials.model'].search( + [('materials_no', '=', item['texture_type_code'])]).id, + # 'model_surface_process_ids': self.get_production_process_id(item['surface_process_code']), + 'model_process_parameters_ids': [(6, 0, [])] if not item.get('process_parameters_code') else self.get_process_parameters_id(item['process_parameters_code']), + 'model_remark': item['remark'], + 'default_code': '%s-%s' % (order_number, i), + # 'barcode': item['barcode'], + 'active': True, + # 'route_ids': self._get_routes('') + } + copy_product_id.sudo().write(vals) + # product_id.product_tmpl_id.active = False + return copy_product_id + + def _get_ids(self, param): + type_ids = [] + if not param: + return [] + for item in param: + type_ids.append(item.id) + return [(6, 0, type_ids)] + + def get_process_parameters_id(self, process_parameters_code): + process_parameters_ids = [] + for item in process_parameters_code: + process_parameters = self.env['sf.production.process.parameter'].search([('code', '=', item)]) + process_parameters_ids.append(process_parameters.id) + return [(6, 0, process_parameters_ids)] + + def attachment_create(self, name, data): + attachment = self.env['ir.attachment'].create({ + 'datas': base64.b64decode(data), + 'type': 'binary', + 'public': True, + 'description': '模型文件', + 'name': name + }) + return attachment + + # 创建坯料 + def no_bom_product_create(self, product_id, item, order_id, route_type, i): + no_bom_copy_product_id = product_id.with_user(self.env.ref("base.user_admin")).copy() + no_bom_copy_product_id.product_tmpl_id.active = True + materials_id = self.env['sf.production.materials'].search( + [('materials_no', '=', item['texture_code'])]) + materials_type_id = self.env['sf.materials.model'].search( + [('materials_no', '=', item['texture_type_code'])]) + model_type = self.env['sf.model.type'].search([], limit=1) + supplier = self.env['mrp.bom'].get_supplier(materials_type_id) + logging.info('no_bom_copy_product_supplier-vals:%s' % supplier) + vals = { + 'name': '%s-%s-%s [%s %s-%s * %s * %s]' % ('R', + order_id.name, i, materials_id.name, materials_type_id.name, + item['model_long'] + model_type.embryo_tolerance, + item['model_width'] + model_type.embryo_tolerance, + item['model_height'] + model_type.embryo_tolerance), + 'length': item['model_long'] + model_type.embryo_tolerance, + 'width': item['model_width'] + model_type.embryo_tolerance, + 'height': item['model_height'] + model_type.embryo_tolerance, + 'volume': (item['model_long'] + model_type.embryo_tolerance) * ( + item['model_width'] + model_type.embryo_tolerance) * ( + item['model_height'] + model_type.embryo_tolerance), + 'embryo_model_type_id': model_type.id, + 'list_price': item['price'], + 'materials_id': materials_id.id, + 'materials_type_id': materials_type_id.id, + 'is_bfm': True, + # 'route_ids': self._get_routes(route_type), + # 'categ_id': self.env.ref('sf_dlm.product_category_embryo_sf').id, + # 'model_surface_process_id': self.env['sf.production.process'].search( + # [('process_encode', '=', item['surface_process_code'])]).id, + # 'model_process_parameters_id': self.env['sf.processing.technology'].search( + # [('process_encode', '=', item['process_parameters_code'])]).id, + 'active': True + } + # 外协和采购生成的坯料需要根据材料型号绑定供应商 + if route_type == 'subcontract' or route_type == 'purchase': + no_bom_copy_product_id.purchase_ok = True + no_bom_copy_product_id.seller_ids = [ + (0, 0, {'partner_id': supplier.partner_id.id, 'delay': 1.0})] + if route_type == 'subcontract': + partner = self.env['res.partner'].search([('id', '=', supplier.partner_id.id)]) + partner.is_subcontractor = True + no_bom_copy_product_id.write(vals) + logging.info('no_bom_copy_product_id-vals:%s' % vals) + # product_id.product_tmpl_id.active = False + return no_bom_copy_product_id + + @api.model_create_multi + def create(self, vals_list): + for vals in vals_list: + if vals.get('upload_model_file'): + if vals.get('is_bfm') is False and vals.get('categ_type') == '成品': + for item in vals['upload_model_file']: + logging.info('create-attachment:%s' % int(item[2][0])) + attachment = self.env['ir.attachment'].sudo().search([('id', '=', int(item[2][0]))]) + base64_data = base64.b64encode(attachment.datas) + base64_datas = base64_data.decode('utf-8') + model_code = hashlib.sha1(base64_datas.encode('utf-8')).hexdigest() + report_path = attachment._full_path(attachment.store_fname) + vals['model_file'] = self.transition_glb_file(report_path, model_code) + logging.info('create-model_file:%s' % len(vals['model_file'])) + self._sanitize_vals(vals) + templates = super(ResProductMo, self).create(vals_list) + if "create_product_product" not in self._context: + templates._create_variant_ids() + + # This is needed to set given values to first variant after creation + for template, vals in zip(templates, vals_list): + related_vals = {} + for field_name in self._get_related_fields_variant_template(): + if vals.get(field_name): + related_vals[field_name] = vals[field_name] + if related_vals: + template.write(related_vals) + return templates + + @api.onchange('upload_model_file') + def onchange_model_file(self): + for item in self: + if item.upload_model_file: + if len(item.upload_model_file) > 1: + raise ValidationError('只允许上传一个文件') + manufacturing_order = self.env['mrp.production'].search([('product_id', '=', self.id)]) + if manufacturing_order: + raise ValidationError('该产品已生成制造订单,无法进行修改') + file_attachment_id = item.upload_model_file[0] + # 附件路径 + report_path = file_attachment_id._full_path(file_attachment_id.store_fname) + base64_data = base64.b64encode(file_attachment_id.datas) + base64_datas = base64_data.decode('utf-8') + model_code = hashlib.sha1(base64_datas.encode('utf-8')).hexdigest() + item.model_file = self.transition_glb_file(report_path, model_code) + + # 将attach的datas内容转为glb文件 + def transition_glb_file(self, report_path, code): + shapes = read_step_file(report_path) + output_file = os.path.join('/tmp', str(code) + '.stl') + write_stl_file(shapes, output_file, 'binary', 0.03, 0.5) + # 转化为glb + output_glb_file = os.path.join('/tmp', str(code) + '.glb') + util_path = get_resource_path('sf_dlm', 'static/util') + cmd = 'python3 %s/stl2gltf.py %s %s -b' % (util_path, output_file, output_glb_file) + os.system(cmd) + # 转base64 + with open(output_glb_file, 'rb') as fileObj: + image_data = fileObj.read() + base64_data = base64.b64encode(image_data) + return base64_data + class ResMrpBomMo(models.Model): _inherit = 'mrp.bom' subcontractor_id = fields.Many2one('res.partner', string='外包商') + + def bom_create_line_has(self, embryo): + vals = { + 'bom_id': self.id, + 'product_id': embryo.id, + 'product_tmpl_id': embryo.product_tmpl_id.id, + 'product_qty': 1, + 'product_uom_id': 1 + } + return self.env['mrp.bom.line'].create(vals) + + # 业务平台分配工厂后在智能工厂先创建销售订单再创建该产品后再次进行创建bom + + def bom_create(self, product, bom_type, product_type): + bom_id = self.env['mrp.bom'].create({ + 'product_tmpl_id': product.product_tmpl_id.id, + 'type': bom_type, + # 'subcontractor_id': '' or subcontract.partner_id.id, + 'product_qty': 1, + 'product_uom_id': 1 + }) + if bom_type == 'subcontract' and product_type is not False: + subcontract = self.get_supplier(product.materials_type_id) + bom_id.subcontractor_id = subcontract.partner_id.id + return bom_id + + # 坯料BOM组件:选取当前坯料原材料, + # 然后根据当前的坯料的体积得出需要的原材料重量(立方米m³) *材料密度 * 1000 = 所需原材料重量KG(公斤) + # 坯料所需原材料公式:当前的坯料的体积(立方米m³) *材料密度 * 1000 = 所需原材料重量KG(公斤) + + def bom_create_line(self, embryo): + # 选取当前坯料原材料 + raw_bom_line = self.get_raw_bom(embryo) + if raw_bom_line: + bom_line = self.env['mrp.bom.line'].create({ + 'bom_id': self.id, + 'product_id': raw_bom_line.id, + 'product_tmpl_id': raw_bom_line.product_tmpl_id.id, + 'product_qty': round(embryo.volume * raw_bom_line.materials_type_id.density / 1000000), + 'product_uom_id': raw_bom_line.uom_id.id, + }) + return bom_line + else: + return False + + # 查询材料型号默认排第一的供应商 + + def get_supplier(self, materials_type): + seller_id = self.env['sf.supplier.sort'].search( + [('materials_model_id', '=', materials_type.id)], + limit=1, + order='sequence asc') + return seller_id + + # 匹配bom + + def get_bom(self, product): + embryo_has = self.env['product.product'].search( + [('categ_id.type', '=', '坯料'), ('materials_type_id', '=', product.materials_type_id.id), + ('length', '>', product.length), ('width', '>', product.width), + ('height', '>', product.height), ('is_bfm', '=', False) + ], + limit=1, + order='volume desc' + ) + logging.info('get_bom-vals:%s' % embryo_has) + if embryo_has: + rate_of_waste = ((embryo_has.volume - product.model_volume) % embryo_has.volume) * 100 + if rate_of_waste <= 20: + return embryo_has + else: + return + + # 查bom的原材料 + + def get_raw_bom(self, product): + raw_bom = self.env['product.product'].search( + [('categ_id.type', '=', '原材料'), ('materials_type_id', '=', product.materials_type_id.id)]) + return raw_bom diff --git a/sf_mrs_connect/models/res_config_setting.py b/sf_mrs_connect/models/res_config_setting.py index d3aa687a..c158ab09 100644 --- a/sf_mrs_connect/models/res_config_setting.py +++ b/sf_mrs_connect/models/res_config_setting.py @@ -13,6 +13,7 @@ class ResConfigSettings(models.TransientModel): token = fields.Char(string='TOKEN', default='b811ac06-3f00-11ed-9aed-0242ac110003') sf_secret_key = fields.Char(string='密钥', default='wBmxej38OkErKhD6') sf_url = fields.Char(string='访问地址', default='https://sf.cs.jikimo.com') + bfm_url = fields.Char(string='业务平台后端访问地址', default='https://bfm.jikimo.com') ftp_host = fields.Char(string='FTP的ip') ftp_port = fields.Char(string='FTP端口') ftp_user = fields.Char(string='FTP用户') @@ -81,6 +82,7 @@ class ResConfigSettings(models.TransientModel): token = config.get_param('token', default='') sf_secret_key = config.get_param('sf_secret_key', default='') sf_url = config.get_param('sf_url', default='') + bfm_url = config.get_param('bfm_url', default='') ftp_host = config.get_param('ftp_host', default='') ftp_port = config.get_param('ftp_port', default='') ftp_user = config.get_param('ftp_user', default='') @@ -90,6 +92,7 @@ class ResConfigSettings(models.TransientModel): token=token, sf_secret_key=sf_secret_key, sf_url=sf_url, + bfm_url=bfm_url, ftp_host=ftp_host, ftp_port=ftp_port, ftp_user=ftp_user, @@ -103,6 +106,7 @@ class ResConfigSettings(models.TransientModel): ir_config.set_param("token", self.token or "") ir_config.set_param("sf_secret_key", self.sf_secret_key or "") ir_config.set_param("sf_url", self.sf_url or "") + ir_config.set_param("bfm_url", self.bfm_url or "") ir_config.set_param("ftp_host", self.ftp_host or "") ir_config.set_param("ftp_port", self.ftp_port or "") ir_config.set_param("ftp_user", self.ftp_user or "") diff --git a/sf_mrs_connect/models/sync_common.py b/sf_mrs_connect/models/sync_common.py index e752c483..3d309689 100644 --- a/sf_mrs_connect/models/sync_common.py +++ b/sf_mrs_connect/models/sync_common.py @@ -1525,7 +1525,7 @@ class SyncFixtureModel(models.Model): [('materials_no', '=', item['materials_model_code'])]).id, "driving_way": item['driving_way'], "apply_machine_tool_type_ids": self.env['sf.machine_tool.type'].sudo()._get_ids( - item['apply_machine_tool_type_code']).id, + item['apply_machine_tool_type_code']), "through_hole_size": item['through_hole_size'], "screw_size": item['screw_size'], "active": item['active'], @@ -1555,7 +1555,7 @@ class SyncFixtureModel(models.Model): [('materials_no', '=', item['materials_model_code'])]).id, "driving_way": item['driving_way'], "apply_machine_tool_type_ids": self.env['sf.machine_tool.type'].sudo()._get_ids( - item['apply_machine_tool_type_code']).id, + item['apply_machine_tool_type_code']), "through_hole_size": item['through_hole_size'], "screw_size": item['screw_size'], "active": item['active'], @@ -1607,7 +1607,7 @@ class SyncFixtureModel(models.Model): [('materials_no', '=', item['materials_model_code'])]).id, "driving_way": item['driving_way'], "apply_machine_tool_type_ids": self.env['sf.machine_tool.type'].sudo()._get_ids( - item['apply_machine_tool_type_code']).id, + item['apply_machine_tool_type_code']), "through_hole_size": item['through_hole_size'], "screw_size": item['screw_size'], "active": item['active'], @@ -1637,7 +1637,7 @@ class SyncFixtureModel(models.Model): [('materials_no', '=', item['materials_model_code'])]).id, "driving_way": item['driving_way'], "apply_machine_tool_type_ids": self.env['sf.machine_tool.type'].sudo()._get_ids( - item['apply_machine_tool_type_code']).id, + item['apply_machine_tool_type_code']), "through_hole_size": item['through_hole_size'], "screw_size": item['screw_size'], "active": item['active'], diff --git a/sf_mrs_connect/views/res_config_settings_views.xml b/sf_mrs_connect/views/res_config_settings_views.xml index e8d81453..bc829b2f 100644 --- a/sf_mrs_connect/views/res_config_settings_views.xml +++ b/sf_mrs_connect/views/res_config_settings_views.xml @@ -60,6 +60,20 @@ +
+

业务平台参数配置

+
+
+
+
+
+
+
+
+
+
diff --git a/sf_sale/__manifest__.py b/sf_sale/__manifest__.py index 159f9085..b3cb99a1 100644 --- a/sf_sale/__manifest__.py +++ b/sf_sale/__manifest__.py @@ -10,9 +10,12 @@ """, 'category': 'sf', 'website': 'https://www.sf.jikimo.com', - 'depends': ['sale', 'sale_management', 'web_widget_model_viewer'], + 'depends': ['sale_management', 'web_widget_model_viewer',], 'data': [ - 'views/sale_order_view.xml' + 'security/group_security.xml', + 'security/ir.model.access.csv', + 'views/sale_order_view.xml', + 'views/quick_easy_order_view.xml' ], 'demo': [ ], diff --git a/sf_sale/models/__init__.py b/sf_sale/models/__init__.py index b358ccc1..577497ab 100644 --- a/sf_sale/models/__init__.py +++ b/sf_sale/models/__init__.py @@ -1 +1,4 @@ from. import sale_order +from. import quick_easy_order + + diff --git a/sf_sale/models/auto_quatotion_common.py b/sf_sale/models/auto_quatotion_common.py new file mode 100644 index 00000000..09ea3f0f --- /dev/null +++ b/sf_sale/models/auto_quatotion_common.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +import logging +from odoo.modules import get_resource_path +from odoo import fields, models, api +from quatotion import readSql, feature_recognize, auto_quatotion + +__author__ = 'jinling.yang' +_logger = logging.getLogger(__name__) + + +class AutoQuatotion(models.Model): + _name = 'sf.auto_quatotion.common' + _description = u'自动报价公用类' + + # 获取feature.sqlite的地址 + def get_feature_full_path(self): + return get_resource_path('sf_sale', 'models', 'feature.sqlite') + + # 获取price.sqlite的地址 + def get_price_full_path(self): + return get_resource_path('sf_sale', 'models', 'price.sqlite') + + # 获取price.sqlite的地址 + def get_process_time_db_path(self): + return get_resource_path('sf_sale', 'models', 'process_time.db') + + def get_auto_quatotion(self, stp_url, feature_full_path, process_time_db_path, model_code): + ''' + 通过打包好的.so库, + 以调用autoQuatotion库中Quatotion类, + 初始化后调用类的analyseShape方法对模型文件进行价格预测 + ''' + # 初始化自动报价类(输入特征数据库和加工时间数据库) + reader = auto_quatotion.Quatotion(feature_full_path, process_time_db_path) + # 获取价格、加工时间、尺寸、XYZ、翻面次数 + feature_info = reader.analyseShape(stp_url, InfoJson={}) + return feature_info diff --git a/sf_sale/models/feature.sqlite b/sf_sale/models/feature.sqlite new file mode 100644 index 00000000..f03f3437 Binary files /dev/null and b/sf_sale/models/feature.sqlite differ diff --git a/sf_sale/models/price.sqlite b/sf_sale/models/price.sqlite new file mode 100644 index 00000000..50faad92 Binary files /dev/null and b/sf_sale/models/price.sqlite differ diff --git a/sf_sale/models/process_time.db b/sf_sale/models/process_time.db new file mode 100644 index 00000000..0b48a3da Binary files /dev/null and b/sf_sale/models/process_time.db differ diff --git a/sf_sale/models/quick_easy_order.py b/sf_sale/models/quick_easy_order.py new file mode 100644 index 00000000..369851b3 --- /dev/null +++ b/sf_sale/models/quick_easy_order.py @@ -0,0 +1,281 @@ +from odoo import models, fields, api +from odoo.modules import get_resource_path +from OCC.Extend.DataExchange import read_step_file +from OCC.Extend.DataExchange import write_stl_file +from odoo.exceptions import ValidationError, UserError +from odoo.addons.sf_base.commons.common import Common +from datetime import datetime +import logging +import base64 +import hashlib +import os +import json +import requests + + +class QuickEasyOrder(models.Model): + _name = 'quick.easy.order' + _description = '简易下单' + + name = fields.Char('订单编号', default=lambda self: self.env['ir.sequence'].next_by_code('quick.easy.order')) + model_length = fields.Float('长(mm)', digits=(16, 3)) + model_width = fields.Float('宽(mm)', digits=(16, 3)) + model_height = fields.Float('高(mm)', digits=(16, 3)) + model_volume = fields.Float('体积(mm³)', digits=(16, 3)) + model_processing_side = fields.Char('加工面', default='A') + model_feature = fields.Char('特征') + machining_precision = fields.Selection([ + ('0.10', '±0.10mm'), + ('0.05', '±0.05mm'), + ('0.03', '±0.03mm'), + ('0.02', '±0.02mm'), + ('0.01', '±0.01mm')], string='加工精度', default='0.10') + material_id = fields.Many2one('sf.production.materials', '材料', compute='_compute_material_model', store=True) + material_model_id = fields.Many2one('sf.materials.model', '型号', compute='_compute_material_model', store=True) + process_id = fields.Many2one('sf.production.process', string='表面工艺') + parameter_ids = fields.One2many('sf.production.process.parameter', 'process_id', string='可选参数') + quantity = fields.Integer('数量', default=1) + unit_price = fields.Float('单价') + price = fields.Float('总价') + model_file = fields.Binary('模型文件') + upload_model_file = fields.Many2many('ir.attachment', 'upload_qf_model_file_attachment_ref', string='模型文件') + delivery_time = fields.Date('交货日期') + customer_id = fields.Many2one('res.partner', string='客户', default=lambda self: self.env.user.partner_id.id) + state = fields.Selection([('草稿', '草稿'), ('待付款', '待付款'), ('待派单', '待派单'), + ('待接单', '待接单'), ('加工中', '加工中'), + ('物流中', '物流中'), ('已交付', '已交付')], string='订单状态', default='草稿', + readonly=True) + model_color_state = fields.Selection([ + ('success', '成功'), + ('fail', '失败')], string='模型上色状态') + + @api.depends('material_id', 'material_model_id') + def _compute_material_model(self): + for item in self: + materials = self.env['sf.production.materials'].search([], limit=1, order='id desc') + item.material_id = materials.id + item.material_model_id = self.env['sf.materials.model'].search( + [('materials_id', '=', materials.id)], + limit=1, order='id desc') + + @api.model + def create(self, vals): + if vals.get('upload_model_file'): + logging.info('create-attachment:%s' % vals['upload_model_file'][0]) + for item in vals['upload_model_file']: + print(len(item[2])) + if len(item[2]) > 0: + logging.info('create-attachment:%s' % int(item[2][0])) + attachment = self.env['ir.attachment'].sudo().search([('id', '=', int(item[2][0]))]) + base64_data = base64.b64encode(attachment.datas) + base64_datas = base64_data.decode('utf-8') + model_code = hashlib.sha1(base64_datas.encode('utf-8')).hexdigest() + report_path = attachment._full_path(attachment.store_fname) + vals['model_file'] = self.transition_glb_file(report_path, model_code) + logging.info('create-model_file:%s' % len(vals['model_file'])) + + obj = super(QuickEasyOrder, self).create(vals) + self.model_coloring() + self.distribute_to_factory(obj) + return obj + + # 将attach的datas内容转为glb文件 + def transition_glb_file(self, report_path, model_code): + shapes = read_step_file(report_path) + output_file = os.path.join('C:/Users/43484/Desktop/机企猫工作文档', str(model_code) + '.stl') + # output_file = os.path.join('/tmp', str(model_code) + '.stl') + write_stl_file(shapes, output_file, 'binary', 0.03, 0.5) + # 转化为glb + output_glb_file = os.path.join('C:/Users/43484/Desktop/机企猫工作文档', str(model_code) + '.glb') + # output_glb_file = os.path.join('/tmp', str(model_code) + '.glb') + util_path = get_resource_path('mrs_base', 'static/util') + cmd = 'python3 %s/stl2gltf.py %s %s -b' % (util_path, output_file, output_glb_file) + os.system(cmd) + # 转base64 + with open(output_glb_file, 'rb') as fileObj: + image_data = fileObj.read() + base64_data = base64.b64encode(image_data) + return base64_data + # return False + + @api.onchange('upload_model_file') + def onchange_model_file(self): + for item in self: + if len(item.upload_model_file) > 1: + raise ValidationError('只允许上传一个文件') + if item.upload_model_file: + file_attachment_id = item.upload_model_file[0] + # 附件路径 + report_path = file_attachment_id._full_path(file_attachment_id.store_fname) + logging.info("模型路径: %s" % report_path) + base64_data = base64.b64encode(file_attachment_id.datas) + base64_datas = base64_data.decode('utf-8') + model_code = hashlib.sha1(base64_datas.encode('utf-8')).hexdigest() + logging.info("模型编码: %s" % model_code) + item.model_file = self.transition_glb_file(report_path, model_code) + ret = self.feature_recognition(report_path, model_code) + logging.info("自动报价返回值: %s" % ret) + boxshape = ret['boxshape'].tolist() + logging.info("自动报价boxshape: %s" % boxshape) + logging.info('自动报价feature_infos:%s' % ret['feature_infos']) + item.model_length = boxshape[0] # 长 单位mm + item.model_width = boxshape[1] # 宽 + item.model_height = boxshape[2] # 高 + item.model_volume = boxshape[0] * boxshape[1] * boxshape[2] + item.model_feature = json.dumps(ret['feature_infos'], ensure_ascii=False) + self._get_price(item) + else: + item.model_file = False + item.model_feature = False + item.model_length = 0 + item.model_width = 0 + item.model_height = 0 + item.model_volume = 0 + + def distribute_to_factory(self, obj): + """ + 派单到工厂 + :return: + """ + web_base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url', default='') + logging.info("自动报价返回值: %s" % ret) + url = '/api/bfm_process_order/list' + res = {'order_number': obj.name, 'delivery_end_date': str(datetime.now()), + 'delivery_name': 'XXXXX', 'delivery_telephone': 'XXXXX', + 'delivery_address': 'XXXXX', + 'bfm_process_order_list': []} + factory = self.env['res.partner'].sudo().search([], limit=1, order='id desc') + config_header = Common.get_headers(self, factory.sf_token, factory.sf_secret_key) + for item in obj: + attachment = item.upload_model_file[0] + base64_data = base64.b64encode(attachment.datas) + base64_datas = base64_data.decode('utf-8') + barcode = hashlib.sha1(base64_datas.encode('utf-8')).hexdigest() + logging.info('model_file-size: %s' % len(item.model_file)) + val = { + 'model_long': 100, + 'model_width': 100, + 'model_height': 100, + 'model_volume': 300, + 'model_machining_precision': item.machining_precision, + 'model_name': attachment.name, + 'model_data': base64_datas, + 'model_file': base64.b64encode(item.model_file).decode('utf-8'), + 'texture_code': item.material_id.materials_no, + 'texture_type_code': item.material_model_id.materials_no, + # 'surface_process_code': self.env['jikimo.surface.process']._json_surface_process_code(item), + # 'process_parameters_code': self.env['jikimo.surface.process.item']._json_surface_process_item_code( + # item), + 'price': item.price, + 'number': item.quantity, + 'total_amount': item.price, + 'remark': '', + 'barcode': barcode + } + res['bfm_process_order_list'].append(val) + res['bfm_process_order_list'] = json.dumps(res['bfm_process_order_list']) + try: + ret = requests.post((web_base_url[0] + url), json={}, data=res, + headers=config_header) + ret = ret.json() + if ret['status'] == 1: + self.write( + {'state': '待接单'}) + else: + raise UserError(ret['message']) + except Exception as e: + if ret['status'] != 1: + raise UserError(e) + else: + raise UserError("分配工厂失败,请联系管理员") + + # 特征识别 + def feature_recognition(self, report_path, model_code): + feature_path = self.env['sf.auto_quatotion.common'].sudo().get_feature_full_path() + # price_path = self.env['jikimo.auto_quatotion.common'].get_price_full_path() + process_time_db_path = self.env['sf.auto_quatotion.common'].sudo().get_process_time_db_path() + ret = self.env['sf.auto_quatotion.common'].sudo().get_auto_quatotion(report_path, feature_path, + process_time_db_path, + model_code) + return ret + + # 模型上色 + def model_coloring(self): + url = '/api/library_of_models/create' + config = self.env['res.config.settings'].get_values() + config_header = Common.get_headers(self, config['sf_token'], config['sf_key_secret']) + order = self.search([('id', '=', self.id)]) + logging.info('order: %s' % order.name) + if order: + attachment = order.upload_model_file[0] + base64_data = base64.b64encode(attachment.datas) + base64_datas = base64_data.decode('utf-8') + model_code = hashlib.sha1(base64_datas.encode('utf-8')).hexdigest() + logging.info('model_file-size: %s' % len(order.model_file)) + logging.info('attachment.datas-size: %s' % len(attachment.datas)) + vals = { + 'model_code': model_code, + 'model_data': base64_data, + 'model_name': attachment.name, + 'model_long': order.model_length, + 'model_width': order.model_width, + 'model_height': order.model_height, + 'model_volume': order.model_volume, + 'model_order_no': order.name, + 'remark': '订单号:%s 客户:%s' % (order.name, order.customer_id.name) + } + try: + ret = requests.post((config['sf_url'] + url), json={}, data=vals, headers=config_header, + timeout=60) + ret = ret.json() + # result = json.loads(ret['result']) + if ret['status'] == 1: + order.model_color_state = 'success' + else: + order.model_color_state = 'fail' + raise UserError(ret['message']) + except Exception as e: + order.model_color_state = 'fail' + raise UserError("模型上色失败") + + # 自动报价 + def _get_price(self, order): + + url = '/api/automatic_quotes' + config = self.env['res.config.settings'].sudo().get_values() + config_header = Common.get_headers(self, config['sf_token'], config['sf_key_secret']) + logging.info("报价接口..........% s" % order.name) + try: + if order: + vals = {} + # mrs合作伙伴token + vals['token'] = config['sf_token'] + vals['accuracy'] = order.machining_precision + vals['number'] = order.quantity + vals['process_code'] = 0 + vals['texture_code'] = order.material_model_id.code + vals['delivery_days'] = 15 + if order.model_file: + attachment = self.env['ir.attachment'].sudo().search( + [('id', '=', order.upload_model_file[0])]) + vals['attachment_id'] = attachment.id + else: + vals['attachment_id'] = '' + vals['feature_infos'] = order.model_feature + vals['model_long'] = order.model_length + vals['model_width'] = order.model_width + vals['model_height'] = order.model_height + logging.info('vals:%s' % vals) + ret = requests.post((config['sf_url'] + url), json={}, data=vals, headers=config_header) + result = json.dumps(json.loads(ret.text), ensure_ascii=False, indent=4, separators=(',', ':')) + logging.info('报价接口返回:%s' % result) + price_result = json.loads(result) + # random.randint(0, 10000) + order.write({'price': price_result.get('price')}) + else: + raise UserError("订单不存在") + except Exception as e: + if ret['status'] != 1: + raise UserError(e) + else: + raise UserError("自动报价失败,请联系管理员") diff --git a/sf_sale/security/group_security.xml b/sf_sale/security/group_security.xml new file mode 100644 index 00000000..b8668782 --- /dev/null +++ b/sf_sale/security/group_security.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/sf_sale/security/ir.model.access.csv b/sf_sale/security/ir.model.access.csv new file mode 100644 index 00000000..9bc8bba0 --- /dev/null +++ b/sf_sale/security/ir.model.access.csv @@ -0,0 +1,9 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_quick_easy_order,quick_easy_order,model_quick_easy_order,base.group_user,1,1,1,1 + + + + + + + diff --git a/sf_sale/views/quick_easy_order_view.xml b/sf_sale/views/quick_easy_order_view.xml new file mode 100644 index 00000000..6c5f263c --- /dev/null +++ b/sf_sale/views/quick_easy_order_view.xml @@ -0,0 +1,114 @@ + + + + + 快速订单编码规则 + quick.easy.order + FP-%(year)s-%(month)s%(day)s- + 4 + + + # ---------- 快速订单 ------------ + + + tree.quick.easy.order + quick.easy.order + + + + + + + + + + + + + + + + form.quick.easy.order + quick.easy.order + +
+ +

+ +

+ + + + + + + + + + + + + + + + + +
+
+
+
+ + + search.quick.easy.order + quick.easy.order + + + + + + + + + + + 快速订单 + quick.easy.order + tree,form + [] + {} + +

+ [快速订单] 还没有哦!点左上角的[创建]按钮,沙发归你了! +

+

+

+
+
+ + + + + + +
+
\ No newline at end of file