diff --git a/sf_base/models/base.py b/sf_base/models/base.py index 5762822e..a6e1d4cc 100644 --- a/sf_base/models/base.py +++ b/sf_base/models/base.py @@ -77,6 +77,7 @@ class MachineTool(models.Model): b_axis = fields.Integer('B轴') c_axis = fields.Integer('C轴') remark = fields.Text('备注') + is_binding = fields.Boolean('是否绑定机床', default=False) precision = fields.Float('加工精度') control_system_id = fields.Many2one('sf.machine.control_system', string="控制系统") diff --git a/sf_base/views/base_view.xml b/sf_base/views/base_view.xml index ff85bfda..7d4854d3 100644 --- a/sf_base/views/base_view.xml +++ b/sf_base/views/base_view.xml @@ -359,7 +359,7 @@
-
diff --git a/sf_bf_connect/controllers/controllers.py b/sf_bf_connect/controllers/controllers.py index 4b352dcf..87b48468 100644 --- a/sf_bf_connect/controllers/controllers.py +++ b/sf_bf_connect/controllers/controllers.py @@ -8,7 +8,7 @@ from odoo.http import request class Sf_Bf_Connect(http.Controller): - @http.route('/api/bfm_process_order/list', type='json', auth='none', methods=['GET', 'POST'], csrf=False, + @http.route('/api/bfm_process_order/list', type='http', auth='none', methods=['GET', 'POST'], csrf=False, cors="*") def get_bfm_process_order_list(self, **kw): """ @@ -19,9 +19,9 @@ class Sf_Bf_Connect(http.Controller): res = {'status': 1, 'factory_order_no': ''} logging.info('get_bfm_process_order_list:%s' % kw) try: - datas = request.httprequest.data - ret = json.loads(datas) - ret = json.loads(ret['result']) + # datas = request.httprequest.data + # ret = json.loads(datas) + # ret = json.loads(ret['result']) product_id = request.env.ref('sf_dlm.product_template_sf').sudo() logging.info('product_id:%s' % product_id) self_machining_id = request.env.ref('sf_dlm.product_embryo_sf_self_machining').sudo() @@ -30,13 +30,20 @@ class Sf_Bf_Connect(http.Controller): company_id = request.env.ref('base.main_company').sudo() user_id = request.env.ref('base.user_admin').sudo() logging.info('user_id:%s' % user_id) + bfm_process_order_list = json.loads(kw['bfm_process_order_list']) order_id = request.env['sale.order'].with_user(request.env.ref("base.user_admin")).sale_order_create( - company_id, ret['delivery_name'], ret['delivery_telephone'], ret['delivery_address'], - ret['delivery_end_date'], user_id) + company_id, kw['delivery_name'], kw['delivery_telephone'], kw['delivery_address'], + kw['delivery_end_date'], user_id) i = 1 - for item in ret['bfm_process_order_list']: + for item in bfm_process_order_list: + # product_has = request.env['product.template'].with_user(request.env.ref("base.user_admin")).search([('barcode','=', item['barcode'])]) + # if product_has: + # logging.info('product_has:%s' % product_has) + # logging.info('barcode:%s' % item['barcode']) + # order_id.with_user(request.env.ref("base.user_admin")).sale_order_create_line(product_has, item) + # else: product = request.env['product.template'].sudo().product_create(product_id, item, order_id, - ret['order_number'], i) + kw['order_number'], i) # order_id.with_user(request.env.ref("base.user_admin")).sale_order_create_line(product, item) logging.info('order_id:%s' % order_id) logging.info('product:%s' % product) @@ -53,11 +60,11 @@ class Sf_Bf_Connect(http.Controller): self_machining_embryo = request.env['product.template'].sudo().no_bom_product_create( self_machining_id, item, - order_id, 'self_machining') + order_id, 'self_machining', i) # 创建胚料的bom self_machining_bom = request.env['mrp.bom'].with_user( request.env.ref("base.user_admin")).bom_create( - self_machining_embryo, 'normal') + self_machining_embryo, 'normal', False) # 创建胚料里bom的组件 self_machining_bom.with_user(request.env.ref("base.user_admin")).bom_create_line( self_machining_embryo) @@ -72,7 +79,8 @@ class Sf_Bf_Connect(http.Controller): outsource_embryo = request.env['product.template'].sudo().no_bom_product_create(outsource_id, item, order_id, - 'subcontract') + 'subcontract', + i) # 创建胚料的bom outsource_bom = request.env['mrp.bom'].with_user(request.env.ref("base.user_admin")).bom_create( outsource_embryo, @@ -88,16 +96,15 @@ class Sf_Bf_Connect(http.Controller): purchase_embryo = request.env['product.template'].sudo().no_bom_product_create(purchase_id, item, order_id, - 'purchase') + 'purchase', i) # 产品配置bom product_bom_purchase = request.env['mrp.bom'].with_user( request.env.ref("base.user_admin")).bom_create(product, 'normal', False) product_bom_purchase.with_user(request.env.ref("base.user_admin")).bom_create_line_has( purchase_embryo) + i += 1 order_id.with_user(request.env.ref("base.user_admin")).sale_order_create_line(product, item) - i += 1 res['factory_order_no'] = order_id.name - return json.JSONEncoder().encode(res) except Exception as e: logging.info('get_bfm_process_order_list error:%s' % e) res['status'] = -1 diff --git a/sf_bf_connect/models/process_status.py b/sf_bf_connect/models/process_status.py new file mode 100644 index 00000000..c89755ec --- /dev/null +++ b/sf_bf_connect/models/process_status.py @@ -0,0 +1,110 @@ +from odoo import api, fields, models, SUPERUSER_ID, _ +from odoo.exceptions import ValidationError +from datetime import datetime +import logging +from odoo.exceptions import UserError +import requests + +_logger = logging.getLogger(__name__) + +class StatusChange(models.Model): + _inherit = 'sale.order' + + default_code = fields.Char(string='内部编码') + + def action_confirm(self): + logging.info('函数已经执行=============') + if self._get_forbidden_state_confirm() & set(self.mapped('state')): + raise UserError(_( + "It is not allowed to confirm an order in the following states: %s", + ", ".join(self._get_forbidden_state_confirm()), + )) + logging.info('函数已经执行=============1') + for order in self: + if order.partner_id in order.message_partner_ids: + logging.info('函数已经执行=============2') + continue + order.message_subscribe([order.partner_id.id]) + logging.info('函数已经执行=============3') + self.write(self._prepare_confirmation_values()) + + # Context key 'default_name' is sometimes propagated up to here. + # We don't need it and it creates issues in the creation of linked records. + context = self._context.copy() + context.pop('default_name', None) + logging.info('函数已经执行=============4') + + self.with_context(context)._action_confirm() + if self.env.user.has_group('sale.group_auto_done_setting'): + logging.info('函数已经执行=============5') + self.action_done() + process_start_time = str(datetime.now()) + json1 = { + 'params': { + 'model_name': 'jikimo.process.order', + 'field_name': 'name', + # 'default_code': 'PO-2022-1214-0022', + 'default_code': self.default_code, + # 'default_code': self.name, + 'state': '加工中', + 'process_start_time': process_start_time, + }, + } + url1 = 'https://bfm.cs.jikimo.com/api/get/state/get_order' + requests.post(url1, json=json1, data=None) + logging.info('接口已经执行=============') + + return True + + def action_cancel(self): + """ Cancel SO after showing the cancel wizard when needed. (cfr `_show_cancel_wizard`) + + For post-cancel operations, please only override `_action_cancel`. + + note: self.ensure_one() if the wizard is shown. + """ + logging.info('函数已经执行=============') + cancel_warning = self._show_cancel_wizard() + logging.info('函数已经执行=============2') + json1 = { + 'params': { + 'model_name': 'jikimo.process.order', + 'field_name': 'name', + 'default_code': self.default_code, + # 'default_code': self.name, + 'state': '待派单', + }, + } + url1 = 'https://bfm.cs.jikimo.com/api/get/state/cancel_order' + requests.post(url1, json=json1, data=None) + if cancel_warning: + logging.info('函数已经执行=============3') + self.ensure_one() + logging.info('函数已经执行=============4') + template_id = self.env['ir.model.data']._xmlid_to_res_id( + 'sale.mail_template_sale_cancellation', raise_if_not_found=False + ) + lang = self.env.context.get('lang') + template = self.env['mail.template'].browse(template_id) + if template.lang: + lang = template._render_lang(self.ids)[self.id] + ctx = { + 'default_use_template': bool(template_id), + 'default_template_id': template_id, + 'default_order_id': self.id, + 'mark_so_as_canceled': True, + 'default_email_layout_xmlid': "mail.mail_notification_layout_with_responsible_signature", + 'model_description': self.with_context(lang=lang).type_name, + } + return { + 'name': _('Cancel %s', self.type_name), + 'view_mode': 'form', + 'res_model': 'sale.order.cancel', + 'view_id': self.env.ref('sale.sale_order_cancel_view_form').id, + 'type': 'ir.actions.act_window', + 'context': ctx, + 'target': 'new' + } + else: + return self._action_cancel() + diff --git a/sf_dlm/__manifest__.py b/sf_dlm/__manifest__.py index 3400d984..da6dba4b 100644 --- a/sf_dlm/__manifest__.py +++ b/sf_dlm/__manifest__.py @@ -10,7 +10,7 @@ """, 'category': 'sf', 'website': 'https://www.sf.jikimo.com', - 'depends': ['mrp', 'base', 'sf_manufacturing', 'purchase', 'mrp_subcontracting'], + 'depends': ['mrp', 'base', 'sf_manufacturing'], 'data': [ 'data/product_data.xml', 'views/product_template_view.xml' diff --git a/sf_dlm/data/product_data.xml b/sf_dlm/data/product_data.xml index e3ecbfae..aad3714c 100644 --- a/sf_dlm/data/product_data.xml +++ b/sf_dlm/data/product_data.xml @@ -15,7 +15,7 @@ 原材料 - + CNC加工产品模板 @@ -31,7 +31,7 @@ serial - + 胚料自加工模板 @@ -48,7 +48,7 @@ - + 胚料外协加工模板 @@ -63,7 +63,7 @@ serial - + 胚料采购模板 diff --git a/sf_dlm/data/uom_data.xml b/sf_dlm/data/uom_data.xml new file mode 100644 index 00000000..de698554 --- /dev/null +++ b/sf_dlm/data/uom_data.xml @@ -0,0 +1,11 @@ + + + + + + 立方毫米 + + 1000 + bigger + + diff --git a/sf_dlm/models/product_template.py b/sf_dlm/models/product_template.py index 25420671..13321df6 100644 --- a/sf_dlm/models/product_template.py +++ b/sf_dlm/models/product_template.py @@ -1,12 +1,17 @@ from odoo import models, fields, api from odoo.exceptions import ValidationError import logging +import base64 +import os +# from OCC.Extend.DataExchange import read_step_file, write_stl_file +from odoo.modules import get_resource_path class ResProductTemplate(models.Model): _inherit = 'product.template' # 模型的长,宽,高,体积,精度,材料 + model_name = fields.Char('模型名称') model_long = fields.Float('模型长[mm]', digits=(16, 3)) model_width = fields.Float('模型宽[mm]', digits=(16, 3)) model_height = fields.Float('模型高[mm]', digits=(16, 3)) @@ -29,6 +34,17 @@ class ResProductTemplate(models.Model): materials_id = fields.Many2one('sf.production.materials', string='材料') materials_type_id = fields.Many2one('sf.materials.model', string='材料型号') single_manufacturing = fields.Boolean(string="单个制造") + upload_model_file = fields.Many2many('ir.attachment', 'upload_model_file_attachment_ref', string='上传模型文件') + model_code = fields.Char('模型编码') + + 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') + + # model_file = fields.Binary('模型文件') # 胚料的库存路线设置 # def _get_routes(self, route_type): @@ -53,59 +69,82 @@ class ResProductTemplate(models.Model): # 业务平台分配工厂后在智能工厂先创建销售订单再创建该产品 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 + 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' % (order_id.name, i), - 'model_long': item['model_long'], - 'model_width': item['model_width'], - 'model_height': item['model_height'], + '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), 'model_type_id': 1, - 'model_machining_precision': item['model_machining_precision'], - 'model_processing_panel': 'R,U', + # 'model_machining_precision': item['model_machining_precision'], + 'model_processing_panel': 'R', + 'model_machining_precision': '±0.10mm', + '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_price': item['price'], + '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])], # '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_id': self.env['sf.production.process'].search( - # [('process_encode', '=', item['surface_process_code'])]).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, 'model_remark': item['remark'], 'default_code': '%s-%s' % (order_number, i), - 'barcode': item['barcode'], + # 'barcode': item['barcode'], 'active': True, # 'route_ids': self._get_routes('') } copy_product_id.sudo().write(vals) - # product_id.active = False + # product_id.product_tmpl_id.active = False return copy_product_id + def attachment_create(self, name, data): + attachment = self.env['ir.attachment'].create({ + 'datas': base64.b64decode(data), + 'type': 'binary', + 'description': '模型文件', + 'name': name + }) + return attachment + # 创建胚料 - def no_bom_product_create(self, product_id, item, order_id, route_type): + 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 + 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' % ( - order_id.name, materials_id.name, materials_type_id.name, item['model_long'], item['model_width'], - item['model_height']), - 'length': item['model_long'], - 'width': item['model_width'], - 'height': item['model_height'], - 'volume': item['model_long'] * item['model_width'] * item['model_height'], + 'name': '%s-%s %s %s %s * %s * %s' % ( + 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), # 'model_price': item['price'], 'list_price': item['price'], 'materials_id': materials_id.id, @@ -131,22 +170,32 @@ class ResProductTemplate(models.Model): logging.info('no_bom_copy_product_id-seller_ids-vals:%s' % no_bom_copy_product_id.seller_ids) no_bom_copy_product_id.write(vals) logging.info('no_bom_copy_product_id-vals:%s' % vals) - # product_id.active = False + # product_id.product_tmpl_id.active = False return no_bom_copy_product_id - # 根据模型类型默认给模型的长高宽加配置的长度; - @api.onchange('model_type_id') - def add_product_size(self): - if not self.model_type_id: - return - model_type = self.env['sf.model.type'].search( - [('id', '=', self.model_type_id.id), ('embryo_tolerance', '=', True)]) - if model_type: - self.model_long = self.model_long + 1 - self.model_width = self.model_width + 1 - self.model_height = self.model_width + 1 - else: - return + # @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] + # item.model_name = file_attachment_id.name + # # 附件路径 + # report_path = file_attachment_id._full_path(file_attachment_id.store_fname) + # shapes = read_step_file(report_path) + # output_file = get_resource_path('sf_dlm', 'static/file', 'out.stl') + # write_stl_file(shapes, output_file, 'binary', 0.03, 0.5) + # # 转化为glb + # output_glb_file = get_resource_path('sf_dlm', 'static/file', 'out.glb') + # util_path = get_resource_path('sf_dlm', 'static/util') + # cmd = 'python %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) + # item.model_file = base64_data class ResMrpBom(models.Model): @@ -207,8 +256,6 @@ class ResMrpBom(models.Model): # 匹配bom def get_bom(self, product): - logging.info('get_bom-product:%s' % product) - logging.info('get_bom-product:%s' % product.materials_type_id.id) embryo_has = self.env['product.product'].search( [('categ_id.type', '=', '胚料'), ('materials_type_id', '=', product.materials_type_id.id), ('length', '>', product.length), ('width', '>', product.width), @@ -220,7 +267,7 @@ class ResMrpBom(models.Model): 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: + if rate_of_waste <= 20: return embryo_has else: return diff --git a/sf_dlm/static/file/out.glb b/sf_dlm/static/file/out.glb new file mode 100644 index 00000000..c4a6352b Binary files /dev/null and b/sf_dlm/static/file/out.glb differ diff --git a/sf_dlm/static/file/out.stl b/sf_dlm/static/file/out.stl new file mode 100644 index 00000000..4f8b834a Binary files /dev/null and b/sf_dlm/static/file/out.stl differ diff --git a/sf_dlm/static/util/stl2gltf.py b/sf_dlm/static/util/stl2gltf.py new file mode 100644 index 00000000..1bdcc942 --- /dev/null +++ b/sf_dlm/static/util/stl2gltf.py @@ -0,0 +1,277 @@ +import os + +def stl_to_gltf(binary_stl_path, out_path, is_binary): + import struct + + gltf2 = ''' +{ + "scenes" : [ + { + "nodes" : [ 0 ] + } + ], + "nodes" : [ + { + "mesh" : 0 + } + ], + "meshes" : [ + { + "primitives" : [ { + "attributes" : { + "POSITION" : 1 + }, + "indices" : 0 + } ] + } + ], + "buffers" : [ + { + %s + "byteLength" : %d + } + ], + "bufferViews" : [ + { + "buffer" : 0, + "byteOffset" : 0, + "byteLength" : %d, + "target" : 34963 + }, + { + "buffer" : 0, + "byteOffset" : %d, + "byteLength" : %d, + "target" : 34962 + } + ], + "accessors" : [ + { + "bufferView" : 0, + "byteOffset" : 0, + "componentType" : 5125, + "count" : %d, + "type" : "SCALAR", + "max" : [ %d ], + "min" : [ 0 ] + }, + { + "bufferView" : 1, + "byteOffset" : 0, + "componentType" : 5126, + "count" : %d, + "type" : "VEC3", + "min" : [%f, %f, %f], + "max" : [%f, %f, %f] + } + ], + "asset" : { + "version" : "2.0" + } +} +''' + + header_bytes = 80 + unsigned_long_int_bytes = 4 + float_bytes = 4 + vec3_bytes = 4 * 3 + spacer_bytes = 2 + num_vertices_in_face = 3 + + vertices = {} + indices = [] + + if not is_binary: + out_bin = os.path.join(out_path, "out.bin") + out_gltf = os.path.join(out_path, "out.gltf") + else: + out_bin = out_path + + unpack_face = struct.Struct("<12fH").unpack + face_bytes = float_bytes*12 + 2 + + with open(path_to_stl, "rb") as f: + f.seek(header_bytes) # skip 80 bytes headers + + num_faces_bytes = f.read(unsigned_long_int_bytes) + number_faces = struct.unpack(" maxx: maxx = x + if y < miny: miny = y + if y > maxy: maxy = y + if z < minz: minz = z + if z > maxz: maxz = z + + # f.seek(spacer_bytes, 1) # skip the spacer + + number_vertices = len(vertices) + vertices_bytelength = number_vertices * vec3_bytes # each vec3 has 3 floats, each float is 4 bytes + unpadded_indices_bytelength = number_vertices * unsigned_long_int_bytes + + out_number_vertices = len(vertices) + out_number_indices = len(indices) + + unpadded_indices_bytelength = out_number_indices * unsigned_long_int_bytes + indices_bytelength = (unpadded_indices_bytelength + 3) & ~3 + + out_bin_bytelength = vertices_bytelength + indices_bytelength + + if is_binary: + out_bin_uir = "" + else: + out_bin_uir = '"uri": "out.bin",' + + gltf2 = gltf2 % ( out_bin_uir, + #buffer + out_bin_bytelength, + + # bufferViews[0] + indices_bytelength, + + # bufferViews[1] + indices_bytelength, + vertices_bytelength, + + # accessors[0] + out_number_indices, + out_number_vertices - 1, + + # accessors[1] + out_number_vertices, + minx, miny, minz, + maxx, maxy, maxz + ) + + glb_out = bytearray() + if is_binary: + gltf2 = gltf2.replace(" ", "") + gltf2 = gltf2.replace("\n", "") + + scene = bytearray(gltf2.encode()) + + scene_len = len(scene) + padded_scene_len = (scene_len + 3) & ~3 + body_offset = padded_scene_len + 12 + 8 + + file_len = body_offset + out_bin_bytelength + 8 + + # 12-byte header + glb_out.extend(struct.pack(' 3: + is_binary = True + else: + is_binary = False + + if out_path.lower().endswith(".glb"): + print("Use binary mode since output file has glb extension") + is_binary = True + else: + if is_binary: + print("output file should have glb extension but not %s", out_path) + + if not os.path.exists(path_to_stl): + print("stl file does not exists %s" % path_to_stl) + + if not is_binary: + if not os.path.isdir(out_path): + os.mkdir(out_path) + + stl_to_gltf(path_to_stl, out_path, is_binary) \ No newline at end of file diff --git a/sf_dlm/views/product_template_view.xml b/sf_dlm/views/product_template_view.xml index f363f0b2..c563f728 100644 --- a/sf_dlm/views/product_template_view.xml +++ b/sf_dlm/views/product_template_view.xml @@ -6,7 +6,13 @@ product.template + + + + + + @@ -101,8 +107,8 @@ - + diff --git a/sf_manufacturing/__manifest__.py b/sf_manufacturing/__manifest__.py index 2f73f417..a133d6f6 100644 --- a/sf_manufacturing/__manifest__.py +++ b/sf_manufacturing/__manifest__.py @@ -17,8 +17,8 @@ 'report/tray_report.xml', 'views/mrp_maintenance_views.xml', 'views/mrp_routing_workcenter_view.xml', - 'views/mrp_workorder_view.xml', 'views/mrp_workcenter_views.xml', + 'views/mrp_workorder_view.xml', 'views/tray_view.xml', 'views/model_type_view.xml', diff --git a/sf_manufacturing/models/__init__.py b/sf_manufacturing/models/__init__.py index b439701c..419dee46 100644 --- a/sf_manufacturing/models/__init__.py +++ b/sf_manufacturing/models/__init__.py @@ -6,6 +6,9 @@ from . import mrp_routing_workcenter from . import mrp_workorder from . import model_type from . import stock +from . import res_user + + diff --git a/sf_manufacturing/models/mrp_production.py b/sf_manufacturing/models/mrp_production.py index 55cd0ca4..5046e128 100644 --- a/sf_manufacturing/models/mrp_production.py +++ b/sf_manufacturing/models/mrp_production.py @@ -2,6 +2,9 @@ from odoo import api, fields, models,_ +class resProduct(models.Model): + _inherit = 'product.template' + model_file = fields.Binary('模型文件') class MrpProduction(models.Model): _inherit = 'mrp.production' @@ -10,6 +13,7 @@ class MrpProduction(models.Model): tray_ids = fields.One2many('sf.tray', 'production_id', string="托盘") maintenance_count = fields.Integer(compute='_compute_maintenance_count', string="Number of maintenance requests") request_ids = fields.One2many('maintenance.request', 'production_id') + model_file = fields.Binary('模型文件', related='product_id.model_file') @api.depends('request_ids') def _compute_maintenance_count(self): diff --git a/sf_manufacturing/models/mrp_workcenter.py b/sf_manufacturing/models/mrp_workcenter.py index ad7a0419..e853ab3f 100644 --- a/sf_manufacturing/models/mrp_workcenter.py +++ b/sf_manufacturing/models/mrp_workcenter.py @@ -7,6 +7,8 @@ class ResWorkcenter(models.Model): _inherit = "mrp.workcenter" machine_tool_id = fields.Many2one('sf.machine_tool', '机床') + users_ids = fields.Many2many("res.users", 'users_workcenter') + equipment_ids = fields.One2many( 'maintenance.equipment', 'workcenter_id', string="Maintenance Equipment", check_company=True) diff --git a/sf_manufacturing/models/mrp_workorder.py b/sf_manufacturing/models/mrp_workorder.py index 332e3e2f..a5e8cba6 100644 --- a/sf_manufacturing/models/mrp_workorder.py +++ b/sf_manufacturing/models/mrp_workorder.py @@ -4,6 +4,7 @@ import math import requests import logging import base64 +import hashlib # import subprocess from datetime import datetime from dateutil.relativedelta import relativedelta @@ -19,6 +20,7 @@ class ResMrpWorkOrder(models.Model): _order = 'sequence' workcenter_id = fields.Many2one('mrp.workcenter', required=False) + users_ids = fields.Many2many("res.users", 'users_workorder', related="workcenter_id.users_ids") processing_panel = fields.Char('加工面') sequence = fields.Integer(string='工序') routing_type = fields.Selection([ @@ -29,6 +31,26 @@ class ResMrpWorkOrder(models.Model): ('后置三元质量检测', '后置三元质量检测'), ('解除装夹', '解除装夹'), ], string="工序类型") + + @api.onchange('users_ids') + def get_user_permissions(self): + uid = self.env.uid + for workorder in self: + if workorder.users_ids: + list_user_id = [] + for item in workorder.users_ids: + list_user_id.append(item.id) + if uid in list_user_id: + workorder.user_permissions = True + else: + workorder.user_permissions = False + else: + workorder.user_permissions = False + + user_permissions = fields.Boolean('用户权限', compute='get_user_permissions') + programming_no = fields.Char('编程单号') + work_state = fields.Char('业务状态') + programming_state = fields.Char('编程状态') cnc_worksheet = fields.Binary( '工作指令', readonly=True) material_center_point = fields.Char(string='配料中心点') @@ -104,7 +126,11 @@ class ResMrpWorkOrder(models.Model): print("(%.2f,%.2f)" % (x, y)) self.material_center_point = ("(%.2f,%.2f,%.2f)" % (x, y, z)) self.X_deviation_angle = jdz - return self.material_center_point + # 将补偿值写入CNC加工工单 + workorder = self.env['mrp.workorder'].browse(self.ids) + work = workorder.production_id.workorder_ids + work.compensation_value_x = eval(self.material_center_point)[0] + work.compensation_value_y = eval(self.material_center_point)[1] def json_workorder_str(self, k, production, route): workorders_values_str = [0, '', { @@ -114,6 +140,7 @@ class ResMrpWorkOrder(models.Model): 'name': route.route_workcenter_id.name, 'processing_panel': k, 'routing_type': route.routing_type, + 'work_state': '' if not route.routing_type == '获取CNC加工程序' else '待发起', 'workcenter_id': self.env['mrp.routing.workcenter'].get_workcenter(route.workcenter_ids.ids), 'date_planned_start': False, 'date_planned_finished': False, @@ -231,6 +258,9 @@ class ResMrpWorkOrder(models.Model): else: return True + # def fetchCNCing(self): + # return None + # cnc程序获取 def fetchCNC(self): try: @@ -280,12 +310,21 @@ class ResMrpWorkOrder(models.Model): self.write( {'programming_no': ret['programming_no'], 'programming_state': '编程中', 'work_state': '编程中'}) else: - logging.info('fetchCNC-error:%s' % ret['message']) - raise UserError(ret['message']) + logging.info('fetchCNC-error:%s' % cnc) + raise UserError('行业资源库解析失败') except Exception as e: logging.info('fetchCNC error:%s' % e) raise UserError(e) + # return { + # 'name': _("工单"), + # 'view_mode': 'form', + # 'res_model': 'mrp.workorder', + # 'res_id': self.id, + # 'type': 'ir.actions.act_window', + # 'target': 'new' + # } + def json_workorder_str1(self, k, production, route): workorders_values_str = [0, '', { 'product_uom_id': production.product_uom_id.id, @@ -294,6 +333,7 @@ class ResMrpWorkOrder(models.Model): 'name': route.route_workcenter_id.name, 'processing_panel': k, 'routing_type': route.routing_type, + 'work_state': '' if not route.routing_type == '获取CNC加工程序' else '待发起', 'workcenter_id': self.env['mrp.routing.workcenter'].get_workcenter(route.workcenter_ids.ids), 'date_planned_start': False, 'date_planned_finished': False, @@ -367,49 +407,78 @@ class CNCprocessing(models.Model): depth_of_processing_z = fields.Char('加工深度(Z)') cutting_tool_extension_length = fields.Char('刀具伸出长度') cutting_tool_handle_type = fields.Char('刀柄型号') - estimated_processing_time = fields.Datetime('预计加工时间') + estimated_processing_time = fields.Char('预计加工时间') remark = fields.Text('备注') workorder_id = fields.Many2one('mrp.workorder', string="工单") # mrs下发编程单创建CNC加工 - def cnc_processing_create(self, obj): - workorder = self.env['mrp.workorder'].search([('production_id.name', '=', obj['manufacturing_order_no']), - ('processing_panel', '=', obj['processing_panel']), - ('routing_type', '=', 'CNC加工')]) - vals = { - 'workorder_id': workorder.id, - 'sequence_number': obj['sequence_number'], - 'program_name': obj['program_name'], - 'cutting_tool_name': obj['cutting_tool_name'], - 'cutting_tool_no': obj['cutting_tool_no'], - 'processing_type': obj['processing_type'], - 'margin_x_y': obj['margin_x_y'], - 'margin_z': obj['margin_z'], - 'depth_of_processing_z': obj['depth_of_processing_z'], - 'cutting_tool_extension_length': obj['cutting_tool_extension_length'], - 'cutting_tool_handle_type': obj['cutting_tool_handle_type'], - 'estimated_processing_time': obj['estimated_processing_time'], - 'remark': obj['remark'] - } - return self.env['sf.cnc.processing'].create(vals) + def cnc_processing_create(self, cnc_workorder, ret): + for obj in ret['programming_list']: + workorder = self.env['mrp.workorder'].search([('production_id.name', '=', ret['production_order_no']), + ('processing_panel', '=', obj['processing_panel']), + ('routing_type', '=', 'CNC加工')]) + cnc_processing = self.env['sf.cnc.processing'].create({ + 'workorder_id': workorder.id, + 'sequence_number': obj['sequence_number'], + 'program_name': obj['program_name'], + 'cutting_tool_name': obj['cutting_tool_name'], + 'cutting_tool_no': obj['cutting_tool_no'], + 'processing_type': obj['processing_type'], + 'margin_x_y': obj['margin_x_y'], + 'margin_z': obj['margin_z'], + 'depth_of_processing_z': obj['depth_of_processing_z'], + 'cutting_tool_extension_length': obj['cutting_tool_extension_length'], + 'cutting_tool_handle_type': obj['cutting_tool_handle_type'], + 'estimated_processing_time': obj['estimated_processing_time'], + 'remark': obj['remark'] + }) + self.get_cnc_processing_file(ret['folder_name'], cnc_processing) + cnc_workorder.state = 'done' + cnc_workorder.work_state = '已编程' + cnc_workorder.programming_state = '已编程' + cnc_workorder.time_ids.date_end = datetime.now() + + def get_cnc_processing_file(self, folder_name, cnc_processing): + logging.info('folder_name:%s' % folder_name) + serverdir = os.path.join('/', folder_name, 'return', cnc_processing.processing_panel) + logging.info('serverdir:%s' % serverdir) + for root, dirs, files in os.walk(server_dir): + for f in files: + logging.info('f:%s' % f) + if os.path.splitext(f)[1] == ".pdf": + full_path = os.path.join(server_dir, root, f) + logging.info('pdf:%s' % full_path) + if full_path != False: + if not cnc_processing.workorder_id.cnc_worksheet: + cnc_processing.workorder_id.cnc_worksheet = base64.b64encode( + open(full_path, 'rb').read()) + else: + if cnc_processing.program_name == f.split('.')[0]: + cnc_file_path = os.path.join(server_dir, root, f) + logging.info('cnc_file_path:%s' % cnc_file_path) + cnc_processing.with_user(request.env.ref("base.user_admin")).write_file(cnc_file_path, + cnc_processing) # 创建附件(nc文件) def attachment_create(self, name, data): attachment = self.env['ir.attachment'].create({ 'datas': base64.b64encode(data), 'type': 'binary', + 'public': True, 'description': '程序文件', 'name': name }) return attachment # 将FTP的nc文件下载到临时目录 - def download_file_tmp(self, model_code, processing_panel): - remotepath = os.path.join('/', model_code, 'return', processing_panel) - serverdir = os.path.join('/tmp', model_code, 'return', processing_panel) - ftp = FtpController() - ftp.download_file_tree(remotepath, serverdir) - return serverdir + def download_file_tmp(self, production_no, processing_panel): + remotepath = os.path.join('/', production_no, 'return', processing_panel) + serverdir = os.path.join('/tmp', production_no, 'return', processing_panel) + ftp_resconfig = self.env['res.config.settings'].get_values() + ftp = FtpController(str(ftp_resconfig['ftp_host']), int(ftp_resconfig['ftp_port']), ftp_resconfig['ftp_user'], + ftp_resconfig['ftp_password']) + download_state = ftp.download_file_tree(remotepath, serverdir) + return download_state # 将nc文件存到attach的datas里 def write_file(self, nc_file_path, cnc): diff --git a/sf_manufacturing/models/res_user.py b/sf_manufacturing/models/res_user.py new file mode 100644 index 00000000..d79313db --- /dev/null +++ b/sf_manufacturing/models/res_user.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- +from odoo import SUPERUSER_ID, _, api, fields, models, registry + +class Users(models.Model): + _inherit = 'res.users' + + workcenter_ids = fields.Many2many("mrp.workcenter", 'users_workcenter') \ No newline at end of file diff --git a/sf_manufacturing/models/stock.py b/sf_manufacturing/models/stock.py index cf1433f7..7adf4004 100644 --- a/sf_manufacturing/models/stock.py +++ b/sf_manufacturing/models/stock.py @@ -66,9 +66,12 @@ class StockRule(models.Model): list2 = [] for item in procurements: num = int(item[0].product_qty) - product = self.env['product.template'].search( - ["&", ("name", '=', item[0].product_id.display_name), ('single_manufacturing', '!=', False)]) - if product: + + product = self.env['product.product'].search( + [("id", '=', item[0].product_id.id)]) + product_tmpl = self.env['product.template'].search( + ["&", ("id", '=', product.product_tmpl_id.id), ('single_manufacturing', "!=", False)]) + if product_tmpl: if num > 1: for no in range(1, num + 1): Procurement = namedtuple('Procurement', ['product_id', 'product_qty', diff --git a/sf_manufacturing/models/tray.py b/sf_manufacturing/models/tray.py index 08906217..a572f52c 100644 --- a/sf_manufacturing/models/tray.py +++ b/sf_manufacturing/models/tray.py @@ -20,6 +20,41 @@ class Tray(models.Model): def updateTrayState(self): if self.workorder_id != False: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + self.state = '占用' else: self.state = '空闲' diff --git a/sf_manufacturing/views/mrp_workcenter_views.xml b/sf_manufacturing/views/mrp_workcenter_views.xml index 11777e58..186dce3c 100644 --- a/sf_manufacturing/views/mrp_workcenter_views.xml +++ b/sf_manufacturing/views/mrp_workcenter_views.xml @@ -24,14 +24,16 @@ - + mrp.workcenter.view.kanban.inherit.mrp.workorder mrp.workcenter -