Merge branch develop into feature/增加一键合并下发功能,修正合并下发逻辑

This commit is contained in:
马广威
2023-04-24 14:33:44 +08:00
1075 changed files with 245703 additions and 5606 deletions

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,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()

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,
@@ -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):
"""

View 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

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加工程序')]}"/>
@@ -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>

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>