467 lines
25 KiB
Python
467 lines
25 KiB
Python
import logging
|
||
import base64
|
||
import hashlib
|
||
import os
|
||
import platform
|
||
import json
|
||
from datetime import datetime
|
||
import requests
|
||
from odoo import http
|
||
from odoo.http import request
|
||
# from OCC.Extend.DataExchange import read_step_file
|
||
# from OCC.Extend.DataExchange import write_stl_file
|
||
from odoo import models, fields, api
|
||
from odoo.modules import get_resource_path
|
||
from odoo.exceptions import ValidationError, UserError
|
||
from odoo.addons.sf_base.commons.common import Common
|
||
from . import parser_and_calculate_work_time as pc
|
||
|
||
|
||
class QuickEasyOrder(models.Model):
|
||
_name = 'quick.easy.order'
|
||
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||
_description = '简易下单'
|
||
_order = 'id desc'
|
||
|
||
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', '材料', tracking=True)
|
||
material_model_id = fields.Many2one('sf.materials.model', '型号', tracking=True)
|
||
# process_id = fields.Many2one('sf.production.process', string='表面工艺')
|
||
parameter_ids = fields.Many2many('sf.production.process.parameter', 'process_item_order_rel', string='可选参数',
|
||
tracking=True)
|
||
quantity = fields.Integer('数量', default=1, tracking=True)
|
||
unit_price = fields.Float('单价', tracking=True)
|
||
price = fields.Float('总价', tracking=True)
|
||
model_file = fields.Binary('glb模型文件')
|
||
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, tracking=True)
|
||
model_color_state = fields.Selection([
|
||
('success', '成功'),
|
||
('fail', '失败')], string='模型上色状态', tracking=True)
|
||
processing_time = fields.Integer('加工时长(min)')
|
||
sale_order_id = fields.Many2one('sale.order', '销售订单号')
|
||
|
||
@api.depends('unit_price', 'quantity')
|
||
def _compute_total_amount(self):
|
||
for item in self:
|
||
item.price = item.unit_price * item.quantity
|
||
|
||
@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.model_analyze(attachment)
|
||
# logging.info('create-model_file:%s' % len(vals['model_file']))
|
||
|
||
obj = super(QuickEasyOrder, self).create(vals)
|
||
# self.model_coloring(obj)
|
||
logging.info('---------开始派单到工厂-------')
|
||
sale_order = self.distribute_to_factory(obj)
|
||
obj.sale_order_id = sale_order.id
|
||
obj.state = '待接单'
|
||
return obj
|
||
|
||
def model_analyze(self, model_attachment):
|
||
"""
|
||
step模型解析,上传模型时转为web可显示的格式
|
||
:return:
|
||
"""
|
||
config = request.env['res.config.settings'].sudo().get_values()
|
||
try:
|
||
# 获取当前操作系统
|
||
os_name = platform.system()
|
||
for item in model_attachment:
|
||
# 将拿到的3D模型数据存入文件
|
||
# 定义文件名和文件的二进制内容
|
||
file_name = item.name # 请将这里替换为你的文件名
|
||
print('file_name', file_name)
|
||
# base64_data = base64.b64encode(item.datas)
|
||
# base64_datas = base64_data.decode('utf-8')
|
||
binary_content = item.datas # 请将这里替换为你的文件的二进制内容
|
||
# binary_content从字符串转为二进制
|
||
binary_content = base64.b64decode(binary_content)
|
||
# 定义新的文件夹路径
|
||
# 根据操作系统不同,文件路径不同
|
||
path_header = '/model_parser' if os_name == 'Linux' else 'D:/model_analysis'
|
||
# new_folder_path = 'D:/11111final' + '/' + item['name'].split(".")[0]
|
||
new_folder_path = path_header + '/' + item.name.rpartition('.')[0]
|
||
print('new_folder_path', new_folder_path)
|
||
# 检查新的文件夹是否存在,如果不存在,则创建
|
||
if not os.path.exists(new_folder_path):
|
||
os.makedirs(new_folder_path)
|
||
# 定义新的文件路径
|
||
new_file_path = os.path.join(new_folder_path, file_name)
|
||
# 将二进制内容写入新的文件
|
||
with open(new_file_path, 'wb') as f:
|
||
f.write(binary_content)
|
||
# 检查文件是否已经成功写入
|
||
if os.path.exists(new_file_path):
|
||
print(f'Successfully wrote binary content to {new_file_path}')
|
||
else:
|
||
print(f'Failed to write binary content to {new_file_path}')
|
||
# 附件
|
||
# attachment = request.env['ir.attachment'].sudo().create({
|
||
# 'datas': item['data'].encode('utf-8'),
|
||
# 'type': 'binary',
|
||
# 'description': '模型文件',
|
||
# 'name': item['name'],
|
||
# 'public': True,
|
||
# 'model_name': item['name'],
|
||
# })
|
||
headers = {'Content-Type': 'application/json'}
|
||
# 调用写入宿主机接口
|
||
# url_dir = 'http://192.168.50.202:8000/create_and_write_file'
|
||
url_dir = config['model_parser_url'] + '/create_and_write_file'
|
||
data = {
|
||
'folder_path': new_folder_path, # 您想要创建的文件夹路径
|
||
'file_path': new_file_path, # 您想要创建的文件名
|
||
'content': item['data'] # 您想要写入文件的内容
|
||
}
|
||
requests.post(url_dir, json=data, headers=headers)
|
||
# 调用特征包接口
|
||
url = config['model_parser_url'] + '/process_file'
|
||
payload = {
|
||
'file_path': new_file_path,
|
||
'dest_path': new_folder_path,
|
||
'back_url': config['bfm_url_new']
|
||
}
|
||
response = requests.post(url, json=payload, headers=headers)
|
||
if response.status_code == 200:
|
||
print("Request was successful.")
|
||
print("Response: ", response.json())
|
||
else:
|
||
print("Request failed.")
|
||
# 特征识别
|
||
xml_path = new_folder_path + '/' + item.name.rpartition('.')[0] + '_FeatrueTable.XML'
|
||
print('xml_path', xml_path)
|
||
parser_obj = pc.FeatureParser(xml_path)
|
||
print('parser_obj', parser_obj)
|
||
slot = parser_obj.slots
|
||
print('slot', slot)
|
||
hole = parser_obj.holes
|
||
print('hole', hole)
|
||
size = parser_obj.size
|
||
print('size', size)
|
||
open_slot = parser_obj.open_slots
|
||
print('open_slot', open_slot)
|
||
vector = parser_obj.vectors
|
||
print('vector', vector)
|
||
print('all parcer', size)
|
||
try:
|
||
hole_time = pc.hole_time(parser_obj)
|
||
print('hole_time', hole_time)
|
||
except Exception as e:
|
||
return json.dumps({'code': 400, 'msg': '孔尺寸超限', 'error_msg': str(e)})
|
||
try:
|
||
slot_time = pc.slot_time(parser_obj)
|
||
print('slot_time', slot_time)
|
||
except Exception as e:
|
||
return json.dumps({'code': 400, 'msg': '槽尺寸超限', 'error_msg': str(e)})
|
||
try:
|
||
open_slot_time = pc.open_slot_time(parser_obj)
|
||
print('open_slot_time', open_slot_time)
|
||
except Exception as e:
|
||
return json.dumps({'code': 400, 'msg': '开口槽尺寸超限', 'error_msg': str(e)})
|
||
total_time = hole_time + slot_time + open_slot_time
|
||
print(hole_time, slot_time, open_slot_time)
|
||
print('total_time', total_time)
|
||
ret = {'feature_infos': [{'name': 'all_feature', 'type': '铣', 'process_time': total_time}],
|
||
'boxshape': size, 'slugX': 10.0, 'slugY': 90.0, 'slugZ': 42.0,
|
||
'turn_over_times': 2,
|
||
'target_faces': ['A', 'B']}
|
||
self.model_feature = json.dumps(ret['feature_infos'], ensure_ascii=False)
|
||
self.model_length = size['length'] # 长 单位mm
|
||
self.model_width = size['width'] # 宽
|
||
self.model_height = size['height'] # 高
|
||
self.model_volume = size['length'] * size['width'] * size['height']
|
||
# 附件处理
|
||
base64_data = base64.b64encode(item.datas)
|
||
base64_datas = base64_data.decode('utf-8')
|
||
model_code = hashlib.sha1(base64_datas.encode('utf-8')).hexdigest()
|
||
# 读取文件
|
||
shapes = read_step_file(new_file_path)
|
||
output_file = os.path.join(new_folder_path, str(model_code) + '.stl')
|
||
write_stl_file(shapes, output_file, 'binary', 0.03, 0.5)
|
||
# 转化为glb
|
||
output_glb_file = os.path.join(new_folder_path, str(model_code) + '.glb')
|
||
util_path = get_resource_path('jikimo_gateway_api', 'static/util')
|
||
# 根据操作系统确定使用 'python' 还是 'python3'
|
||
python_cmd = 'python3' if os_name == 'Linux' else 'python'
|
||
print('python_cmd', python_cmd)
|
||
print('os_name', os_name)
|
||
# 使用引号包围路径
|
||
cmd = '%s "%s/stl2gltf.py" "%s" "%s" -b' % (python_cmd, util_path, output_file, output_glb_file)
|
||
logging.info(cmd)
|
||
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
|
||
except Exception as e:
|
||
return UserError('模型自动报价失败,请联系管理员')
|
||
|
||
# 将attach的datas内容转为glb文件
|
||
def transition_glb_file(self, report_path, model_code):
|
||
shapes = read_step_file(report_path)
|
||
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('/tmp', str(model_code) + '.glb')
|
||
util_path = get_resource_path('sf_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]
|
||
item.model_file = self.model_analyze(file_attachment_id)
|
||
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:
|
||
"""
|
||
try:
|
||
logging.info('---------派单到工厂-------')
|
||
res = {'bfm_process_order_list': []}
|
||
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))
|
||
res['bfm_process_order_list'].append({
|
||
'model_long': item.model_length,
|
||
'model_width': item.model_width,
|
||
'model_height': item.model_height,
|
||
'model_volume': item.model_volume,
|
||
'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[
|
||
'sf.production.process.parameter']._json_production_process_item_code(
|
||
item),
|
||
'price': item.price,
|
||
'number': item.quantity,
|
||
'total_amount': item.price,
|
||
'remark': '',
|
||
'barcode': barcode
|
||
})
|
||
# res['bfm_process_order_list'] = json.dumps(res['bfm_process_order_list'])
|
||
product_id = self.env.ref('sf_dlm.product_template_sf').sudo()
|
||
self_machining_id = self.env.ref('sf_dlm.product_embryo_sf_self_machining').sudo()
|
||
outsource_id = self.env.ref('sf_dlm.product_embryo_sf_outsource').sudo()
|
||
purchase_id = self.env.ref('sf_dlm.product_embryo_sf_purchase').sudo()
|
||
company_id = self.env.ref('base.main_company').sudo()
|
||
# user_id = request.env.ref('base.user_admin').sudo()
|
||
order_id = self.env['sale.order'].sale_order_create(company_id, 'XXXXX', 'XXXXX', 'XXXXX',
|
||
str(datetime.now()), '现结', '支付宝')
|
||
i = 1
|
||
# 给sale_order的default_code字段赋值
|
||
aa = self.env['sale.order'].sudo().search([('name', '=', order_id.name)])
|
||
logging.info('---------aa------- %s' % aa.name)
|
||
aa.default_code = obj.name
|
||
for item in res['bfm_process_order_list']:
|
||
product = self.env['product.template'].sudo().product_create(product_id, item, order_id,
|
||
obj.name, i)
|
||
bom_data = self.env['mrp.bom'].get_bom(product)
|
||
logging.info('bom_data:%s' % bom_data)
|
||
if bom_data:
|
||
bom = self.env['mrp.bom'].bom_create(product, 'normal', False)
|
||
bom.bom_create_line_has(bom_data)
|
||
else:
|
||
if product.materials_type_id.gain_way == '自加工':
|
||
# 创建坯料
|
||
self_machining_embryo = self.env['product.template'].sudo().no_bom_product_create(
|
||
self_machining_id,
|
||
item, order_id,
|
||
'self_machining',
|
||
i)
|
||
# 创建坯料的bom
|
||
self_machining_bom = self.env['mrp.bom'].bom_create(self_machining_embryo, 'normal', False)
|
||
# 创建坯料里bom的组件
|
||
self_machining_bom_line = self_machining_bom.bom_create_line(self_machining_embryo)
|
||
if self_machining_bom_line is False:
|
||
self.cr.rollback()
|
||
return UserError('该订单模型的材料型号在您分配的工厂里暂未有原材料,请先配置再进行分配')
|
||
# 产品配置bom
|
||
product_bom_self_machining = self.env['mrp.bom'].bom_create(product, 'normal', False)
|
||
product_bom_self_machining.bom_create_line_has(self_machining_embryo)
|
||
elif product.materials_type_id.gain_way == '外协':
|
||
# 创建坯料
|
||
outsource_embryo = self.env['product.template'].sudo().no_bom_product_create(outsource_id, item,
|
||
order_id,
|
||
'subcontract', i)
|
||
# 创建坯料的bom
|
||
outsource_bom = self.env['mrp.bom'].bom_create(outsource_embryo, 'subcontract', True)
|
||
# 创建坯料的bom的组件
|
||
outsource_bom_line = outsource_bom.with_user(
|
||
self.env.ref("base.user_admin")).bom_create_line(outsource_embryo)
|
||
if outsource_bom_line is False:
|
||
self.cr.rollback()
|
||
return UserError('该订单模型的材料型号在您分配的工厂里暂未有原材料,请先配置再进行分配')
|
||
# 产品配置bom
|
||
product_bom_outsource = self.env['mrp.bom'].bom_create(product, 'normal', False)
|
||
product_bom_outsource.bom_create_line_has(outsource_embryo)
|
||
elif product.materials_type_id.gain_way == '采购':
|
||
purchase_embryo = self.env['product.template'].sudo().no_bom_product_create(purchase_id, item,
|
||
order_id,
|
||
'purchase', i)
|
||
# 产品配置bom
|
||
product_bom_purchase = self.env['mrp.bom'].bom_create(product, 'normal', False)
|
||
product_bom_purchase.bom_create_line_has(purchase_embryo)
|
||
order_id.with_user(self.env.ref("base.user_admin")).sale_order_create_line(product, item)
|
||
except Exception as e:
|
||
logging.error('工厂创建销售订单和产品失败,请联系管理员'.format(e))
|
||
# self.cr.rollback()
|
||
return UserError('工厂创建销售订单和产品失败,请联系管理员')
|
||
|
||
# 特征识别
|
||
def feature_recognition(self, report_path, model_code):
|
||
feature_path = self.env['sf.auto_quatotion.common'].sudo().get_feature_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, order):
|
||
url = '/api/library_of_models/create'
|
||
config = self.env['res.config.settings'].get_values()
|
||
config_header = Common.get_headers(self, config['token'], config['sf_secret_key'])
|
||
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['token'], config['sf_secret_key'])
|
||
logging.info("报价接口..........% s" % order.name)
|
||
try:
|
||
if order:
|
||
vals = {}
|
||
# mrs合作伙伴token
|
||
vals['token'] = config['token']
|
||
vals['accuracy'] = order.machining_precision
|
||
vals['number'] = order.quantity
|
||
vals['process_code'] = 0
|
||
vals['texture_code'] = order.material_model_id.materials_no
|
||
vals['delivery_days'] = 15
|
||
if order.model_file:
|
||
for item in order.upload_model_file:
|
||
if item.ids[0]:
|
||
logging.info('create-attachment:%s' % int(item.ids[0]))
|
||
attachment = self.env['ir.attachment'].sudo().search([('id', '=', int(item.ids[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("自动报价失败,请联系管理员")
|