diff --git a/sf_base/static/src/scss/format_img.scss b/sf_base/static/src/scss/format_img.scss index 279c5fff..982d3c50 100644 --- a/sf_base/static/src/scss/format_img.scss +++ b/sf_base/static/src/scss/format_img.scss @@ -1,7 +1,7 @@ .o_data_row .w-100 { width: 40px !important; height: 40px !important; - display: block !important; + //display: block !important; } .o_list_renderer .o_list_table tbody > tr > td:not(.o_list_record_selector):not(.o_handle_cell):not(.o_list_button):not(.o_list_record_remove) { diff --git a/sf_dlm/models/product_template.py b/sf_dlm/models/product_template.py new file mode 100644 index 00000000..1e37be2b --- /dev/null +++ b/sf_dlm/models/product_template.py @@ -0,0 +1,292 @@ +from odoo import models, fields +import logging +import base64 + + +class ResProductTemplate(models.Model): + _inherit = 'product.template' + + # 模型的长,宽,高,体积,精度,材料 + model_name = fields.Char('模型名称') + categ_type = fields.Selection( + [("成品", "成品"), ("胚料", "胚料"), ("原材料", "原材料")], string='产品的类别', related='categ_id.type', store=True) + model_long = 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('模型体积[m³]') + model_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='加工精度') + product_model_type_id = fields.Many2one('sf.model.type', string='产品模型类型') + embryo_model_type_id = fields.Many2one('sf.model.type', string='胚料模型类型') + model_processing_panel = fields.Char('模型加工面板') + model_surface_process_id = fields.Many2one('sf.production.process', string='表面工艺') + model_process_parameters_id = fields.Many2one('sf.processing.technology', string='工艺参数') + # model_price = fields.Float('模型单价', digits=(16, 3)) + model_remark = fields.Char('模型备注说明') + length = fields.Float('长[mm]', digits=(16, 3)) + width = fields.Float('宽[mm]', digits=(16, 3)) + height = fields.Float('高[mm]', digits=(16, 3)) + 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('模型编码') + is_bfm = fields.Boolean('业务平台是否自动创建', default=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') + + # model_file = fields.Binary('模型文件') + + # 胚料的库存路线设置 + # def _get_routes(self, route_type): + # route_manufacture = self.env.ref('mrp.route_warehouse0_manufacture', raise_if_not_found=False).sudo() + # route_mto = self.env.ref('stock.route_warehouse0_mto', raise_if_not_found=False).sudo() + # route_purchase = self.env.ref('purchase_stock.route_warehouse0_buy', raise_if_not_found=False).sudo() + # if route_manufacture and route_mto: + # # 外协 + # if route_type == 'subcontract': + # route_subcontract = self.env.ref('mrp_subcontracting.route_resupply_subcontractor_mto', + # raise_if_not_found=False).sudo() + # return [route_mto.id, route_purchase.id, route_subcontract.id] + # elif route_type == 'purchase': + # # 采购 + # return [route_mto.id, route_purchase.id] + # else: + # return [route_mto.id, route_manufacture.id] + # return [] + + # route_ids = fields.Many2many(default=lambda self: self._get_route()) + + # 业务平台分配工厂后在智能工厂先创建销售订单再创建该产品 + 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])], + # '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_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'], + '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 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.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): + _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 + + +class ResProductCategory(models.Model): + _inherit = "product.category" + + type = fields.Selection( + [("成品", "成品"), ("胚料", "胚料"), ("原材料", "原材料")], + default="", string="类型") + + # @api.constrains('type') + # def _check_type(self): + # category = self.env['product.category'].search( + # [('type', '=', self.type)]) + # if category: + # raise ValidationError("该类别已存在,请选择其他类别") diff --git a/sf_dlm/models/product_workorder.py b/sf_dlm/models/product_workorder.py new file mode 100644 index 00000000..eafa3175 --- /dev/null +++ b/sf_dlm/models/product_workorder.py @@ -0,0 +1,13 @@ +from odoo import api, fields, models + + + +class ResMrpWorkOrder(models.Model): + _inherit = 'mrp.workorder' + _order = 'sequence' + + product_tmpl_id_length = fields.Float(related='production_id.product_tmpl_id.length', readonly=True, store=True, check_company=True, string="胚料长度(mm)") + product_tmpl_id_width = fields.Float(related='production_id.product_tmpl_id.width', readonly=True, store=True, check_company=True, string="胚料宽度(mm)") + product_tmpl_id_height = fields.Float(related='production_id.product_tmpl_id.height', readonly=True, store=True, check_company=True, string="胚料高度(mm)") + product_tmpl_id_materials_id = fields.Many2one(related='production_id.product_tmpl_id.materials_id', readonly=True, store=True, check_company=True, string="材料") + product_tmpl_id_materials_type_id = fields.Many2one(related='production_id.product_tmpl_id.materials_type_id', readonly=True, store=True, check_company=True, string="型号") 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_workorder.xml b/sf_dlm/views/product_workorder.xml new file mode 100644 index 00000000..4c5f2a10 --- /dev/null +++ b/sf_dlm/views/product_workorder.xml @@ -0,0 +1,27 @@ + + + + production.workorder.dlm + mrp.workorder + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/sf_machine_connect/static/src/js/test.js b/sf_machine_connect/static/src/js/test.js new file mode 100644 index 00000000..1f1c1d58 --- /dev/null +++ b/sf_machine_connect/static/src/js/test.js @@ -0,0 +1,82 @@ +/** @odoo-module **/ + +import { browser } from "@web/core/browser/browser"; +import { Dialog } from "@web/core/dialog/dialog"; +import { _lt } from "@web/core/l10n/translation"; +import { useChildRef, useOwnedDialogs, useService } from "@web/core/utils/hooks"; +import { sprintf } from "@web/core/utils/strings"; +import { isMobileOS } from "@web/core/browser/feature_detection"; +import * as BarcodeScanner from "@web/webclient/barcode/barcode_scanner"; + +const {xml, Component} = owl; +import { standardFieldProps } from "@web/views/fields/standard_field_props"; +// Import the registry +import {registry} from "@web/core/registry"; + + +export class CodeField extends Component { + setup() { + super.setup(); + } + async onBarcodeBtnClick() { + const barcode = await BarcodeScanner.scanBarcode(); + if (barcode) { + await this.onBarcodeScanned(barcode); + if ("vibrate" in browser.navigator) { + browser.navigator.vibrate(100); + } + } else { + this.notification.add(this.env._t("Please, scan again !"), { + type: "warning", + }); + } + } + async search(barcode) { + const results = await this.orm.call("sf.tray", "name_search", [code], { + name: barcode, + args: this.getDomain(), + operator: "ilike", + limit: 2, // If one result we set directly and if more than one we use normal flow so no need to search more + context: this.context, + }); + return results.map((result) => { + const [id, displayName] = result; + return { + id, + name: displayName, + }; + }); + } + async onBarcodeScanned(barcode) { + const results = await this.search(barcode); + const records = results.filter((r) => !!r.id); + if (records.length === 1) { + this.update([{ id: records[0].id, name: records[0].name }]); + } else { + const searchInput = this.autocompleteContainerRef.el.querySelector("input"); + searchInput.value = barcode; + searchInput.dispatchEvent(new Event("input")); + if (this.env.isSmall) { + searchInput.click(); + } + } + } +} + +CodeField.template = xml` + + + + + + + + + + + + + + maintenance.request.view.form.inherit.mrp + maintenance.request + + + + + + + + + + +
+
+
+
+
+ + ['|', (not workorder_id and 1 or 0, '=', 1), '|', ('workcenter_id', '=', + False), ('workcenter_id.order_ids', 'in', workorder_id)] + + +
+
+ + + maintenence.request.view.search.inherit.mrp + maintenance.request + + + + + + + + + + + + + diff --git a/sf_manufacturing/views/mrp_workorder_view.xml b/sf_manufacturing/views/mrp_workorder_view.xml index d9cbf52c..cf4e6b8b 100644 --- a/sf_manufacturing/views/mrp_workorder_view.xml +++ b/sf_manufacturing/views/mrp_workorder_view.xml @@ -623,7 +623,6 @@ - @@ -673,14 +672,18 @@ 空料架配送 sf.workpiece.delivery - +
- - - - + + + + +