Accept Merge Request #159: (feature/表面工艺外协 -> develop)

Merge Request: 表面工艺外协

Created By: @杨金灵
Accepted By: @杨金灵
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/159?initial=true
This commit is contained in:
杨金灵
2023-04-21 15:46:04 +08:00
35 changed files with 808 additions and 224 deletions

View File

@@ -23,6 +23,7 @@
],
'qweb': [
],
'license': 'LGPL-3',
'installable': True,
'application': False,
'auto_install': False,

View File

@@ -75,6 +75,18 @@ class MrsMaterialModel(models.Model):
active = fields.Boolean('有效', default=True)
class MrsProductionProcessCategory(models.Model):
_name = 'sf.production.process.category'
_description = '表面工艺类别'
order = 'id desc'
name = fields.Char('名称')
code = fields.Char("编码")
sequence = fields.Integer('排序')
production_process_ids = fields.One2many('sf.production.process', 'category_id', string="表面工艺")
active = fields.Boolean('有效', default=True)
# 工艺 编码,名称,备注
class MrsProductionProcess(models.Model):
_name = 'sf.production.process'
@@ -87,6 +99,8 @@ class MrsProductionProcess(models.Model):
partner_process_ids = fields.Many2many('res.partner', 'process_ids', '加工工厂')
active = fields.Boolean('有效', default=True)
parameter_ids = fields.One2many('sf.production.process.parameter', 'process_id', string='可选参数')
category_id = fields.Many2one('sf.production.process.category')
# workcenter_ids = fields.Many2many('mrp.workcenter', 'rel_workcenter_process', required=True)
class MrsProcessingTechnology(models.Model):
@@ -134,12 +148,30 @@ class SupplierSort(models.Model):
('supplier_sort_uniq', 'unique (partner_id,materials_model_id)', '排序不能重复!')
]
class MrsProductionProcessParameter(models.Model):
_name = 'sf.production.process.parameter'
_description = '可选参数'
name = fields.Char('参数名')
active = fields.Boolean('有效', default=True)
price = fields.Float('单价')
# _display_name = 'name'
code = fields.Char("编码")
name = fields.Char('名称')
gain_way = fields.Selection([("自加工", "自加工"), ("外协", "外协")], default="", string="获取方式")
is_check = fields.Boolean(default=False)
# price = fields.Float('单价')
process_id = fields.Many2one('sf.production.process', string='表面工艺')
materials_model_ids = fields.Many2many('sf.materials.model', 'applicable_material', string='适用材料')
code = fields.Char("编码")
active = fields.Boolean('有效', default=True)
def name_get(self):
result = []
for parameter in self:
if parameter.process_id:
name = parameter.process_id.name + '-' + parameter.name
result.append((parameter.id, name))
return result
# 获取表面工艺的获取方式
def get_gain_way(self, item):
process_parameter = self.env['sf.production.process.parameter'].search([('id', '=', item.id)])
return process_parameter

View File

@@ -13,8 +13,8 @@ access_sf_materials_model,sf_materials_model,model_sf_materials_model,base.group
access_sf_processing_technology,sf_processing_technology,model_sf_processing_technology,base.group_user,1,1,1,1
access_sf_tray,sf_tray,model_sf_tray,base.group_user,1,1,1,1
access_sf_supplier_sort,sf_supplier_sort,model_sf_supplier_sort,base.group_user,1,1,1,1
access_sf_production_process_parameter,sf_production_process_parameter,model_sf_production_process_parameter,base.group_user,1,1,1,1
access_sf_production_process_category,sf_production_process_category,model_sf_production_process_category,base.group_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
13 access_sf_processing_technology sf_processing_technology model_sf_processing_technology base.group_user 1 1 1 1
14 access_sf_tray sf_tray model_sf_tray base.group_user 1 1 1 1
15 access_sf_supplier_sort sf_supplier_sort model_sf_supplier_sort base.group_user 1 1 1 1
access_sf_production_process_parameter sf_production_process_parameter model_sf_production_process_parameter base.group_user 1 1 1 1
16 access_sf_production_process_parameter sf_production_process_parameter model_sf_production_process_parameter base.group_user 1 1 1 1
17 access_sf_production_process_category sf_production_process_category model_sf_production_process_category base.group_user 1 1 1 1
18
19
20

View File

@@ -39,6 +39,61 @@
</search>
</field>
</record>
<!--表面工艺类别-->
<record model="ir.ui.view" id="sf_production_process_category_form">
<field name="model">sf.production.process.category</field>
<field name="arch" type="xml">
<form string="表面工艺类别">
<sheet>
<group>
<group>
<field name="code" readonly="1"/>
<field name="sequence"/>
</group>
<group>
<field name="name" required="1"/>
</group>
</group>
<notebook>
<page string="表面工艺">
<field name='production_process_ids' widget="ony2many">
<tree editable="bottom">
<field name="process_encode" string="编码号" readonly="1" force_save="1"/>
<field name="name" string="名称" required="1"/>
<field name='category_id' default="default" invisible="1"/>
<field name="remark"/>
</tree>
</field>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<record model="ir.ui.view" id="sf_production_process_category_tree">
<field name="model">sf.production.process.category</field>
<field name="arch" type="xml">
<tree string="表面工艺类别" default_order="sequence, id" >
<field name="sequence" widget="handle" string="序号"/>
<field name="code" readonly="1"/>
<field name="name"/>
</tree>
</field>
</record>
<record model="ir.ui.view" id="search_sf_production_process_category_view">
<field name="name">search.sf.production.process.category</field>
<field name="model">sf.production.process.category</field>
<field name="arch" type="xml">
<search>
<field name="name" string="名称" filter_domain="[('name','ilike',self)]"/>
<field name="code" string="编码" filter_domain="[('code','ilike',self)]"/>
</search>
</field>
</record>
<!--表面工艺-->
<record model="ir.ui.view" id="sf_production_process_tree">
<field name="model">sf.production.process</field>
@@ -55,55 +110,65 @@
<field name="model">sf.production.process</field>
<field name="arch" type="xml">
<form string="表面工艺">
<group>
<field name="process_encode" readonly="1"/>
<field name="name" required="1"/>
</group>
<notebook>
<page string="工序">
<field name='processing_order_ids' options="{'no_create':True}" widget="one2many">
<tree editable='bottom'>
<field name="sequence" widget="handle"/>
<field name="processing_technology_ids" widget="many2many_tags">
</field>
</tree>
<form>
<field name="processing_technology_ids" widget="many2many">
</field>
</form>
</field>
</page>
<page string="可选参数">
<field name="parameter_ids">
<tree force_save="1">
<field name="code" readonly="1" force_save="1"/>
<field name="name"/>
<field name="price"/>
<field name='process_id' default="default" invisible="1"/>
</tree>
<form>
<sheet>
<group>
<sheet>
<group>
<group>
<field name="process_encode" readonly="1"/>
</group>
<group>
<field name="name" required="1"/>
</group>
<notebook>
<page string="可选参数">
<field name="parameter_ids">
<tree force_save="1">
<field name="code" readonly="1" force_save="1"/>
<field name="name"/>
<field name="price"/>
</group>
<notebook>
<page string="适用材料">
<field name="materials_model_ids"/>
</page>
</notebook>
</sheet>
</form>
</field>
</page>
</notebook>
<group>
<field name="remark"/>
</group>
<field name="gain_way"/>
<field name='process_id' default="default"/>
</tree>
<form>
<sheet>
<group>
<group>
<field name="code" readonly="1"/>
<field name="name" string="参数名"/>
</group>
<group>
<field name='process_id'/>
<field name="gain_way"/>
</group>
</group>
<notebook>
<page string="适用材料">
<field name="materials_model_ids"/>
</page>
</notebook>
</sheet>
</form>
</field>
</page>
<page string="工序">
<field name='processing_order_ids' options="{'no_create':True}" widget="one2many">
<tree editable='bottom'>
<field name="sequence" widget="handle"/>
<field name="processing_technology_ids" widget="many2many_tags">
</field>
</tree>
<form>
<field name="processing_technology_ids" widget="many2many">
</field>
</form>
</field>
</page>
</notebook>
</group>
<group>
<group>
<field name="remark"/>
</group>
</group>
</sheet>
</form>
</field>
</record>
@@ -229,26 +294,6 @@
</field>
</record>
<!-- <record model="ir.ui.view" id="sf_production_materials_form">-->
<!-- <field name="model">sf.production.materials</field>-->
<!-- <field name="arch" type="xml">-->
<!-- <form string="材料">-->
<!-- <group string="详情">-->
<!-- <group>-->
<!-- <field name="materials_no" required="1" default="编码"/>-->
<!-- </group>-->
<!-- <group>-->
<!-- <field name="name" required="1"/>-->
<!-- </group>-->
<!-- <field name="materials_model_ids">-->
<!-- </field>-->
<!-- </group>-->
<!-- </form>-->
<!-- </field>-->
<!-- </record>-->
<record id="sf_production_materials" model="ir.actions.act_window">
<field name="name">材料</field>
<field name="type">ir.actions.act_window</field>
@@ -288,13 +333,24 @@
<field name="res_model">sf.processing.technology</field>
<field name="view_mode">tree,form</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
加工工艺!
</p>
</field>
</record>
<record id="sf_production_process_category" model="ir.actions.act_window">
<field name="name">表面工艺类别</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">sf.production.process.category</field>
<field name="view_mode">tree,form</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
表面工艺类别!
</p>
</field>
</record>
#------------------托盘------------------
<record id="action_sf_tray" model="ir.actions.act_window">
<field name="name">托盘</field>

View File

@@ -63,6 +63,13 @@
action="sf_production_process"
/>
<menuitem
id="menu_sf_production_process_category"
name="表面工艺类别"
parent="menu_sf_production_process_1"
sequence="2"
action="sf_production_process_category"
/>
<menuitem
id="menu_sf_production_materials"

View File

@@ -13,13 +13,14 @@
'depends': ['sf_base', 'sf_sale', 'sf_dlm'],
'data': [
'views/res_partner_view.xml',
'views/view.xml',
# 'views/view.xml',
'report/bill_report.xml',
],
'demo': [
],
'qweb': [
],
'license': 'LGPL-3',
'installable': True,
'application': False,
'auto_install': False,

View File

@@ -11,7 +11,7 @@ class Sf_Bf_Connect(http.Controller):
cors="*")
def get_bfm_process_order_list(self, **kw):
"""
接收业务平台加工订单分配工厂时传送来的订单数据并生成销售订单和产品及
接收业务平台加工订单分配工厂时传送来的订单数据并生成销售订单和产品及
:param kw:
:return:
"""
@@ -52,16 +52,16 @@ class Sf_Bf_Connect(http.Controller):
bom.with_user(request.env.ref("base.user_admin")).bom_create_line_has(bom_data)
else:
if product.materials_type_id.gain_way == '自加工':
# 创建
# 创建
self_machining_embryo = request.env['product.template'].sudo().no_bom_product_create(
self_machining_id,
item,
order_id, 'self_machining', i)
# 创建料的bom
# 创建料的bom
self_machining_bom = request.env['mrp.bom'].with_user(
request.env.ref("base.user_admin")).bom_create(
self_machining_embryo, 'normal', False)
# 创建料里bom的组件
# 创建料里bom的组件
self_machining_bom_line = self_machining_bom.with_user(
request.env.ref("base.user_admin")).bom_create_line(
self_machining_embryo)
@@ -77,17 +77,17 @@ class Sf_Bf_Connect(http.Controller):
product_bom_self_machining.with_user(request.env.ref("base.user_admin")).bom_create_line_has(
self_machining_embryo)
elif product.materials_type_id.gain_way == '外协':
# 创建
# 创建
outsource_embryo = request.env['product.template'].sudo().no_bom_product_create(outsource_id,
item,
order_id,
'subcontract',
i)
# 创建料的bom
# 创建料的bom
outsource_bom = request.env['mrp.bom'].with_user(request.env.ref("base.user_admin")).bom_create(
outsource_embryo,
'subcontract', True)
# 创建料的bom的组件
# 创建料的bom的组件
outsource_bom_line = outsource_bom.with_user(
request.env.ref("base.user_admin")).bom_create_line(outsource_embryo)
if outsource_bom_line == False:
@@ -111,7 +111,6 @@ class Sf_Bf_Connect(http.Controller):
product_bom_purchase.with_user(request.env.ref("base.user_admin")).bom_create_line_has(
purchase_embryo)
order_id.with_user(request.env.ref("base.user_admin")).sale_order_create_line(product, item)
order_id.step_file = product.model_file
i += 1
res['factory_order_no'] = order_id.name
return json.JSONEncoder().encode(res)

View File

@@ -1,4 +1,4 @@
import cpca
#import cpca
import logging
import base64
import requests
@@ -37,7 +37,7 @@ class JdEclp(models.Model):
deliveryType = fields.Selection([('6', '特快零担'), ('25', '特快重货')], string='运输类型', default='25')
# bill = fields.Char(string='物流面单')
bill = fields.Many2one('ir.attachment', string='物流面单', compute='query_bill_pdf')
# bill = fields.Many2one('ir.attachment', string='物流面单', compute='query_bill_pdf')
# bill_show = fields.Binary(string='物流面单展示', readonly=True, related='self.bill.datas')
bill_show = fields.Binary(string='物流面单展示', readonly=True)

View File

@@ -1,5 +1,6 @@
from odoo import api, fields, models, SUPERUSER_ID, _
from odoo.exceptions import ValidationError
from collections import defaultdict, namedtuple
from datetime import datetime
import logging
from odoo.exceptions import UserError
@@ -34,7 +35,6 @@ class StatusChange(models.Model):
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')
@@ -148,17 +148,20 @@ class FinishStatusChange(models.Model):
if self.user_has_groups('stock.group_reception_report') \
and self.picking_type_id.auto_show_reception_report:
lines = self.move_ids.filtered(lambda m: m.product_id.type == 'product' and m.state != 'cancel' and m.quantity_done and not m.move_dest_ids)
lines = self.move_ids.filtered(lambda
m: m.product_id.type == 'product' and m.state != 'cancel' and m.quantity_done and not m.move_dest_ids)
if lines:
# don't show reception report if all already assigned/nothing to assign
wh_location_ids = self.env['stock.location']._search([('id', 'child_of', self.picking_type_id.warehouse_id.view_location_id.id), ('usage', '!=', 'supplier')])
wh_location_ids = self.env['stock.location']._search(
[('id', 'child_of', self.picking_type_id.warehouse_id.view_location_id.id),
('usage', '!=', 'supplier')])
if self.env['stock.move'].search([
('state', 'in', ['confirmed', 'partially_available', 'waiting', 'assigned']),
('product_qty', '>', 0),
('location_id', 'in', wh_location_ids),
('move_orig_ids', '=', False),
('picking_id', 'not in', self.ids),
('product_id', 'in', lines.product_id.ids)], limit=1):
('state', 'in', ['confirmed', 'partially_available', 'waiting', 'assigned']),
('product_qty', '>', 0),
('location_id', 'in', wh_location_ids),
('move_orig_ids', '=', False),
('picking_id', 'not in', self.ids),
('product_id', 'in', lines.product_id.ids)], limit=1):
action = self.action_view_reception_report()
action['context'] = {'default_picking_ids': self.ids}
return action

View File

@@ -10,8 +10,10 @@
""",
'category': 'sf',
'website': 'https://www.sf.jikimo.com',
'depends': ['mrp', 'base', 'sf_manufacturing','web_widget_model_viewer','mrp_subcontracting'],
'depends': ['mrp', 'base', 'sf_manufacturing', 'web_widget_model_viewer', 'mrp_subcontracting', 'purchase_stock',
'uom'],
'data': [
'data/product_data.xml',
'data/uom_data.xml',
'views/product_template_view.xml',
'views/product_workorder.xml'
@@ -20,6 +22,7 @@
],
'qweb': [
],
'license': 'LGPL-3',
'installable': True,
'application': False,
'auto_install': False,

View File

@@ -2,8 +2,8 @@
<odoo>
<data noupdate="1">
<record id="product_category_embryo_sf" model="product.category">
<field name="name"></field>
<field name="type"></field>
<field name="name"></field>
<field name="type"></field>
</record>
<record id="product_category_finished_sf" model="product.category">
<field name="name">成品</field>
@@ -15,6 +15,11 @@
<field name="type">原材料</field>
</record>
<record id="product_category_surface_technics_sf" model="product.category">
<field name="name">表面工艺</field>
<field name="type">表面工艺</field>
</record>
<record id="product_template_sf" model="product.product">
<field name="name">CNC加工产品模板</field>
<field name="active" eval="False"/>
@@ -29,10 +34,10 @@
<field name="company_id" ref="base.main_company"/>
<field name="single_manufacturing">true</field>
<field name="tracking">serial</field>
<field name="is_bfm">false</field>
</record>
<record id="product_embryo_sf_self_machining" model="product.product">
<field name="name">料自加工模板</field>
<field name="name">料自加工模板</field>
<field name="active" eval="False"/>
<field name="categ_id" ref="product_category_embryo_sf"/>
<field name="route_ids"
@@ -45,10 +50,11 @@
<field name="company_id" ref="base.main_company"/>
<field name="single_manufacturing">true</field>
<field name="tracking">serial</field>
<field name="is_bfm">false</field>
</record>
<record id="product_embryo_sf_outsource" model="product.product">
<field name="name">料外协加工模板</field>
<field name="name">料外协加工模板</field>
<field name="active" eval="False"/>
<field name="categ_id" ref="product_category_embryo_sf"/>
<field name="route_ids"
@@ -60,9 +66,10 @@
<field name="uom_po_id" ref="uom.product_uom_unit"/>
<field name="company_id" ref="base.main_company"/>
<field name="tracking">serial</field>
<field name="is_bfm">false</field>
</record>
<record id="product_embryo_sf_purchase" model="product.product">
<field name="name">料采购模板</field>
<field name="name">料采购模板</field>
<field name="active" eval="False"/>
<field name="categ_id" ref="product_category_embryo_sf"/>
<field name="route_ids"
@@ -74,6 +81,7 @@
<field name="uom_po_id" ref="uom.product_uom_unit"/>
<field name="company_id" ref="base.main_company"/>
<field name="tracking">serial</field>
<field name="is_bfm">false</field>
</record>
</data>
</odoo>

View File

@@ -15,7 +15,8 @@ class ResProductTemplate(models.Model):
# 模型的长,宽,高,体积,精度,材料
model_name = fields.Char('模型名称')
categ_type = fields.Selection(
[("成品", "成品"), ("", ""), ("原材料", "原材料")], string='产品的类别', related='categ_id.type',
[("成品", "成品"), ("", ""), ("原材料", "原材料"), ("表面工艺", "表面工艺")],
string='产品的类别', related='categ_id.type',
store=True)
model_long = fields.Float('模型长[mm]', digits=(16, 3))
model_width = fields.Float('模型宽[mm]', digits=(16, 3))
@@ -28,10 +29,14 @@ class ResProductTemplate(models.Model):
('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='料模型类型')
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_surface_process_category_id = fields.Many2one('sf.production.process.category', string='表面工艺类别')
# model_surface_process_ids = fields.Many2many('sf.production.process', 'rel_product_process', string='表面工艺')
server_product_process_parameters_id = fields.Many2one('sf.production.process.parameter',
string='表面工艺参数(服务产品)')
model_process_parameters_ids = fields.Many2many('sf.production.process.parameter', 'process_parameter_rel',
string='表面工艺参数')
# model_price = fields.Float('模型单价', digits=(16, 3))
model_remark = fields.Char('模型备注说明')
length = fields.Float('长[mm]', digits=(16, 3))
@@ -53,7 +58,7 @@ class ResProductTemplate(models.Model):
# 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()
@@ -98,6 +103,7 @@ class ResProductTemplate(models.Model):
'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'],
@@ -106,10 +112,8 @@ class ResProductTemplate(models.Model):
[('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_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'],
@@ -120,6 +124,20 @@ class ResProductTemplate(models.Model):
# product_id.product_tmpl_id.active = False
return copy_product_id
# def get_production_process_id(self, surface_process_code):
# production_process_ids = []
# for item in surface_process_code:
# production_process = self.env['sf.production.process'].search([('process_encode', '=', item)])
# production_process_ids.append(production_process.id)
# return [(6, 0, production_process_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),
@@ -130,7 +148,7 @@ class ResProductTemplate(models.Model):
})
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
@@ -166,7 +184,7 @@ class ResProductTemplate(models.Model):
# [('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 = [
@@ -179,32 +197,33 @@ class ResProductTemplate(models.Model):
# 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['upload_model_file']:
# for item in vals['upload_model_file']:
# 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)
# self._sanitize_vals(vals)
# templates = super(ResProductTemplate, 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.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']:
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)
self._sanitize_vals(vals)
templates = super(ResProductTemplate, 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):
@@ -224,7 +243,7 @@ class ResProductTemplate(models.Model):
item.model_file = self.transition_glb_file(report_path, model_code)
# 将attach的datas内容转为glb文件
def transition_glb_file(self,report_path, code):
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)
@@ -269,11 +288,11 @@ class ResMrpBom(models.Model):
bom_id.subcontractor_id = subcontract.partner_id.id
return bom_id
# 料BOM组件选取当前料原材料,
# 然后根据当前的料的体积得出需要的原材料重量立方米m³ *材料密度 * 1000 = 所需原材料重量KG公斤
# 料所需原材料公式:当前的料的体积立方米m³ *材料密度 * 1000 = 所需原材料重量KG公斤
# 料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({
@@ -298,7 +317,7 @@ class ResMrpBom(models.Model):
# 匹配bom
def get_bom(self, product):
embryo_has = self.env['product.product'].search(
[('categ_id.type', '=', ''), ('materials_type_id', '=', product.materials_type_id.id),
[('categ_id.type', '=', ''), ('materials_type_id', '=', product.materials_type_id.id),
('length', '>', product.length), ('width', '>', product.width),
('height', '>', product.height), ('is_bfm', '=', False)
],
@@ -324,7 +343,7 @@ class ResProductCategory(models.Model):
_inherit = "product.category"
type = fields.Selection(
[("成品", "成品"), ("", ""), ("原材料", "原材料")],
[("成品", "成品"), ("", ""), ("原材料", "原材料"), ("表面工艺", "表面工艺")],
default="", string="类型")
# @api.constrains('type')

View File

@@ -5,8 +5,8 @@ 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_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="型号")

View File

@@ -14,16 +14,21 @@
<field name="detailed_type" position="before">
<field name='categ_type' invisible="1"/>
<field name="upload_model_file"
widget="many2many_binary"/>
widget="many2many_binary"
attrs="{'invisible': ['|', ('categ_type', '!=', '成品'),('categ_type', '=', False)]}"/>
<field name="model_file" widget="Viewer3D" string="模型" readonly="1" force_save="1"
attrs="{'invisible': ['|', ('categ_type', '!=', '成品'),('categ_type', '=', False)]}"/>
</field>
<field name="invoice_policy" position="after">
<field name="embryo_model_type_id" string="模型类型"
attrs="{'invisible': ['|',('categ_type', '!=', '料'),('categ_type', '=', False)]}"/>
<field name="materials_id" string="材料"/>
attrs="{'invisible': ['|',('categ_type', '!=', '料'),('categ_type', '=', False)]}"/>
<field name="materials_id" string="材料" attrs="{'invisible': [('categ_type', '=', '表面工艺')]}"/>
<field name="materials_type_id" string="型号"
domain="[('materials_id', '=', materials_id)]"/>
domain="[('materials_id', '=', materials_id)]"
attrs="{'invisible': [('categ_type', '=', '表面工艺')]}"/>
<field name="server_product_process_parameters_id" string="表面工艺参数"
options="{'no_create': True}"
attrs="{'invisible': ['|',('categ_type', '!=', '表面工艺'),('categ_type', '=', False)]}"/>
</field>
<xpath expr="//label[@for='volume']" position="before">
@@ -64,9 +69,9 @@
<field name="product_model_type_id" string="模型类型"/>
<field name="model_processing_panel" placeholder="例如R,U" string="加工面板"/>
<field name="model_machining_precision"/>
<field name="model_surface_process_id" string="表面工艺"/>
<field name="model_process_parameters_id" string="工艺参数"
domain="[('processing_order_ids', '=', model_surface_process_id)]"/>
<!-- <field name="model_surface_process_ids" string="表面工艺"/>-->
<field name="model_process_parameters_ids" string="表面工艺参数" widget="many2many_tags"
options="{'no_create': True}"/>
<field name="model_remark" string="备注说明"/>
</group>
</group>
@@ -140,7 +145,7 @@
<xpath expr="//filter[@name='consumable']" position="after">
<separator/>
<filter name="finish_product" string="成品" domain="[('categ_id.type','=','成品')]"/>
<filter name="embryo" string="料" domain="[('categ_id.type','=','料')]"/>
<filter name="embryo" string="料" domain="[('categ_id.type','=','料')]"/>
<filter name="raw_bom" string="原材料" domain="[('categ_id.type','=','原材料')]"/>
</xpath>
</field>

View File

@@ -37,7 +37,7 @@
'sf_machine_connect/static/src/js/*'
],
},
'license': 'LGPL-3',
'installable': True,
'application': True,
# 'auto_install': False,

View File

@@ -10,8 +10,9 @@
""",
'category': 'sf',
'website': 'https://www.sf.jikimo.com',
'depends': ['mrp', 'sf_base', 'maintenance', 'web_widget_model_viewer'],
'depends': ['mrp', 'sf_base', 'maintenance', 'web_widget_model_viewer', 'stock'],
'data': [
'data/stock_data.xml',
'security/group_security.xml',
'security/ir.model.access.csv',
'report/tray_report.xml',
@@ -27,6 +28,7 @@
],
'qweb': [
],
'license': 'LGPL-3',
'installable': True,
'application': False,
'auto_install': False,

View File

@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="sequence_stock_picking_in" model="ir.sequence">
<field name="name">YourCompany Sequence ocin</field>
<field name="prefix">WH/OCIN/</field>
<field name="padding">5</field>
</record>
<record id="sequence_stock_picking_out" model="ir.sequence">
<field name="name">YourCompany Sequence ocout</field>
<field name="prefix">WH/OCOUT/</field>
<field name="padding">5</field>
</record>
<record id="stock_location_locations_virtual_outcontract" model="stock.location">
<field name="name">外协</field>
<field name="location_id" ref="stock.stock_location_locations_virtual"/>
<field name="usage">internal</field>
<field name="barcode">VL-OC</field>
<field name="active">true</field>
<field name="company_id" ref="base.main_company"/>
</record>
<record id="outcontract_picking_in" model="stock.picking.type">
<field name="name">外协入库</field>
<field name="code">internal</field>
<field name="active">true</field>
<field name="sequence_id" ref="sequence_stock_picking_in"/>
<field name="company_id" ref="base.main_company"/>
<field name="sequence_code">OCIN</field>
<field name="default_location_src_id" ref="stock_location_locations_virtual_outcontract"/>
<field name="default_location_dest_id"
search="[('barcode','=','WH-PREPRODUCTION')]"/>
</record>
<record id="outcontract_picking_out" model="stock.picking.type">
<field name="name">外协出库</field>
<field name="code">internal</field>
<field name="sequence_id" ref="sequence_stock_picking_out"/>
<field name="active">true</field>
<field name="company_id" ref="base.main_company"/>
<field name="sequence_code">OCOUT</field>
<field name="default_location_src_id"
search="[('barcode','=','WH-PREPRODUCTION')]"/>
<field name="default_location_dest_id" ref="stock_location_locations_virtual_outcontract"/>
</record>
</data>
</odoo>

View File

@@ -6,11 +6,14 @@ class ModelType(models.Model):
_description = '模型类型'
name = fields.Char('名称')
embryo_tolerance = fields.Integer('料容余')
embryo_tolerance = fields.Integer('料容余')
product_routing_tmpl_ids = fields.One2many('sf.product.model.type.routing.sort', 'product_model_type_id',
'成品工序模板')
embryo_routing_tmpl_ids = fields.One2many('sf.embryo.model.type.routing.sort', 'embryo_model_type_id',
'料工序模板')
'料工序模板')
surface_technics_routing_tmpl_ids = fields.One2many('sf.surface_technics.model.type.routing.sort',
'surface_technics_model_type_id',
'表面工艺工序模板')
class ProductModelTypeRoutingSort(models.Model):
@@ -26,7 +29,7 @@ class ProductModelTypeRoutingSort(models.Model):
('前置三元定位检测', '前置三元定位检测'),
('CNC加工', 'CNC加工'),
('后置三元质量检测', '后置三元质量检测'),
('解除装夹', '解除装夹'), ('切割', '切割')
('解除装夹', '解除装夹'), ('切割', '切割'), ('表面工艺', '表面工艺')
], string="工序类型", related='route_workcenter_id.routing_type')
workcenter_ids = fields.Many2many('mrp.workcenter', required=False, related='route_workcenter_id.workcenter_ids')
product_model_type_id = fields.Many2one('sf.model.type')
@@ -38,7 +41,7 @@ class ProductModelTypeRoutingSort(models.Model):
class EmbryoModelTypeRoutingSort(models.Model):
_name = 'sf.embryo.model.type.routing.sort'
_description = '料工序排序'
_description = '料工序排序'
sequence = fields.Integer('Sequence')
route_workcenter_id = fields.Many2one('mrp.routing.workcenter')
@@ -49,11 +52,35 @@ class EmbryoModelTypeRoutingSort(models.Model):
('前置三元定位检测', '前置三元定位检测'),
('CNC加工', 'CNC加工'),
('后置三元质量检测', '后置三元质量检测'),
('解除装夹', '解除装夹'), ('切割', '切割')
('解除装夹', '解除装夹'), ('切割', '切割'), ('表面工艺', '表面工艺')
], string="工序类型", related='route_workcenter_id.routing_type')
workcenter_ids = fields.Many2many('mrp.workcenter', required=False, related='route_workcenter_id.workcenter_ids')
embryo_model_type_id = fields.Many2one('sf.model.type')
_sql_constraints = [
('route_model_type_uniq', 'unique (route_workcenter_id,embryo_model_type_id)', '料工序不能重复!')
('route_model_type_uniq', 'unique (route_workcenter_id,embryo_model_type_id)', '料工序不能重复!')
]
class SurfaceTechnicsModelTypeRoutingSort(models.Model):
_name = 'sf.surface_technics.model.type.routing.sort'
_description = '表面工艺工序排序'
sequence = fields.Integer('Sequence')
route_workcenter_id = fields.Many2one('mrp.routing.workcenter')
is_repeat = fields.Boolean('重复', related='route_workcenter_id.is_repeat')
routing_type = fields.Selection([
('获取CNC加工程序', '获取CNC加工程序'),
('装夹', '装夹'),
('前置三元定位检测', '前置三元定位检测'),
('CNC加工', 'CNC加工'),
('后置三元质量检测', '后置三元质量检测'),
('解除装夹', '解除装夹'), ('切割', '切割'), ('表面工艺', '表面工艺')
], string="工序类型", related='route_workcenter_id.routing_type')
workcenter_ids = fields.Many2many('mrp.workcenter', required=False, related='route_workcenter_id.workcenter_ids')
surface_technics_model_type_id = fields.Many2one('sf.model.type')
_sql_constraints = [
(
'route_model_type_uniq', 'unique (route_workcenter_id,surface_technics_model_type_id)', '表面工艺工序不能重复!')
]

View File

@@ -23,7 +23,7 @@ class MrpProduction(models.Model):
for production in self:
production.maintenance_count = len(production.request_ids)
#维修模块按钮
# 维修模块按钮
def button_maintenance_req(self):
self.ensure_one()
return {
@@ -37,7 +37,8 @@ class MrpProduction(models.Model):
},
'domain': [('production_id', '=', self.id)],
}
#打开维修模块请求
# 打开维修模块请求
def open_maintenance_request_mo(self):
self.ensure_one()
action = {
@@ -59,7 +60,8 @@ class MrpProduction(models.Model):
def action_generate_serial(self):
self.ensure_one()
iot_code = self.env['stock.lot']._get_next_serial(self.company_id, self.product_id) or self.env['ir.sequence'].next_by_code('stock.lot.serial')
iot_code = self.env['stock.lot']._get_next_serial(self.company_id, self.product_id) or self.env[
'ir.sequence'].next_by_code('stock.lot.serial')
iot_code_name = re.sub('[\u4e00-\u9fa5]', "", iot_code)
self.lot_producing_id = self.env['stock.lot'].create({
'product_id': self.product_id.id,
@@ -127,7 +129,44 @@ class MrpProduction(models.Model):
if i == processing_panel_len and route.routing_type == '解除装夹':
workorders_values.append(
self.env['mrp.workorder'].json_workorder_str(k, production, route))
elif production.product_id.categ_id.type == '胚料':
# 表面工艺工序
# 获取表面工艺id
surface_technics_arr = []
# 工序id
route_workcenter_arr = []
for item in production.product_id.product_model_type_id.surface_technics_routing_tmpl_ids:
surface_technics_arr.append(item.route_workcenter_id.surface_technics_id.id)
route_workcenter_arr.append(item.route_workcenter_id.id)
if surface_technics_arr:
production_process_category = self.env['sf.production.process.category'].search(
[('production_process_ids.id', 'in', surface_technics_arr)],
order='sequence asc'
)
# 用filter刷选表面工艺id'是否存在工艺类别对象里
if production_process_category:
for p in production_process_category:
production_process = p.production_process_ids.filtered(
lambda pp: pp.id in surface_technics_arr)
if production_process:
process_parameter = production.product_id.model_process_parameters_ids.filtered(
lambda pm: pm.process_id.id == production_process.id)
if process_parameter:
# 产品为表面工艺服务的供应商
product_production_process = self.env['product.template'].search(
[('server_product_process_parameters_id', '=', process_parameter.id)])
if product_production_process:
route_production_process = self.env[
'mrp.routing.workcenter'].search(
[('surface_technics_id', '=', production_process.id),
('id', 'in', route_workcenter_arr)])
if route_production_process:
workorders_values.append(
self.env[
'mrp.workorder']._json_workorder_surface_process_str(
production, route_production_process,
process_parameter,
product_production_process.seller_ids[0].partner_id.id))
elif production.product_id.categ_id.type == '坯料':
embryo_routing_workcenter = self.env['sf.embryo.model.type.routing.sort'].search(
[('embryo_model_type_id', '=', production.product_id.embryo_model_type_id.id)],
order='sequence asc'
@@ -135,12 +174,50 @@ class MrpProduction(models.Model):
for route in embryo_routing_workcenter:
workorders_values.append(
self.env['mrp.workorder'].json_workorder_str('', production, route))
production.workorder_ids= workorders_values
production.workorder_ids = workorders_values
process_parameter_workorder = self.env['mrp.workorder'].search(
[('surface_technics_parameters_id', '!=', False), ('production_id', '=', production.id),
('is_subcontract', '=', True)])
if process_parameter_workorder:
is_pick = False
consecutive_workorders = []
m = 0
sorted_workorders = sorted(process_parameter_workorder, key=lambda w: w.id)
for i in range(len(sorted_workorders) - 1):
if m == 0:
is_pick = False
if sorted_workorders[i].supplier_id.id == sorted_workorders[i + 1].supplier_id.id and \
sorted_workorders[i].is_subcontract == sorted_workorders[i + 1].is_subcontract and \
sorted_workorders[i].id == sorted_workorders[i + 1].id - 1:
if sorted_workorders[i] not in consecutive_workorders:
consecutive_workorders.append(sorted_workorders[i])
consecutive_workorders.append(sorted_workorders[i + 1])
m += 1
continue
else:
if m == len(consecutive_workorders) - 1 and m != 0:
self.env['stock.picking'].create_outcontract_picking(consecutive_workorders, production)
if sorted_workorders[i] in consecutive_workorders:
is_pick = True
consecutive_workorders = []
m = 0
# 当前面的连续工序生成对应的外协出入库单再生成当前工序的外协出入库单
if is_pick is False:
self.env['stock.picking'].create_outcontract_picking(sorted_workorders[i], production)
if m == len(consecutive_workorders) - 1 and m != 0:
self.env['stock.picking'].create_outcontract_picking(consecutive_workorders, production)
if sorted_workorders[i] in consecutive_workorders:
is_pick = True
consecutive_workorders = []
m = 0
if m == len(consecutive_workorders) - 1 and m != 0:
self.env['stock.picking'].create_outcontract_picking(consecutive_workorders, production)
if is_pick is False and m == 0:
self.env['stock.picking'].create_outcontract_picking(sorted_workorders[i], production)
for workorder in production.workorder_ids:
workorder.duration_expected = workorder._get_duration_expected()
#在之前的销售单上重新生成制造订单
# 在之前的销售单上重新生成制造订单
def create_production1_values(self, production):
production_values_str = {'origin': production.origin,
'product_id': production.product_id.id,
@@ -162,7 +239,21 @@ class MrpProduction(models.Model):
'user_id': production.user_id.id}
return production_values_str
#工单排序
def _get_stock_move_values_Res(self, item, location_src_id, location_dest_id, picking_type_id):
move_values = {
'name': item.name if item.name else '/',
'company_id': item.company_id.id,
'product_id': item.bom_id.bom_line_ids.product_id.id,
'product_uom': item.bom_id.bom_line_ids.product_uom_id.id,
'product_uom_qty': 1.0,
'location_id': location_src_id,
'location_dest_id': location_dest_id,
'origin': item.origin,
'picking_type_id': picking_type_id,
}
return move_values
# 工单排序
def _reset_work_order_sequence1(self, k):
sequen = 0
for rec in self:
@@ -179,7 +270,7 @@ class MrpProduction(models.Model):
if work.name == 'CNC加工(返工)' and work.processing_panel == k:
work.sequence = sequen + 1
#在制造订单上新增工单
# 在制造订单上新增工单
def _create_workorder1(self, k):
for production in self:
if not production.bom_id or not production.product_id:
@@ -213,9 +304,9 @@ class MrpProduction(models.Model):
production.product_id.model_processing_panel = k
for k in (production.product_id.model_processing_panel.split(',')):
routingworkcenter = self.env['sf.product.model.type.routing.sort'].search(
[('product_model_type_id', '=', production.product_id.product_model_type_id.id)],
order='sequence asc'
)
[('product_model_type_id', '=', production.product_id.product_model_type_id.id)],
order='sequence asc'
)
i += 1
for route in routingworkcenter:
@@ -248,9 +339,9 @@ class MrpProduction(models.Model):
if work.name == '获取CNC加工程序':
work.button_start()
work.button_finish()
#work.fetchCNC()
work.fetchCNC()
#创建工单并进行排序
# 创建工单并进行排序
def _create_workorder(self):
res = self._create_workorder3()
self._reset_work_order_sequence()

View File

@@ -1,4 +1,5 @@
from odoo import fields, models
import logging
class ResMrpRoutingWorkcenter(models.Model):
@@ -11,12 +12,14 @@ class ResMrpRoutingWorkcenter(models.Model):
('CNC加工', 'CNC加工'),
('后置三元质量检测', '后置三元质量检测'),
('解除装夹', '解除装夹'),
('切割', '切割')
('切割', '切割'),
('表面工艺', '表面工艺')
], string="工序类型")
is_repeat = fields.Boolean('重复', default=False)
workcenter_id = fields.Many2one('mrp.workcenter', required=False)
workcenter_ids = fields.Many2many('mrp.workcenter', 'rel_workcenter_route', required=True)
bom_id = fields.Many2one('mrp.bom', required=False)
surface_technics_id = fields.Many2one('sf.production.process', string="表面工艺")
# 获得当前登陆者公司
def get_company_id(self):
@@ -24,7 +27,8 @@ class ResMrpRoutingWorkcenter(models.Model):
company_id = fields.Many2one('res.company', compute="get_company_id", related=False)
# 排产的时候, 根据胚料的长宽高比对一下机床的最大加工尺寸.不符合就不要分配给这个加工中心(机床).
# 排产的时候, 根据坯料的长宽高比对一下机床的最大加工尺寸.不符合就不要分配给这个加工中心(机床).
# 工单对应的工作中心,根据工序中的工作中心去匹配,
# 如果只配置了一个工作中心,则默认采用该工作中心;
# 如果有多个工作中心,
@@ -39,11 +43,13 @@ class ResMrpRoutingWorkcenter(models.Model):
workcenter = self.env['mrp.workcenter'].search([('id', 'in', workcenter_ids)])
workcenter_ids = []
for item in workcenter:
print(item.name)
logging.info('get_workcenter-vals:%s' % item.machine_tool_id.name)
if item.machine_tool_id:
machine_tool = self.env['sf.machine_tool'].search(
[('x_axis', '>', product.bom_ids.bom_line_ids.product_id.length), ('y_axis', '>', product.bom_ids.bom_line_ids.product_id.width),
('z_axis', '>', product.bom_ids.bom_line_ids.product_id.height), ('id', '=', item.machine_tool_id.id)])
[('x_axis', '>', product.bom_ids.bom_line_ids.product_id.length),
('y_axis', '>', product.bom_ids.bom_line_ids.product_id.width),
('z_axis', '>', product.bom_ids.bom_line_ids.product_id.height),
('id', '=', item.machine_tool_id.id)])
if machine_tool:
workcenter_ids.append(item.id)
if len(workcenter_ids) == 1:
@@ -52,5 +58,9 @@ class ResMrpRoutingWorkcenter(models.Model):
SELECT workcenter_id FROM mrp_workorder where workcenter_id
in %s group by workcenter_id
order by count(*),workcenter_id asc limit 1 """, [tuple(workcenter_ids)])
workcenter_id = self.env.cr.dictfetchall()[0].get('workcenter_id')
res = self.env.cr.fetchone()
if res:
workcenter_id = res[0]
else:
workcenter_id = workcenter_ids[0]
return workcenter_id

View File

@@ -7,13 +7,18 @@ from odoo.addons.resource.models.resource import Intervals
class ResWorkcenter(models.Model):
_inherit = "mrp.workcenter"
machine_tool_id = fields.Many2one('sf.machine_tool', '机床')
is_process_outsourcing = fields.Boolean('工艺外协')
users_ids = fields.Many2many("res.users", 'users_workcenter')
equipment_ids = fields.One2many(
'maintenance.equipment', 'workcenter_id', string="Maintenance Equipment",
check_company=True)
# 查询工艺外协加工中心
def get_process_outsourcing_workcenter(self):
outsourcing_workcenter = self.env['mrp.workcenter'].search([('is_process_outsourcing', '=', True)])
return outsourcing_workcenter.id
@api.onchange('machine_tool_id')
def update_machine_tool_is_binding(self):
machine_tool = self.env["sf.machine_tool"].search([('is_binding', '=', True)])

View File

@@ -29,7 +29,7 @@ class ResMrpWorkOrder(models.Model):
('CNC加工', 'CNC加工'),
('后置三元质量检测', '后置三元质量检测'),
('解除装夹', '解除装夹'),
('切割', '切割')
('切割', '切割'), ('表面工艺', '表面工艺')
], string="工序类型")
results = fields.Char('检测结果')
@@ -54,7 +54,7 @@ class ResMrpWorkOrder(models.Model):
programming_state = fields.Char('编程状态')
cnc_worksheet = fields.Binary(
'工作指令', readonly=True)
material_center_point = fields.Char(string='料中心点')
material_center_point = fields.Char(string='料中心点')
X1_axis = fields.Float(default=0)
Y1_axis = fields.Float(default=0)
Z1_axis = fields.Float(default=0)
@@ -91,6 +91,16 @@ class ResMrpWorkOrder(models.Model):
cnc_ids = fields.One2many("sf.cnc.processing", 'workorder_id', string="CNC加工")
tray_code = fields.Char(string="托盘")
glb_file = fields.Binary("glb模型文件")
is_subcontract = fields.Boolean(string='是否外协')
surface_technics_parameters_id = fields.Many2one('sf.production.process.parameter', string="表面工艺可选参数")
picking_in_id = fields.Many2one('stock.picking', string='外协入库单')
picking_out_id = fields.Many2one('stock.picking', string='外协出库单')
supplier_id = fields.Many2one('res.partner', string='外协供应商')
def get_no_data(self, production_id):
process_parameter_workorder = self.search(
[('surface_technics_parameters_id', '!=', False), ('production_id', '=', production_id)])
return process_parameter_workorder
# 计算配料中心点和与x轴倾斜度方法
def getcenter(self):
@@ -137,7 +147,7 @@ class ResMrpWorkOrder(models.Model):
except:
raise UserError("参数计算有误")
#拼接工单对象属性值
# 拼接工单对象属性值
def json_workorder_str(self, k, production, route):
workorders_values_str = [0, '', {
'product_uom_id': production.product_uom_id.id,
@@ -159,6 +169,31 @@ class ResMrpWorkOrder(models.Model):
}]
return workorders_values_str
# 拼接工单对象属性值(表面工艺)
def _json_workorder_surface_process_str(self, production, route, process_parameter, supplier_id):
workorders_values_str = [0, '', {
'product_uom_id': production.product_uom_id.id,
'qty_producing': 0,
'operation_id': False,
'name': '%s-%s' % (route.name, process_parameter.name),
'processing_panel': '',
'routing_type': '表面工艺',
'surface_technics_parameters_id': process_parameter.id,
'work_state': '',
'supplier_id': supplier_id,
'is_subcontract': True if process_parameter.gain_way == '外协' else False,
'workcenter_id': self.env[
'mrp.workcenter'].get_process_outsourcing_workcenter() if process_parameter.gain_way == '外协' else
self.env['mrp.routing.workcenter'].get_workcenter(route.workcenter_ids.ids,
route.routing_type,
production.product_id),
'date_planned_start': False,
'date_planned_finished': False,
'duration_expected': 60,
'duration': 0
}]
return workorders_values_str
# 维修模块按钮
def button_maintenance_req(self):
self.ensure_one()
@@ -205,9 +240,9 @@ class ResMrpWorkOrder(models.Model):
else:
raise UserError('托盘码不能为空')
#验证坯料序列号是否正确
def pro_code_is_ok(self,barcode):
if barcode!=False:
# 验证坯料序列号是否正确
def pro_code_is_ok(self, barcode):
if barcode != False:
if barcode != self.pro_code:
raise UserError('坯料序列号错误')
return False
@@ -218,7 +253,7 @@ class ResMrpWorkOrder(models.Model):
pro_code_ok = fields.Boolean(default=False)
#托盘扫码绑定
# 托盘扫码绑定
def gettray_auto(self, barcode):
if barcode != False:
values = self.env['sf.tray'].search([("code", "=", barcode)])
@@ -257,7 +292,6 @@ class ResMrpWorkOrder(models.Model):
else:
raise UserError('托盘码不能为空')
# 解除托盘绑定
def unbindtray(self):
tray = self.env['sf.tray'].search([("production_id", "=", self.production_id.id)])
@@ -396,7 +430,6 @@ class ResMrpWorkOrder(models.Model):
}]
return workorders_values_str
# 重写工单开始按钮方法
def button_start(self):
if self.routing_type == '装夹':
@@ -449,6 +482,51 @@ class ResMrpWorkOrder(models.Model):
else:
raise UserError(_('请先完成上一步工单'))
def button_finish(self):
end_date = datetime.now()
for workorder in self:
if workorder.state in ('done', 'cancel'):
continue
workorder.end_all()
vals = {
'qty_produced': workorder.qty_produced or workorder.qty_producing or workorder.qty_production,
'state': 'done',
'date_finished': end_date,
'date_planned_finished': end_date,
'costs_hour': workorder.workcenter_id.costs_hour
}
if not workorder.date_start:
vals['date_start'] = end_date
if not workorder.date_planned_start or end_date < workorder.date_planned_start:
vals['date_planned_start'] = end_date
workorder.with_context(bypass_duration_calculation=True).write(vals)
self.env.cr.commit()
finish_workorder_count = self.env['mrp.workorder'].search_count(
[('production_id', '=', workorder.production_id.id),
('is_subcontract', '=', True)])
subcontract_workorder_count = self.env['mrp.workorder'].search_count(
[('production_id', '=', workorder.production_id.id), ('is_subcontract', '=', True),
('state', '=', 'done')])
if finish_workorder_count > 0 and subcontract_workorder_count > 0:
subcontract_workorder = self.env['mrp.workorder'].search(
[('production_id', '=', workorder.production_id.id), ('is_subcontract', '=', True),
('state', '=', 'done')])
for item in subcontract_workorder:
order_line_ids = []
server_product = self.env['product.template'].search(
[('server_product_process_parameters_id', '=', item.surface_technics_parameters_id.id),
('categ_type', '=', '服务'), ('detailed_type', '=', 'service')])
order_line_ids.append((0, 0, {
'product_id': server_product.product_variant_id.id,
'product_qty': 1,
'product_uom': server_product.uom_id.id
}))
self.env['purchase.order'].create({
'partner_id': server_product.seller_ids.partner_id.id,
'state': 'draft',
'order_line': order_line_ids,
})
class CNCprocessing(models.Model):
_name = 'sf.cnc.processing'
@@ -471,7 +549,6 @@ class CNCprocessing(models.Model):
workorder_id = fields.Many2one('mrp.workorder', string="工单")
button_state = fields.Boolean(string='是否已经下发')
# mrs下发编程单创建CNC加工
def cnc_processing_create(self, cnc_workorder, ret):
logging.info('ret:%s' % ret)
@@ -554,6 +631,25 @@ class CNCprocessing(models.Model):
else:
return False
# end_date = datetime.now()
# for workorder in self:
# if workorder.state in ('done', 'cancel'):
# continue
# workorder.end_all()
# vals = {
# 'qty_produced': workorder.qty_produced or workorder.qty_producing or workorder.qty_production,
# 'state': 'done',
# 'date_finished': end_date,
# 'date_planned_finished': end_date,
# 'costs_hour': workorder.workcenter_id.costs_hour
# }
# if not workorder.date_start:
# vals['date_start'] = end_date
# if not workorder.date_planned_start or end_date < workorder.date_planned_start:
# vals['date_planned_start'] = end_date
# workorder.with_context(bypass_duration_calculation=True).write(vals)
return True
class SfWorkOrderBarcodes(models.Model):
"""
@@ -571,8 +667,6 @@ class SfWorkOrderBarcodes(models.Model):
else:
self.pro_code_ok = workorder.pro_code_is_ok(barcode)
# return {
# 'type': 'ir.actions.act_window',
# 'name': '工单',
@@ -581,5 +675,3 @@ class SfWorkOrderBarcodes(models.Model):
# 'context': {'active_id': self.id},
# # 'target': 'current',
# }

View File

@@ -3,9 +3,11 @@ from collections import defaultdict, namedtuple
from odoo.addons.stock.models.stock_rule import ProcurementException
from re import findall as regex_findall
from re import split as regex_split
from odoo import SUPERUSER_ID, _, api, models
from odoo import SUPERUSER_ID, _, api, fields, models
from odoo.tools import float_compare
from odoo.exceptions import UserError
class StockRule(models.Model):
_inherit = 'stock.rule'
@@ -226,3 +228,72 @@ class ProductionLot(models.Model):
return self.env['stock.lot'].generate_lot_names1(product.name, last_serial.name, 2)[
1]
return "%s-%03d" % (product.name, 1)
class StockPicking(models.Model):
_inherit = 'stock.picking'
workorder_in_id = fields.One2many('mrp.workorder', 'picking_in_id')
workorder_out_id = fields.One2many('mrp.workorder', 'picking_out_id')
# 设置外协出入单的名称
def _get_name_Res(self, rescode):
count = self.env['ir.sequence'].search_count([('prefix', 'like', rescode)])
if not count:
num = "%04d" % 1
else:
m = int(count) + 1
num = "%04d" % m
return '%s%s' % (rescode, num)
# 创建 外协出库入单
def create_outcontract_picking(self, sorted_workorders_arr, item):
m = 0
for sorted_workorders in sorted_workorders_arr:
if m == 0:
outcontract_stock_move = self.env['stock.move'].search(
[('workorder_id', '=', sorted_workorders.id), ('production_id', '=', item.id)])
if not outcontract_stock_move:
location_id = self.env.ref(
'sf_manufacturing.stock_location_locations_virtual_outcontract').id,
location_dest_id = self.env['stock.location'].search(
[('barcode', '=', 'WH-PREPRODUCTION')]).id,
outcontract_picking_type_in = self.env.ref(
'sf_manufacturing.outcontract_picking_in').id,
outcontract_picking_type_out = self.env.ref(
'sf_manufacturing.outcontract_picking_out').id,
moves_in = self.env['stock.move'].sudo().create(
item._get_stock_move_values_Res(item, location_id, location_dest_id,
outcontract_picking_type_in))
moves_out = self.env['stock.move'].sudo().create(
item._get_stock_move_values_Res(item, location_dest_id, location_id,
outcontract_picking_type_out))
new_picking = True
picking_in = self.env['stock.picking'].create(
moves_in._get_new_picking_values_Res(item, sorted_workorders,'WH/OCIN/'))
picking_out = self.env['stock.picking'].create(
moves_out._get_new_picking_values_Res(item, sorted_workorders, 'WH/OCOUT/'))
moves_in.write({'picking_id': picking_in.id})
moves_out.write({'picking_id': picking_out.id})
moves_in._assign_picking_post_process(new=new_picking)
moves_out._assign_picking_post_process(new=new_picking)
m += 1
sorted_workorders.write({'picking_in_id': picking_in.id, 'picking_out_id': picking_out.id})
class ReStockMove(models.Model):
_inherit = 'stock.move'
def _get_new_picking_values_Res(self, item, sorted_workorders, rescode):
return {
'name': self.env['stock.picking']._get_name_Res(rescode),
'origin': item.name,
'company_id': self.mapped('company_id').id,
'user_id': False,
'move_type': self.mapped('group_id').move_type or 'direct',
'partner_id': sorted_workorders.supplier_id.id,
'picking_type_id': self.mapped('picking_type_id').id,
'location_id': self.mapped('location_id').id,
'location_dest_id': self.mapped('location_dest_id').id,
'state': 'draft',
}

View File

@@ -3,7 +3,7 @@
import base64
from io import BytesIO
from odoo import api, fields, models
from pystrich.code128 import Code128Encoder
#from pystrich.code128 import Code128Encoder
class Tray(models.Model):

View File

@@ -3,6 +3,8 @@ access_sf_cnc_processing,sf_cnc_processing,model_sf_cnc_processing,base.group_us
access_sf_model_type,sf_model_type,model_sf_model_type,base.group_user,1,1,1,1
access_sf_product_model_type_routing_sort,sf_product_model_type_routing_sort,model_sf_product_model_type_routing_sort,base.group_user,1,1,1,1
access_sf_embryo_model_type_routing_sort,sf_embryo_model_type_routing_sort,model_sf_embryo_model_type_routing_sort,base.group_user,1,1,1,1
access_sf_surface_technics_model_type_routing_sort,sf_surface_technics_model_type_routing_sort,model_sf_surface_technics_model_type_routing_sort,base.group_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
3 access_sf_model_type sf_model_type model_sf_model_type base.group_user 1 1 1 1
4 access_sf_product_model_type_routing_sort sf_product_model_type_routing_sort model_sf_product_model_type_routing_sort base.group_user 1 1 1 1
5 access_sf_embryo_model_type_routing_sort sf_embryo_model_type_routing_sort model_sf_embryo_model_type_routing_sort base.group_user 1 1 1 1
6 access_sf_surface_technics_model_type_routing_sort sf_surface_technics_model_type_routing_sort model_sf_surface_technics_model_type_routing_sort base.group_user 1 1 1 1
7
8
9
10

View File

@@ -31,7 +31,7 @@
<form string="模型类型">
<group>
<field name="name" required="1"/>
<field name="embryo_tolerance" required="1" string="料容余(mm)"/>
<field name="embryo_tolerance" required="1" string="料容余(mm)"/>
</group>
<group>
<field name='product_routing_tmpl_ids'>
@@ -55,6 +55,17 @@
</tree>
</field>
</group>
<group>
<field name='surface_technics_routing_tmpl_ids' style="white-space: pre-wrap;">
<tree editable='bottom'>
<field name="sequence" widget="handle" string="序号"/>
<field name="route_workcenter_id" string="工序"/>
<field name="routing_type" string="类型"/>
<field name="is_repeat" string="重复"/>
<field name="workcenter_ids" string="工作中心" widget="many2many_tags"/>
</tree>
</field>
</group>
</form>
</field>
</record>

View File

@@ -8,6 +8,8 @@
<field name="arch" type="xml">
<field name="workcenter_id" position="replace">
<field name="workcenter_ids" widget="many2many_tags" string="工作中心" required="0"/>
<field name="surface_technics_id"
attrs="{'invisible':[('routing_type','!=','表面工艺')],'required':[('routing_type','=','表面工艺')]}"/>
</field>
<field name="bom_product_template_attribute_value_ids" position="after">
<field name="routing_type" required="1"/>

View File

@@ -109,6 +109,9 @@
<xpath expr="//field[@name='alternative_workcenter_ids']" position="after">
<field name="machine_tool_id" domain="[('is_binding','=',False)]"/>
</xpath>
<xpath expr="//field[@name='resource_calendar_id']" position="after">
<field name="is_process_outsourcing"/>
</xpath>
</field>
</record>

View File

@@ -5,6 +5,10 @@
<field name="model">mrp.workorder</field>
<field name="inherit_id" ref="mrp.mrp_production_workorder_tree_editable_view"/>
<field name="arch" type="xml">
<field name="name" position="replace">
<field name="is_subcontract" invisible="1"/>
<field name="name" decoration-success="is_subcontract" decoration-bf="is_subcontract"/>
</field>
<field name="name" position="before">
<field name="sequence"/>
<field name='user_permissions' invisible="1"/>
@@ -113,8 +117,7 @@
('is_user_working', '!=', False),('user_permissions','=',False),('name','=','获取CNC加工程序')]}"/>
<button name="button_pending" type="object" string="暂停" class="btn-warning"
attrs="{'invisible': ['|', '|','|', ('production_state', 'in', ('draft', 'done', 'cancel')), ('working_state', '=', 'blocked'), ('is_user_working', '=', False),('name','=','获取CNC加工程序')]}"/>
<button name="button_finish" type="object" string="完成" class="btn-success"
attrs="{'invisible': ['|', '|','|', ('production_state', 'in', ('draft', 'done', 'cancel')), ('working_state', '=', 'blocked'), ('is_user_working', '=', False),('name','=','获取CNC加工程序')]}"/>
<button name="button_finish" type="object" string="完成" class="btn-success"/>
<button name="%(mrp.act_mrp_block_workcenter_wo)d" type="action" string="停工"
context="{'default_workcenter_id': workcenter_id}" class="btn-danger"
attrs="{'invisible': ['|', '|','|', ('production_state', 'in', ('draft', 'done', 'cancel')), ('working_state', '=', 'blocked'),('user_permissions','=',False),('name','=','获取CNC加工程序')]}"/>
@@ -162,7 +165,7 @@
<group>
<div class="col-12 col-lg-6 o_setting_box" style="white-space: nowrap">
<button type="object" class="oe_highlight" name="fetchCNC" string="获取CNC程序代码"
attrs='{"invisible": ["|", "|", ("state","!=","progress"),("user_permissions","=",False),("programming_no","!=",False)]}'/>
/>
</div>
</group>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<data>
<record model="ir.ui.view" id="view_picking_form_inherit_sf">
<field name="name">stock.picking.form.inherit.sf</field>
<field name="model">stock.picking</field>
<field name="inherit_id" ref="stock.view_picking_form"/>
<field name="arch" type="xml">
</field>
</record>
</data>
</odoo>

View File

@@ -19,6 +19,7 @@
],
'qweb': [
],
'license': 'LGPL-3',
'installable': True,
'application': False,
'auto_install': False,

View File

@@ -77,16 +77,16 @@
<field name="doall" eval="False"/>
</record>
<!-- <record model="ir.cron" id="sf_cron8">-->
<!-- <field name="name">同步注册机床</field>-->
<!-- <field name="model_id" ref="model_mrs_machine_tool"/>-->
<!-- <field name="state">code</field>-->
<!-- <field name="code">model.enroll_machine_tool()</field>-->
<!-- <field name="interval_number">1</field>-->
<!-- <field name="interval_type">days</field>-->
<!-- <field name="numbercall">-1</field>-->
<!-- <field name="doall" eval="False"/>-->
<!-- </record>-->
<record model="ir.cron" id="sf_cron8">
<field name="name">同步表面工艺类别</field>
<field name="model_id" ref="model_sf_production_process_category"/>
<field name="state">code</field>
<field name="code">model.sync_production_process_category()</field>
<field name="interval_number">1</field>
<field name="interval_type">days</field>
<field name="numbercall">-1</field>
<field name="doall" eval="False"/>
</record>
<record model="ir.cron" id="sf_cron9">
<field name="name">同步资源库机床型号</field>
@@ -132,7 +132,7 @@
<field name="doall" eval="False"/>
</record>
<record model="ir.cron" id="sf_cron13">
<record model="ir.cron" id="sf_cron13">
<field name="name">同步表面工艺可选参数</field>
<field name="model_id" ref="model_sf_production_process_parameter"/>
<field name="state">code</field>

View File

@@ -24,6 +24,8 @@ class ResConfigSettings(models.TransientModel):
_logger.info("同步资源库材料")
self.env['sf.materials.model'].sync_all_materials_model()
_logger.info("同步资源库材料型号")
self.env['sf.production.process.category'].sync_all_production_process_category()
_logger.info("同步资源库表面工艺类别")
self.env['sf.production.process'].sync_all_production_process()
_logger.info("同步资源库表面工艺")
self.env['sf.processing.technology'].sync_all_processing_technology()

View File

@@ -6,6 +6,7 @@ from odoo import models
from odoo.exceptions import ValidationError
import logging
from odoo.addons.sf_base.commons.common import Common
_logger = logging.getLogger(__name__)
@@ -162,6 +163,66 @@ class sfMaterialModel(models.Model):
raise ValidationError("认证未通过")
class sfProductionProcessCategory(models.Model):
_inherit = 'sf.production.process.category'
_description = '表面工艺类别'
url = '/api/production_process_category/list'
# 定时同步每日表面工艺类别
def sync_production_process_category(self):
sf_sync_config = self.env['res.config.settings'].get_values()
token = sf_sync_config['token']
sf_secret_key = sf_sync_config['sf_secret_key']
headers = Common.get_headers(self, token, sf_secret_key)
strUrl = sf_sync_config['sf_url'] + self.url
r = requests.post(strUrl, json={}, data=None, headers=headers)
r = r.json()
result = json.loads(r['result'])
if result['status'] == 1:
for item in result['production_process_category_yesterday_list']:
if item:
production_process_category = self.env['sf.production.process.category'].search(
[("code", '=', item['code'])])
if production_process_category:
production_process_category.name = item['name'],
production_process_category.code = item['code'],
production_process_category.active = item['active']
else:
self.env['sf.production.process.category'].create({
"name": item['name'],
"code": item['code'],
"active": item['active'],
})
else:
raise ValidationError("认证未通过") # 定时同步表面工艺
# 同步所有表面工艺类别
def sync_all_production_process_category(self):
sf_sync_config = self.env['res.config.settings'].get_values()
token = sf_sync_config['token']
sf_secret_key = sf_sync_config['sf_secret_key']
headers = Common.get_headers(self, token, sf_secret_key)
strUrl = sf_sync_config['sf_url'] + self.url
r = requests.post(strUrl, json={}, data=None, headers=headers)
r = r.json()
result = json.loads(r['result'])
if result['status'] == 1:
for item in result['production_process_category_all_list']:
if item:
category = self.env['sf.production.process.category'].search(
[("code", '=', item['code'])])
if not category:
self.env['sf.production.process.category'].create({
"name": item['name'],
"code": item['code'],
"active": item['active'],
})
else:
raise ValidationError("认证未通过")
class sfProductionProcess(models.Model):
_inherit = 'sf.production.process'
_description = '表面工艺'
@@ -184,21 +245,21 @@ class sfProductionProcess(models.Model):
brand = self.env['sf.production.process'].search(
[("process_encode", '=', item['process_encode'])])
if brand:
brand.id = item['id'],
brand.name = item['name'],
brand.category_id = self.env['sf.production.process.category'].search(
[("code", '=', item['category_code'])]).id,
brand.process_encode = item['process_encode'],
brand.remark = item['remark'],
brand.active = item['active'],
brand.remark = item['remark']
else:
self.env['sf.production.process'].create({
"id": item['id'],
"name": item['name'],
"category_id": self.env['sf.production.process.category'].search(
[("code", '=', item['category_code'])]).id,
"process_encode": item['process_encode'],
"remark": item['remark'],
"active": item['active'],
# "tag_ids": item['tag_ids']
})
else:
raise ValidationError("认证未通过") # 定时同步表面工艺
@@ -221,8 +282,9 @@ class sfProductionProcess(models.Model):
[("process_encode", '=', item['process_encode'])])
if not brand:
self.env['sf.production.process'].create({
"id": item['id'],
"name": item['name'],
"category_id": self.env['sf.production.process.category'].search(
[("code", '=', item['category_code'])]).id,
"process_encode": item['process_encode'],
"remark": item['remark'],
"active": item['active'],
@@ -949,6 +1011,7 @@ class sfProcessingOrder(models.Model):
else:
raise ValidationError("认证未通过")
class sfProductionProcessParameter(models.Model):
_inherit = 'sf.production.process.parameter'
_description = '表面工艺可选参数'
@@ -974,13 +1037,13 @@ class sfProductionProcessParameter(models.Model):
brand.name = item['name'],
brand.code = item['code'],
brand.active = item['active'],
brand.price = item['price'],
# brand.price = item['price'],
else:
self.env['sf.production.process.parameter'].create({
"name": item['name'],
"code": item['code'],
"active": item['active'],
"price" : item['price'],
# "price": item['price'],
"process_id": self.env['sf.production.process'].search(
[('process_encode', '=', item['process_id_code'])]).id,
'materials_model_ids': self.env['sf.materials.model'].search(
@@ -1010,7 +1073,7 @@ class sfProductionProcessParameter(models.Model):
self.env['sf.production.process.parameter'].create({
"name": item['name'],
"code": item['code'],
"price": item['price'],
# "price": item['price'],
"active": item['active'],
"process_id": self.env['sf.production.process'].search(
[('process_encode', '=', item['process_id_code'])]).id,
@@ -1019,4 +1082,4 @@ class sfProductionProcessParameter(models.Model):
})
else:
raise ValidationError("认证未通过")
raise ValidationError("认证未通过")

View File

@@ -18,6 +18,7 @@
],
'qweb': [
],
'license': 'LGPL-3',
'installable': True,
'application': False,
'auto_install': False,