Merge branch develop into feature/增加一键合并下发功能,修正合并下发逻辑
This commit is contained in:
@@ -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,
|
||||
|
||||
52
sf_manufacturing/data/stock_data.xml
Normal file
52
sf_manufacturing/data/stock_data.xml
Normal 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>
|
||||
@@ -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)', '表面工艺工序不能重复!')
|
||||
]
|
||||
|
||||
@@ -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,45 @@ 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
|
||||
if production.product_id.model_process_parameters_ids:
|
||||
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'
|
||||
@@ -136,11 +176,49 @@ class MrpProduction(models.Model):
|
||||
workorders_values.append(
|
||||
self.env['mrp.workorder'].json_workorder_str('', production, route))
|
||||
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 +240,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 +271,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 +305,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:
|
||||
@@ -247,10 +339,10 @@ class MrpProduction(models.Model):
|
||||
current_sequence += 1
|
||||
if work.name == '获取CNC加工程序':
|
||||
work.button_start()
|
||||
#work.button_finish()
|
||||
work.button_finish()
|
||||
work.fetchCNC()
|
||||
|
||||
#创建工单并进行排序
|
||||
# 创建工单并进行排序
|
||||
def _create_workorder(self):
|
||||
res = self._create_workorder3()
|
||||
self._reset_work_order_sequence()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)])
|
||||
|
||||
@@ -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,
|
||||
@@ -145,6 +155,7 @@ class ResMrpWorkOrder(models.Model):
|
||||
'operation_id': False,
|
||||
'name': route.route_workcenter_id.name,
|
||||
'processing_panel': k,
|
||||
'quality_point_ids':route.route_workcenter_id.quality_point_ids,
|
||||
'routing_type': route.routing_type,
|
||||
'work_state': '' if not route.routing_type == '获取CNC加工程序' else '待发起',
|
||||
'workcenter_id': self.env['mrp.routing.workcenter'].get_workcenter(route.workcenter_ids.ids,
|
||||
@@ -153,6 +164,32 @@ class ResMrpWorkOrder(models.Model):
|
||||
'date_planned_start': False,
|
||||
'date_planned_finished': False,
|
||||
'duration_expected': 60,
|
||||
'duration': 0,
|
||||
|
||||
}]
|
||||
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
|
||||
@@ -203,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
|
||||
@@ -216,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)])
|
||||
@@ -255,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)])
|
||||
@@ -389,16 +425,17 @@ class ResMrpWorkOrder(models.Model):
|
||||
'date_planned_start': False,
|
||||
'date_planned_finished': False,
|
||||
'duration_expected': 60,
|
||||
'duration': 0
|
||||
'duration': 0,
|
||||
|
||||
}]
|
||||
return workorders_values_str
|
||||
|
||||
|
||||
# 重写工单开始按钮方法
|
||||
def button_start(self):
|
||||
if self.routing_type == '装夹':
|
||||
self.pro_code = self.production_id.move_raw_ids[0].move_line_ids[0].lot_id.name
|
||||
if self.state == 'waiting' or self.state == 'ready' or self.state == 'progress':
|
||||
self.move_raw_ids = self.production_id.move_raw_ids
|
||||
self.ensure_one()
|
||||
if any(not time.date_end for time in self.time_ids.filtered(lambda t: t.user_id.id == self.env.user.id)):
|
||||
return True
|
||||
@@ -445,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'
|
||||
@@ -467,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)
|
||||
@@ -550,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):
|
||||
"""
|
||||
|
||||
543
sf_manufacturing/models/quality.py
Normal file
543
sf_manufacturing/models/quality.py
Normal file
@@ -0,0 +1,543 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from markupsafe import Markup
|
||||
from odoo import SUPERUSER_ID, api, fields, models, _
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.fields import Command
|
||||
from odoo.tools import float_compare, float_round
|
||||
|
||||
|
||||
class TestType(models.Model):
|
||||
_inherit = "quality.point.test_type"
|
||||
|
||||
allow_registration = fields.Boolean(search='_get_domain_from_allow_registration',
|
||||
store=False, default=False)
|
||||
|
||||
def _get_domain_from_allow_registration(self, operator, value):
|
||||
if value:
|
||||
return []
|
||||
else:
|
||||
return [('technical_name', 'not in', ['register_byproducts', 'register_consumed_materials', 'print_label'])]
|
||||
|
||||
|
||||
class MrpRouting(models.Model):
|
||||
_inherit = "mrp.routing.workcenter"
|
||||
|
||||
quality_point_ids = fields.One2many('quality.point', 'operation_id', copy=True)
|
||||
quality_point_count = fields.Integer('Instructions', compute='_compute_quality_point_count')
|
||||
|
||||
@api.depends('quality_point_ids')
|
||||
def _compute_quality_point_count(self):
|
||||
read_group_res = self.env['quality.point'].sudo().read_group(
|
||||
[('id', 'in', self.quality_point_ids.ids)],
|
||||
['operation_id'], 'operation_id'
|
||||
)
|
||||
data = dict((res['operation_id'][0], res['operation_id_count']) for res in read_group_res)
|
||||
for operation in self:
|
||||
operation.quality_point_count = data.get(operation.id, 0)
|
||||
|
||||
def write(self, vals):
|
||||
res = super().write(vals)
|
||||
if 'bom_id' in vals:
|
||||
self.quality_point_ids._change_product_ids_for_bom(self.bom_id)
|
||||
return res
|
||||
|
||||
def copy(self, default=None):
|
||||
res = super().copy(default)
|
||||
if default and "bom_id" in default:
|
||||
res.quality_point_ids._change_product_ids_for_bom(res.bom_id)
|
||||
return res
|
||||
|
||||
def toggle_active(self):
|
||||
self.with_context(active_test=False).quality_point_ids.toggle_active()
|
||||
return super().toggle_active()
|
||||
|
||||
def action_mrp_workorder_show_steps(self):
|
||||
self.ensure_one()
|
||||
if self.bom_id.picking_type_id:
|
||||
picking_type_ids = self.bom_id.picking_type_id.ids
|
||||
else:
|
||||
picking_type_ids = self.env['stock.picking.type'].search([('code', '=', 'mrp_operation')], limit=1).ids
|
||||
action = self.env["ir.actions.actions"]._for_xml_id("mrp_workorder.action_mrp_workorder_show_steps")
|
||||
ctx = {
|
||||
'default_company_id': self.company_id.id,
|
||||
'default_operation_id': self.id,
|
||||
'default_picking_type_ids': picking_type_ids,
|
||||
}
|
||||
action.update({'context': ctx, 'domain': [('operation_id', '=', self.id)]})
|
||||
return action
|
||||
|
||||
def _get_fields_for_tablet(self):
|
||||
""" List of fields on the operation object that are needed by the tablet
|
||||
client action. The purpose of this function is to be overridden in order
|
||||
to inject new fields to the client action.
|
||||
"""
|
||||
return [
|
||||
'worksheet',
|
||||
'id',
|
||||
]
|
||||
|
||||
|
||||
class QualityPoint(models.Model):
|
||||
_inherit = "quality.point"
|
||||
|
||||
def _default_product_ids(self):
|
||||
# Determines a default product from the default operation's BOM.
|
||||
operation_id = self.env.context.get('default_operation_id')
|
||||
if operation_id:
|
||||
bom = self.env['mrp.routing.workcenter'].browse(operation_id).bom_id
|
||||
return bom.product_id.ids if bom.product_id else bom.product_tmpl_id.product_variant_id.ids
|
||||
|
||||
is_workorder_step = fields.Boolean(compute='_compute_is_workorder_step')
|
||||
operation_id = fields.Many2one(
|
||||
'mrp.routing.workcenter', 'Step', check_company=True)
|
||||
bom_id = fields.Many2one(related='operation_id.bom_id')
|
||||
bom_active = fields.Boolean('Related Bill of Material Active', related='bom_id.active')
|
||||
component_ids = fields.One2many('product.product', compute='_compute_component_ids')
|
||||
product_ids = fields.Many2many(
|
||||
default=_default_product_ids,
|
||||
domain="operation_id and [('id', 'in', bom_product_ids)] or [('type', 'in', ('product', 'consu')), '|', ('company_id', '=', False), ('company_id', '=', company_id)]")
|
||||
bom_product_ids = fields.One2many('product.product', compute="_compute_bom_product_ids")
|
||||
test_type_id = fields.Many2one(
|
||||
'quality.point.test_type',
|
||||
domain="[('allow_registration', '=', operation_id and is_workorder_step)]")
|
||||
test_report_type = fields.Selection([('pdf', 'PDF'), ('zpl', 'ZPL')], string="Report Type", default="pdf", required=True)
|
||||
source_document = fields.Selection(
|
||||
selection=[('operation', 'Specific Page of Operation Worksheet'), ('step', 'Custom')],
|
||||
string="Step Document",
|
||||
default='operation')
|
||||
worksheet_page = fields.Integer('Worksheet Page', default=1)
|
||||
worksheet_document = fields.Binary('Image/PDF')
|
||||
worksheet_url = fields.Char('Google doc URL')
|
||||
# Used with type register_consumed_materials the product raw to encode.
|
||||
component_id = fields.Many2one('product.product', 'Product To Register', check_company=True)
|
||||
|
||||
@api.onchange('bom_product_ids', 'is_workorder_step')
|
||||
def _onchange_bom_product_ids(self):
|
||||
if self.is_workorder_step and self.bom_product_ids:
|
||||
self.product_ids = self.product_ids._origin & self.bom_product_ids
|
||||
self.product_category_ids = False
|
||||
|
||||
@api.depends('bom_id.product_id', 'bom_id.product_tmpl_id.product_variant_ids', 'is_workorder_step', 'bom_id')
|
||||
def _compute_bom_product_ids(self):
|
||||
self.bom_product_ids = False
|
||||
points_for_workorder_step = self.filtered(lambda p: p.operation_id and p.bom_id)
|
||||
for point in points_for_workorder_step:
|
||||
bom_product_ids = point.bom_id.product_id or point.bom_id.product_tmpl_id.product_variant_ids
|
||||
point.bom_product_ids = bom_product_ids.filtered(lambda p: not p.company_id or p.company_id == point.company_id._origin)
|
||||
|
||||
@api.depends('product_ids', 'test_type_id', 'is_workorder_step')
|
||||
def _compute_component_ids(self):
|
||||
self.component_ids = False
|
||||
for point in self:
|
||||
if not point.is_workorder_step or not self.bom_id or point.test_type not in ('register_consumed_materials', 'register_byproducts'):
|
||||
point.component_id = None
|
||||
continue
|
||||
if point.test_type == 'register_byproducts':
|
||||
point.component_ids = point.bom_id.byproduct_ids.product_id
|
||||
else:
|
||||
bom_products = point.bom_id.product_id or point.bom_id.product_tmpl_id.product_variant_ids
|
||||
# If product_ids is set the step will exist only for these product variant then we can filter out for the bom explode
|
||||
if point.product_ids:
|
||||
bom_products &= point.product_ids._origin
|
||||
|
||||
component_product_ids = set()
|
||||
for product in bom_products:
|
||||
dummy, lines_done = point.bom_id.explode(product, 1.0)
|
||||
component_product_ids |= {line[0].product_id.id for line in lines_done}
|
||||
point.component_ids = self.env['product.product'].browse(component_product_ids)
|
||||
|
||||
@api.depends('operation_id', 'picking_type_ids')
|
||||
def _compute_is_workorder_step(self):
|
||||
for quality_point in self:
|
||||
quality_point.is_workorder_step = quality_point.picking_type_ids and\
|
||||
all(pt.code == 'mrp_operation' for pt in quality_point.picking_type_ids)
|
||||
|
||||
def _change_product_ids_for_bom(self, bom_id):
|
||||
products = bom_id.product_id or bom_id.product_tmpl_id.product_variant_ids
|
||||
self.product_ids = [Command.set(products.ids)]
|
||||
|
||||
def _get_comparison_values(self):
|
||||
if not self:
|
||||
return False
|
||||
self.ensure_one()
|
||||
return tuple(self[key] for key in ('test_type_id', 'title', 'component_id', 'sequence'))
|
||||
|
||||
@api.onchange('operation_id')
|
||||
def _onchange_operation_id(self):
|
||||
if self.operation_id:
|
||||
self._change_product_ids_for_bom(self.bom_id)
|
||||
|
||||
|
||||
class QualityAlert(models.Model):
|
||||
_inherit = "quality.alert"
|
||||
|
||||
workorder_id = fields.Many2one('mrp.workorder', 'Operation', check_company=True)
|
||||
workcenter_id = fields.Many2one('mrp.workcenter', 'Work Center', check_company=True)
|
||||
production_id = fields.Many2one('mrp.production', "Production Order", check_company=True)
|
||||
|
||||
|
||||
class QualityCheck(models.Model):
|
||||
_inherit = "quality.check"
|
||||
|
||||
workorder_id = fields.Many2one(
|
||||
'mrp.workorder', 'Operation', check_company=True)
|
||||
workcenter_id = fields.Many2one('mrp.workcenter', related='workorder_id.workcenter_id', store=True, readonly=True) # TDE: necessary ?
|
||||
production_id = fields.Many2one(
|
||||
'mrp.production', 'Production Order', check_company=True)
|
||||
|
||||
# doubly linked chain for tablet view navigation
|
||||
next_check_id = fields.Many2one('quality.check')
|
||||
previous_check_id = fields.Many2one('quality.check')
|
||||
|
||||
# For components registration
|
||||
move_id = fields.Many2one(
|
||||
'stock.move', 'Stock Move', check_company=True)
|
||||
move_line_id = fields.Many2one(
|
||||
'stock.move.line', 'Stock Move Line', check_company=True)
|
||||
component_id = fields.Many2one(
|
||||
'product.product', 'Component', check_company=True)
|
||||
component_uom_id = fields.Many2one('uom.uom', related='move_id.product_uom', readonly=True)
|
||||
|
||||
qty_done = fields.Float('Done', digits='Product Unit of Measure')
|
||||
finished_lot_id = fields.Many2one('stock.lot', 'Finished Lot/Serial', related='production_id.lot_producing_id', store=True)
|
||||
additional = fields.Boolean('Register additional product', compute='_compute_additional')
|
||||
component_tracking = fields.Selection(related='component_id.tracking', string="Is Component Tracked")
|
||||
|
||||
# Workorder specific fields
|
||||
component_remaining_qty = fields.Float('Remaining Quantity for Component', compute='_compute_component_data', digits='Product Unit of Measure')
|
||||
component_qty_to_do = fields.Float(compute='_compute_component_qty_to_do')
|
||||
is_user_working = fields.Boolean(related="workorder_id.is_user_working")
|
||||
consumption = fields.Selection(related="workorder_id.consumption")
|
||||
working_state = fields.Selection(related="workorder_id.working_state")
|
||||
is_deleted = fields.Boolean('Deleted in production')
|
||||
|
||||
# Computed fields
|
||||
title = fields.Char('Title', compute='_compute_title')
|
||||
result = fields.Char('Result', compute='_compute_result')
|
||||
|
||||
# Used to group the steps belonging to the same production
|
||||
# We use a float because it is actually filled in by the produced quantity at the step creation.
|
||||
finished_product_sequence = fields.Float('Finished Product Sequence Number')
|
||||
worksheet_document = fields.Binary('Image/PDF')
|
||||
worksheet_page = fields.Integer(related='point_id.worksheet_page')
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, values):
|
||||
points = self.env['quality.point'].search([
|
||||
('id', 'in', [value.get('point_id') for value in values]),
|
||||
('component_id', '!=', False)
|
||||
])
|
||||
for value in values:
|
||||
if not value.get('component_id') and value.get('point_id'):
|
||||
point = points.filtered(lambda p: p.id == value.get('point_id'))
|
||||
if point:
|
||||
value['component_id'] = point.component_id.id
|
||||
return super(QualityCheck, self).create(values)
|
||||
|
||||
@api.depends('test_type_id', 'component_id', 'component_id.name', 'workorder_id', 'workorder_id.name')
|
||||
def _compute_title(self):
|
||||
super()._compute_title()
|
||||
for check in self:
|
||||
if not check.point_id or check.component_id:
|
||||
check.title = '{} "{}"'.format(check.test_type_id.display_name, check.component_id.name or check.workorder_id.name)
|
||||
|
||||
@api.depends('point_id', 'quality_state', 'component_id', 'component_uom_id', 'lot_id', 'qty_done')
|
||||
def _compute_result(self):
|
||||
for check in self:
|
||||
if check.quality_state == 'none':
|
||||
check.result = ''
|
||||
else:
|
||||
check.result = check._get_check_result()
|
||||
|
||||
@api.depends('move_id')
|
||||
def _compute_additional(self):
|
||||
""" The stock_move is linked to additional workorder line only at
|
||||
record_production. So line without move during production are additionnal
|
||||
ones. """
|
||||
for check in self:
|
||||
check.additional = not check.move_id
|
||||
|
||||
@api.depends('qty_done', 'component_remaining_qty')
|
||||
def _compute_component_qty_to_do(self):
|
||||
for wo in self:
|
||||
wo.component_qty_to_do = wo.qty_done - wo.component_remaining_qty
|
||||
|
||||
def _get_check_result(self):
|
||||
if self.test_type in ('register_consumed_materials', 'register_byproducts') and self.lot_id:
|
||||
return '{} - {}, {} {}'.format(self.component_id.name, self.lot_id.name, self.qty_done, self.component_uom_id.name)
|
||||
elif self.test_type in ('register_consumed_materials', 'register_byproducts'):
|
||||
return '{}, {} {}'.format(self.component_id.name, self.qty_done, self.component_uom_id.name)
|
||||
else:
|
||||
return ''
|
||||
|
||||
@api.depends('workorder_id.state', 'quality_state', 'workorder_id.qty_producing',
|
||||
'component_tracking', 'test_type', 'component_id', 'move_line_id.lot_id'
|
||||
)
|
||||
def _compute_component_data(self):
|
||||
self.component_remaining_qty = False
|
||||
self.component_uom_id = False
|
||||
for check in self:
|
||||
if check.test_type in ('register_byproducts', 'register_consumed_materials'):
|
||||
if check.quality_state == 'none':
|
||||
completed_lines = check.workorder_id.move_line_ids.filtered(lambda l: l.lot_id) if check.component_id.tracking != 'none' else check.workorder_id.move_line_ids
|
||||
if check.move_id.additional:
|
||||
qty = check.workorder_id.qty_remaining
|
||||
else:
|
||||
qty = check.workorder_id.qty_producing
|
||||
check.component_remaining_qty = self._prepare_component_quantity(check.move_id, qty) - sum(completed_lines.mapped('qty_done'))
|
||||
check.component_uom_id = check.move_id.product_uom
|
||||
|
||||
def action_print(self):
|
||||
if self.product_id.uom_id.category_id == self.env.ref('uom.product_uom_categ_unit'):
|
||||
qty = int(self.workorder_id.qty_producing)
|
||||
else:
|
||||
qty = 1
|
||||
|
||||
quality_point_id = self.point_id
|
||||
report_type = quality_point_id.test_report_type
|
||||
|
||||
if self.product_id.tracking == 'none':
|
||||
xml_id = 'product.action_open_label_layout'
|
||||
wizard_action = self.env['ir.actions.act_window']._for_xml_id(xml_id)
|
||||
wizard_action['context'] = {'default_product_ids': self.product_id.ids}
|
||||
if report_type == 'zpl':
|
||||
wizard_action['context']['default_print_format'] = 'zpl'
|
||||
res = wizard_action
|
||||
else:
|
||||
if self.workorder_id.finished_lot_id:
|
||||
if report_type == 'zpl':
|
||||
xml_id = 'stock.label_lot_template'
|
||||
else:
|
||||
xml_id = 'stock.action_report_lot_label'
|
||||
res = self.env.ref(xml_id).report_action([self.workorder_id.finished_lot_id.id] * qty)
|
||||
else:
|
||||
raise UserError(_('You did not set a lot/serial number for '
|
||||
'the final product'))
|
||||
|
||||
res['id'] = self.env.ref(xml_id).id
|
||||
|
||||
# The button goes immediately to the next step
|
||||
self._next()
|
||||
return res
|
||||
|
||||
def action_next(self):
|
||||
self.ensure_one()
|
||||
return self._next()
|
||||
|
||||
def action_continue(self):
|
||||
self.ensure_one()
|
||||
self._next(continue_production=True)
|
||||
|
||||
def add_check_in_chain(self, activity=True):
|
||||
self.ensure_one()
|
||||
if self.workorder_id.current_quality_check_id:
|
||||
self._insert_in_chain('after', self.workorder_id.current_quality_check_id)
|
||||
else:
|
||||
self.workorder_id.current_quality_check_id = self
|
||||
if self.workorder_id.production_id.bom_id and activity:
|
||||
body = Markup(_("<b>New Step suggested by %s</b><br/>"
|
||||
"<b>Reason:</b>"
|
||||
"%s", self.env.user.name, self.additional_note
|
||||
))
|
||||
self.env['mail.activity'].sudo().create({
|
||||
'res_model_id': self.env.ref('mrp.model_mrp_bom').id,
|
||||
'res_id': self.workorder_id.production_id.bom_id.id,
|
||||
'user_id': self.workorder_id.product_id.responsible_id.id or SUPERUSER_ID,
|
||||
'activity_type_id': self.env.ref('mail.mail_activity_data_todo').id,
|
||||
'summary': _('BoM feedback %s (%s)', self.title, self.workorder_id.production_id.name),
|
||||
'note': body,
|
||||
})
|
||||
|
||||
@api.model
|
||||
def _prepare_component_quantity(self, move, qty_producing):
|
||||
""" helper that computes quantity to consume (or to create in case of byproduct)
|
||||
depending on the quantity producing and the move's unit factor"""
|
||||
if move.product_id.tracking == 'serial':
|
||||
uom = move.product_id.uom_id
|
||||
else:
|
||||
uom = move.product_uom
|
||||
return move.product_uom._compute_quantity(
|
||||
qty_producing * move.unit_factor,
|
||||
uom,
|
||||
round=False
|
||||
)
|
||||
|
||||
def _create_extra_move_lines(self):
|
||||
"""Create new sml if quantity produced is bigger than the reserved one"""
|
||||
vals_list = []
|
||||
# apply putaway
|
||||
location_dest_id = self.move_id.location_dest_id._get_putaway_strategy(self.move_id.product_id)
|
||||
quants = self.env['stock.quant']._gather(self.product_id, self.move_id.location_id, lot_id=self.lot_id, strict=False)
|
||||
# Search for a sub-locations where the product is available.
|
||||
# Loop on the quants to get the locations. If there is not enough
|
||||
# quantity into stock, we take the move location. Anyway, no
|
||||
# reservation is made, so it is still possible to change it afterwards.
|
||||
shared_vals = {
|
||||
'move_id': self.move_id.id,
|
||||
'product_id': self.move_id.product_id.id,
|
||||
'location_dest_id': location_dest_id.id,
|
||||
'reserved_uom_qty': 0,
|
||||
'product_uom_id': self.move_id.product_uom.id,
|
||||
'lot_id': self.lot_id.id,
|
||||
'company_id': self.move_id.company_id.id,
|
||||
}
|
||||
for quant in quants:
|
||||
vals = shared_vals.copy()
|
||||
quantity = quant.quantity - quant.reserved_quantity
|
||||
quantity = self.product_id.uom_id._compute_quantity(quantity, self.product_uom_id, rounding_method='HALF-UP')
|
||||
rounding = quant.product_uom_id.rounding
|
||||
if (float_compare(quant.quantity, 0, precision_rounding=rounding) <= 0 or
|
||||
float_compare(quantity, 0, precision_rounding=self.product_uom_id.rounding) <= 0):
|
||||
continue
|
||||
vals.update({
|
||||
'location_id': quant.location_id.id,
|
||||
'qty_done': min(quantity, self.qty_done),
|
||||
})
|
||||
|
||||
vals_list.append(vals)
|
||||
self.qty_done -= vals['qty_done']
|
||||
# If all the qty_done is distributed, we can close the loop
|
||||
if float_compare(self.qty_done, 0, precision_rounding=self.product_id.uom_id.rounding) <= 0:
|
||||
break
|
||||
|
||||
if float_compare(self.qty_done, 0, precision_rounding=self.product_id.uom_id.rounding) > 0:
|
||||
vals = shared_vals.copy()
|
||||
vals.update({
|
||||
'location_id': self.move_id.location_id.id,
|
||||
'qty_done': self.qty_done,
|
||||
})
|
||||
|
||||
vals_list.append(vals)
|
||||
return vals_list
|
||||
|
||||
def _next(self, continue_production=False):
|
||||
""" This function:
|
||||
|
||||
- first: fullfill related move line with right lot and validated quantity.
|
||||
- second: Generate new quality check for remaining quantity and link them to the original check.
|
||||
- third: Pass to the next check or return a failure message.
|
||||
"""
|
||||
self.ensure_one()
|
||||
rounding = self.workorder_id.product_uom_id.rounding
|
||||
if float_compare(self.workorder_id.qty_producing, 0, precision_rounding=rounding) <= 0:
|
||||
raise UserError(_('Please ensure the quantity to produce is greater than 0.'))
|
||||
elif self.test_type in ('register_byproducts', 'register_consumed_materials'):
|
||||
# Form validation
|
||||
# in case we use continue production instead of validate button.
|
||||
# We would like to consume 0 and leave lot_id blank to close the consumption
|
||||
rounding = self.component_uom_id.rounding
|
||||
if self.component_tracking != 'none' and not self.lot_id and self.qty_done != 0:
|
||||
raise UserError(_('Please enter a Lot/SN.'))
|
||||
if float_compare(self.qty_done, 0, precision_rounding=rounding) < 0:
|
||||
raise UserError(_('Please enter a positive quantity.'))
|
||||
|
||||
# Write the lot and qty to the move line
|
||||
if self.move_line_id:
|
||||
rounding = self.move_line_id.product_uom_id.rounding
|
||||
if float_compare(self.qty_done, self.move_line_id.reserved_uom_qty, precision_rounding=rounding) >= 0:
|
||||
self.move_line_id.write({
|
||||
'qty_done': self.qty_done,
|
||||
'lot_id': self.lot_id.id,
|
||||
})
|
||||
else:
|
||||
new_qty_reserved = self.move_line_id.reserved_uom_qty - self.qty_done
|
||||
default = {
|
||||
'reserved_uom_qty': new_qty_reserved,
|
||||
'qty_done': 0,
|
||||
}
|
||||
self.move_line_id.copy(default=default)
|
||||
self.move_line_id.with_context(bypass_reservation_update=True).write({
|
||||
'reserved_uom_qty': self.qty_done,
|
||||
'qty_done': self.qty_done,
|
||||
})
|
||||
self.move_line_id.lot_id = self.lot_id
|
||||
else:
|
||||
line = self.env['stock.move.line'].create(self._create_extra_move_lines())
|
||||
self.move_line_id = line[:1]
|
||||
if continue_production:
|
||||
self.workorder_id._create_subsequent_checks()
|
||||
|
||||
if self.test_type == 'picture' and not self.picture:
|
||||
raise UserError(_('Please upload a picture.'))
|
||||
|
||||
if self.quality_state == 'none':
|
||||
self.do_pass()
|
||||
|
||||
self.workorder_id._change_quality_check(position='next')
|
||||
|
||||
def _update_component_quantity(self):
|
||||
if self.component_tracking == 'serial':
|
||||
self._origin.qty_done = self.component_id.uom_id._compute_quantity(1, self.component_uom_id, rounding_method='HALF-UP')
|
||||
return
|
||||
move = self.move_id
|
||||
# Compute the new quantity for the current component
|
||||
rounding = move.product_uom.rounding
|
||||
new_qty = self._prepare_component_quantity(move, self.workorder_id.qty_producing)
|
||||
qty_todo = float_round(new_qty, precision_rounding=rounding)
|
||||
qty_todo = qty_todo - move.quantity_done
|
||||
if self.move_line_id and self.move_line_id.lot_id:
|
||||
qty_todo = min(self.move_line_id.reserved_uom_qty, qty_todo)
|
||||
self.qty_done = qty_todo
|
||||
|
||||
|
||||
def _insert_in_chain(self, position, relative):
|
||||
"""Insert the quality check `self` in a chain of quality checks.
|
||||
|
||||
The chain of quality checks is implicitly given by the `relative` argument,
|
||||
i.e. by following its `previous_check_id` and `next_check_id` fields.
|
||||
|
||||
:param position: Where we need to insert `self` according to `relative`
|
||||
:type position: string
|
||||
:param relative: Where we need to insert `self` in the chain
|
||||
:type relative: A `quality.check` record.
|
||||
"""
|
||||
self.ensure_one()
|
||||
assert position in ['before', 'after']
|
||||
if position == 'before':
|
||||
new_previous = relative.previous_check_id
|
||||
self.next_check_id = relative
|
||||
self.previous_check_id = new_previous
|
||||
new_previous.next_check_id = self
|
||||
relative.previous_check_id = self
|
||||
else:
|
||||
new_next = relative.next_check_id
|
||||
self.next_check_id = new_next
|
||||
self.previous_check_id = relative
|
||||
new_next.previous_check_id = self
|
||||
relative.next_check_id = self
|
||||
|
||||
@api.model
|
||||
def _get_fields_list_for_tablet(self):
|
||||
return [
|
||||
'lot_id',
|
||||
'move_id',
|
||||
'move_line_id',
|
||||
'note',
|
||||
'additional_note',
|
||||
'title',
|
||||
'quality_state',
|
||||
'qty_done',
|
||||
'test_type_id',
|
||||
'test_type',
|
||||
'user_id',
|
||||
'picture',
|
||||
'additional',
|
||||
'worksheet_document',
|
||||
'worksheet_page',
|
||||
'is_deleted',
|
||||
'point_id',
|
||||
]
|
||||
|
||||
def _get_fields_for_tablet(self, sorted_check_list):
|
||||
""" List of fields on the quality check object that are needed by the tablet
|
||||
client action. The purpose of this function is to be overridden in order
|
||||
to inject new fields to the client action.
|
||||
"""
|
||||
if sorted_check_list:
|
||||
self = self.browse(sorted_check_list)
|
||||
values = self.read(self._get_fields_list_for_tablet(), load=False)
|
||||
for check in values:
|
||||
check['worksheet_url'] = self.env['quality.check'].browse(check['id']).point_id.worksheet_url
|
||||
return values
|
||||
@@ -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',
|
||||
}
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@@ -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>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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加工程序')]}"/>
|
||||
@@ -123,9 +126,9 @@
|
||||
attrs="{'invisible': ['|', '|', ('production_state', 'in', ('draft', 'done', 'cancel')), ('working_state', '!=', 'blocked'),('name','=','获取CNC加工程序')]}"/>
|
||||
</xpath>
|
||||
<!-- 隐藏物料清单-->
|
||||
<xpath expr="//page[@name='components']" position="attributes">
|
||||
<attribute name="invisible">1</attribute>
|
||||
</xpath>
|
||||
<!-- <xpath expr="//page[@name='components']" position="attributes">-->
|
||||
<!-- <attribute name="invisible">1</attribute>-->
|
||||
<!-- </xpath>-->
|
||||
<!-- 隐藏物料清单-->
|
||||
|
||||
<field name="production_id" position="after" invisible="0">
|
||||
@@ -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>
|
||||
|
||||
|
||||
12
sf_manufacturing/views/stock_picking_view.xml
Normal file
12
sf_manufacturing/views/stock_picking_view.xml
Normal 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>
|
||||
Reference in New Issue
Block a user