解决冲突

This commit is contained in:
胡尧
2024-09-10 16:16:30 +08:00
45 changed files with 998 additions and 177 deletions

View File

@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models
from odoo import fields, models, api
class MrpProduction(models.Model):
@@ -12,7 +12,8 @@ class MrpProduction(models.Model):
check_ids = fields.One2many('quality.check', 'production_id', string="Checks")
def _split_productions(self, amounts=False, cancel_remaining_qty=False, set_consumed_qty=False):
productions = super()._split_productions(amounts=amounts, cancel_remaining_qty=cancel_remaining_qty, set_consumed_qty=set_consumed_qty)
productions = super()._split_productions(amounts=amounts, cancel_remaining_qty=cancel_remaining_qty,
set_consumed_qty=set_consumed_qty)
backorders = productions[1:]
if not backorders:
return productions
@@ -20,3 +21,4 @@ class MrpProduction(models.Model):
if wo.current_quality_check_id.component_id:
wo.current_quality_check_id._update_component_quantity()
return productions

View File

@@ -5,13 +5,16 @@
<field name="name">mrp.production.tree.inherit.planning</field>
<field name="model">mrp.production</field>
<field name="arch" type="xml">
<tree default_order="date_planned_start asc" decoration-info="state=='confirmed'" decoration-danger="date_planned_start&lt;current_date and state not in ('done','cancel')" decoration-muted="state in ('done','cancel')" string="Manufacturing Orders" name="Production">
<tree default_order="date_planned_start asc" decoration-info="state=='confirmed'"
decoration-danger="date_planned_start&lt;current_date and state not in ('done','cancel')"
decoration-muted="state in ('done','cancel')" string="Manufacturing Orders" name="Production">
<field name="message_needaction" invisible="1"/>
<field name="name"/>
<field name="date_planned_start"/>
<field name="product_id"/>
<field name="product_qty" sum="Total Qty" string="Quantity"/>
<field name="product_uom_id" string="Unit of Measure" options="{'no_open':True,'no_create':True}" groups="uom.group_uom"/>
<field name="product_uom_id" string="Unit of Measure" options="{'no_open':True,'no_create':True}"
groups="uom.group_uom"/>
<field name="reservation_state" string="Availability"/>
<field name="origin"/>
<field name="state"/>
@@ -19,23 +22,23 @@
</field>
</record>
<!-- <record id="mrp_production_form_inherit_planning" model="ir.ui.view">-->
<!-- <field name="name">mrp.production.form_inherit_planning</field>-->
<!-- <field name="model">mrp.production</field>-->
<!-- <field name="inherit_id" ref="mrp.mrp_production_form_view"/>-->
<!-- <field name="arch" type="xml">-->
<!-- <xpath expr="div[hasclass('oe_chatter')]" position="replace">-->
<!-- &lt;!&ndash; 这里放置替换后的内容 &ndash;&gt;-->
<!-- </xpath>-->
<!-- <xpath expr="//notebook" position="after">-->
<!-- <div class="oe_chatter">-->
<!-- <field name="message_follower_ids"/>-->
<!-- <field name="activity_ids"/>-->
<!-- <field name="message_ids"/>-->
<!-- </div>-->
<!-- </xpath>-->
<!-- </field>-->
<!-- </record>-->
<!-- <record id="mrp_production_form_inherit_planning" model="ir.ui.view">-->
<!-- <field name="name">mrp.production.form_inherit_planning</field>-->
<!-- <field name="model">mrp.production</field>-->
<!-- <field name="inherit_id" ref="mrp.mrp_production_form_view"/>-->
<!-- <field name="arch" type="xml">-->
<!-- <xpath expr="div[hasclass('oe_chatter')]" position="replace">-->
<!-- &lt;!&ndash; 这里放置替换后的内容 &ndash;&gt;-->
<!-- </xpath>-->
<!-- <xpath expr="//notebook" position="after">-->
<!-- <div class="oe_chatter">-->
<!-- <field name="message_follower_ids"/>-->
<!-- <field name="activity_ids"/>-->
<!-- <field name="message_ids"/>-->
<!-- </div>-->
<!-- </xpath>-->
<!-- </field>-->
<!-- </record>-->
<record id="mrp_production_view_search_inherit_planning" model="ir.ui.view">
<field name="name">mrp.production.search.view.inherit.planning</field>
@@ -43,7 +46,9 @@
<field name="inherit_id" ref="mrp.view_mrp_production_filter"/>
<field name="arch" type="xml">
<filter name="filter_planned" position="attributes">
<attribute name="domain">[('is_planned', '=', True), ('date_planned_start', '!=', False), ('date_planned_finished', '!=', False)]</attribute>
<attribute name="domain">[('is_planned', '=', True), ('date_planned_start', '!=', False),
('date_planned_finished', '!=', False)]
</attribute>
</filter>
</field>
</record>
@@ -51,17 +56,20 @@
<record id="production_order_unplan_server_action" model="ir.actions.server">
<field name="name">Unplan orders</field>
<field name="model_id" ref="mrp.model_mrp_production"/>
<field name="binding_model_id" ref="mrp.model_mrp_production" />
<field name="binding_model_id" ref="mrp.model_mrp_production"/>
<field name="binding_view_types">list</field>
<field name="state">code</field>
<field name="code">records.button_unplan()</field>
</record>
<record id="mrp.act_product_mrp_production_workcenter" model="ir.actions.act_window">
<field name="domain">[('bom_id', '!=', False), ('bom_id.operation_ids.workcenter_id', '=', active_id), ('date_planned_start', '!=', False), ('date_planned_finished', '!=', False)]</field>
<field name="domain">[('bom_id', '!=', False), ('bom_id.operation_ids.workcenter_id', '=', active_id),
('date_planned_start', '!=', False), ('date_planned_finished', '!=', False)]
</field>
<field name="view_id" ref="mrp_production_tree_view_planning"/>
</record>
<menuitem id="mrp_workorder_menu_planning"
name="Work Orders"
sequence="2"

View File

@@ -12,7 +12,6 @@
</tree>
</field>
</record>
<record model="ir.ui.view" id="mrs_production_process_parameter_form">
<field name="model">sf.production.process.parameter</field>
<field name="arch" type="xml">
@@ -26,19 +25,19 @@
<group>
<group>
<field name="code" readonly="1"/>
<field name="process_id" readonly="1"/>
<field name="process_description" readonly="1"/>
<field name="process_id" attrs="{'readonly': [('code', '!=', False)]}"/>
<field name="process_description" attrs="{'readonly': [('code', '!=', False)]}"/>
<field name="gain_way"/>
</group>
<group>
<field name="processing_day" readonly="1"/>
<field name="travel_day" readonly="1"/>
<field name="processing_mm" readonly="1"/>
<field name="processing_day" attrs="{'readonly': [('code', '!=', False)]}"/>
<field name="travel_day" attrs="{'readonly': [('code', '!=', False)]}"/>
<field name="processing_mm" attrs="{'readonly': [('code', '!=', False)]}"/>
</group>
</group>
<notebook>
<page string="适用材料">
<field name="materials_model_ids" readonly="1"></field>
<field name="materials_model_ids" attrs="{'readonly': [('code', '!=', False)]}"></field>
</page>
</notebook>
</sheet>

View File

@@ -80,10 +80,10 @@
<field name="name">sf.cutter.function.tree</field>
<field name="model">sf.functional.cutting.tool.model</field>
<field name="arch" type="xml">
<tree string="功能刀具类型" create="0" delete="0" edit="1">
<field name="name" string="名称"/>
<field name="code"/>
<field name="remark"/>
<tree string="功能刀具类型" create="0" delete="0" edit="1" editable="bottom">
<field name="name" string="名称" readonly="1"/>
<field name="code" readonly="1"/>
<field name="remark" readonly="1"/>
</tree>
</field>
</record>

View File

@@ -53,7 +53,7 @@ class StatusChange(models.Model):
if not ret.get('error'):
logging.info('接口已经执行=============')
else:
logging.error('工厂加工同步订单状态失败 {}'.format(ret.text))
logging.error('工厂加工同步订单状态失败 {}'.format(ret))
raise UserError('工厂加工同步订单状态失败')
except UserError as e:
logging.error('工厂加工同步订单状态失败 {}'.format(e))

View File

@@ -24,6 +24,7 @@ class SfEquipmentSaintenanceStandards(models.Model):
remark = fields.Char('备注')
maintenance_type = fields.Selection([('保养', '保养'), ("检修", "检修")], string='类型', default='保养')
name = fields.Char(string='名称')
active = fields.Boolean(default=True)
@api.model_create_multi
def create(self, vals_list):

View File

@@ -6,13 +6,14 @@
<field name="name">equipment.maintenance.standards.form</field>
<field name="model">equipment.maintenance.standards</field>
<field name="arch" type="xml">
<form string="设备维保标准">
<form string="设备维保标准" delete="false" duplicate="false">
<sheet>
<group>
<group>
<field name="active" invisible="1"/>
<field name="code" readonly="1" force_save="1"/>
<field name="name" readonly="1" force_save="1"/>
<field name="maintenance_equipment_category_id" required="1" />
<field name="maintenance_equipment_category_id" required="1"/>
<field name="eq_maintenance_ids" invisible='1'/>
<field name="overhaul_ids" invisible='1'/>
@@ -50,7 +51,8 @@
<field name="name">equipment.maintenance.standards.tree</field>
<field name="model">equipment.maintenance.standards</field>
<field name="arch" type="xml">
<tree string="设备维保标准">
<tree string="设备维保标准" delete="false">
<field name="active" invisible="1"/>
<field name="code" readonly="1" force_save="1"/>
<field name="maintenance_type" required="1"/>
<field name="name" required="1"/>

View File

@@ -477,7 +477,7 @@ class Manufacturing_Connect(http.Controller):
logging.info('LocationChange error:%s' % e)
return json.JSONEncoder().encode(res)
@http.route('/AutoDeviceApi/AGVToProduct', type='json', auth='public', methods=['GET', 'POST'], csrf=False,
@http.route('/AutoDeviceApi/AGVToProduct', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
cors="*")
def AGVToProduct(self, **kw):
"""
@@ -549,7 +549,7 @@ class Manufacturing_Connect(http.Controller):
logging.info('AGVToProduct error:%s' % e)
return json.JSONEncoder().encode(res)
@http.route('/AutoDeviceApi/AGVDownProduct', type='json', auth='public', methods=['GET', 'POST'], csrf=False,
@http.route('/AutoDeviceApi/AGVDownProduct', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
cors="*")
def AGVDownProduct(self, **kw):
"""

View File

@@ -779,7 +779,8 @@ class MrpProduction(models.Model):
routing_workcenter = self.env['mrp.routing.workcenter'].sudo().search(
[('name', '=', work.routing_type)])
work.write({'date_planned_start': date_planned_start, 'date_planned_finished': date_planned_end,'duration_expected':routing_workcenter.time_cycle})
work.write({'date_planned_start': date_planned_start, 'date_planned_finished': date_planned_end,
'duration_expected': routing_workcenter.time_cycle})
# 修改标记已完成方法
def button_mark_done1(self):
@@ -1078,14 +1079,9 @@ class MrpProduction(models.Model):
productions.write({'programming_no': self.programming_no, 'is_remanufacture': True})
# productions.procurement_group_id.mrp_production_ids.move_dest_ids.write(
# {'group_id': self.env['procurement.group'].search([('name', '=', sale_order.name)])})
stock_picking = None
pc_picking = self.env['stock.picking'].search(
[('origin', '=', productions.name), ('name', 'ilike', 'WH/PC/')])
stock_picking = pc_picking
int_picking = self.env['stock.picking'].search(
[('origin', '=', productions.name), ('name', 'ilike', 'WH/INT/')])
stock_picking |= int_picking
for pick in stock_picking:
stock_picking_remanufacture = self.env['stock.picking'].search([('origin', '=', productions.name)])
for pick in stock_picking_remanufacture:
if pick.name.startswith('WH/PC/') or pick.name.startswith('WH/INT/'):
if pick.move_ids:
product_type_id = pick.move_ids[0].product_id.categ_id
if product_type_id.name == '坯料':
@@ -1111,7 +1107,6 @@ class MrpProduction(models.Model):
for process_item in scarp_process_parameter_workorder:
if purchase_item.product_id.categ_type == '表面工艺':
if purchase_item.product_id.server_product_process_parameters_id == process_item.surface_technics_parameters_id:
print(purchase_orders.origin.find(productions.name))
if purchase_orders.origin.find(productions.name) == -1:
purchase_orders.origin += ',' + productions.name
if item['is_reprogramming'] is False:

View File

@@ -1,7 +1,9 @@
import datetime
from datetime import timedelta, time
from collections import defaultdict
from odoo import fields, models
from odoo import fields, models, api
from odoo.addons.resource.models.resource import Intervals
from odoo.exceptions import UserError, ValidationError
class ResWorkcenter(models.Model):
@@ -41,14 +43,16 @@ class ResWorkcenter(models.Model):
oee_target = fields.Float(
string='OEE Target', help="Overall Effective Efficiency Target in percentage", default=90, tracking=True)
oee = fields.Float(compute='_compute_oee', help='Overall Equipment Effectiveness, based on the last month', store=True)
oee = fields.Float(compute='_compute_oee', help='Overall Equipment Effectiveness, based on the last month',
store=True)
time_start = fields.Float('Setup Time', tracking=True)
time_stop = fields.Float('Cleanup Time', tracking=True)
costs_hour = fields.Float(string='Cost per hour', help='Hourly processing cost.', default=0.0, tracking=True)
equipment_status = fields.Selection(
[("正常", "正常"), ("故障停机", "故障停机"), ("计划维保", "计划维保"), ("空闲", "空闲"), ("封存(报废)", "封存(报废)")],
[("正常", "正常"), ("故障停机", "故障停机"), ("计划维保", "计划维保"), ("空闲", "空闲"),
("封存(报废)", "封存(报废)")],
string="设备状态", related='equipment_id.state')
# @api.depends('equipment_id')
@@ -127,6 +131,102 @@ class ResWorkcenter(models.Model):
# AGV是否可配送
is_agv_scheduling = fields.Boolean(string="AGV所属区域", tracking=True)
# 生产线优化
available_machine_number = fields.Integer(string="可用机台数量")
single_machine_capacity = fields.Float(string="单台小时产能")
production_line_hour_capacity = fields.Float(string="生产线小时产能", readonly=True,
compute='_compute_production_line_hour_capacity')
effective_working_hours_day = fields.Float(string="日有效工作时长", default=0, readonly=True,
compute='_compute_effective_working_hours_day')
default_capacity = fields.Float(
string='生产线日产能', compute='_compute_production_line_day_capacity', readonly=True)
# 计算生产线日产能
@api.depends('production_line_hour_capacity', 'effective_working_hours_day')
def _compute_production_line_day_capacity(self):
for record in self:
record.default_capacity = round(
record.production_line_hour_capacity * record.effective_working_hours_day, 2)
# 计算日有效工作时长
@api.depends('resource_calendar_id', 'resource_calendar_id.attendance_ids',
'resource_calendar_id.attendance_ids.hour_to', 'resource_calendar_id.attendance_ids.hour_from')
def _compute_effective_working_hours_day(self):
for record in self:
attendance_ids = [p for p in record.resource_calendar_id.attendance_ids if
p.dayofweek == self.get_current_day_of_week(datetime.datetime.now())]
if attendance_ids:
for attendance_id in attendance_ids:
if attendance_id.hour_from and attendance_id.hour_to:
record.effective_working_hours_day += attendance_id.hour_to - attendance_id.hour_from
else:
record.effective_working_hours_day = 0
# 获取传入时间是星期几
def get_current_day_of_week(self, datetime):
day_num = datetime.weekday()
return str(day_num)
# 计算生产线小时产能
@api.depends('single_machine_capacity', 'available_machine_number')
def _compute_production_line_hour_capacity(self):
for record in self:
record.production_line_hour_capacity = round(
record.single_machine_capacity * record.available_machine_number, 2)
# 判断计划开始时间是否在配置的工作中心的工作日历内
def deal_with_workcenter_calendar(self, start_date):
start_date = start_date + timedelta(hours=8) # 转换为北京时间
for record in self:
attendance_ids = [p for p in record.resource_calendar_id.attendance_ids if
p.dayofweek == record.get_current_day_of_week(start_date) and self.is_between_times(
p.hour_from, p.hour_to, start_date)]
return False if not attendance_ids else True
# 判断传入时间是否在配置的工作中心的工作日历内
def is_between_times(self, hour_from, hour_to, start_date):
integer_part, decimal_part = self.get_integer_and_decimal_parts(hour_from)
start_time = time(integer_part, decimal_part)
integer_part, decimal_part = self.get_integer_and_decimal_parts(hour_to)
end_time = time(integer_part, decimal_part)
return start_time <= start_date.time() <= end_time
# 获取整数部分和小数部分
def get_integer_and_decimal_parts(self, value):
integer_part = int(value)
decimal_part = value - integer_part
return int(integer_part), int(decimal_part)
# 处理排程是否超过日产能
def deal_available_default_capacity(self, date_planned):
date_planned_start = date_planned.strftime('%Y-%m-%d')
date_planned_end = date_planned + timedelta(days=1)
date_planned_end = date_planned_end.strftime('%Y-%m-%d')
plan_ids = self.env['sf.production.plan'].sudo().search([('date_planned_start', '>=', date_planned_start),
('date_planned_start', '<',
date_planned_end), ('state', '!=', 'draft')])
if plan_ids:
sum_qty = sum([p.product_qty for p in plan_ids])
if sum_qty >= self.default_capacity:
return False
return True
# 处理排程是否超过小时产能
def deal_available_single_machine_capacity(self, date_planned):
date_planned_start = date_planned.strftime('%Y-%m-%d %H:00:00')
date_planned_end = date_planned + timedelta(hours=1)
date_planned_end = date_planned_end.strftime('%Y-%m-%d %H:00:00')
plan_ids = self.env['sf.production.plan'].sudo().search([('date_planned_start', '>=', date_planned_start),
('date_planned_start', '<',
date_planned_end), ('state', '!=', 'draft')])
if plan_ids:
sum_qty = sum([p.product_qty for p in plan_ids])
if sum_qty >= self.production_line_hour_capacity:
return False
return True
class ResWorkcenterProductivity(models.Model):
_inherit = 'mrp.workcenter.productivity'

View File

@@ -1100,7 +1100,7 @@ class ResMrpWorkOrder(models.Model):
[('barcode', 'ilike', 'VL-SPOC')]).id),
('origin', '=', self.production_id.name)])
if move_out.state != 'done':
move_out.write({'state': 'assigned'})
move_out.write({'state': 'assigned', 'production_id': False})
self.env['stock.move.line'].create(move_out.get_move_line(self.production_id, self))
# move_out._action_assign()

View File

@@ -574,6 +574,7 @@ class StockPicking(models.Model):
('origin', '=', self.origin), ('picking_id', '=', self.id)])
if self.location_id == move_in.location_id and self.location_dest_id == move_in.location_dest_id:
if move_out.origin == move_in.origin:
move_in.write({'production_id': False})
if move_out.picking_id.state != 'done':
raise UserError(
_('该入库单对应的单号为%s的出库单还未完成,不能进行验证操作!' % move_out.picking_id.name))
@@ -659,6 +660,11 @@ class ReStockMove(models.Model):
return move_values
def _get_new_picking_values_Res(self, item, sorted_workorders, rescode):
picking_type_id = self.mapped('picking_type_id').id
if rescode == 'WH/OCOUT/':
picking_type_id = self.env.ref('sf_manufacturing.outcontract_picking_out').id
elif rescode == 'WH/OCIN/':
picking_type_id = self.env.ref('sf_manufacturing.outcontract_picking_in').id
return {
'name': self.env['stock.picking']._get_name_Res(rescode),
'origin': item.name,
@@ -667,7 +673,7 @@ class ReStockMove(models.Model):
'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,
'picking_type_id': picking_type_id,
'location_id': self.mapped('location_id').id,
'location_dest_id': self.mapped('location_dest_id').id,
'state': 'confirmed',

View File

@@ -6,9 +6,9 @@
<field name="model">mrp.production</field>
<field name="inherit_id" ref="mrp.mrp_production_form_view"/>
<field name="arch" type="xml">
<!-- <button name="action_cancel" position="before"> -->
<!-- <button name="button_maintenance_req" type="object" string="维修请求"/> -->
<!-- </button> -->
<!-- <button name="action_cancel" position="before"> -->
<!-- <button name="button_maintenance_req" type="object" string="维修请求"/> -->
<!-- </button> -->
<div name="button_box" position="inside">
<button name="open_maintenance_request_mo" type="object" class="oe_stat_button" icon="fa-wrench"
attrs="{'invisible': [('maintenance_count', '=', 0)]}"
@@ -35,6 +35,49 @@
<field name="message_ids"/>
</div>
</xpath>
<xpath expr="//field[@name='default_capacity'][last()]" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//field[@name='default_capacity'][last()]" position="after">
<label for="default_capacity"/>
<div class="o_row">
<field name="default_capacity" string="产线日产能"/>
</div>
</xpath>
<xpath expr="//field[@name='default_capacity'][last()]" position="before">
<label for="available_machine_number"/>
<div class="o_row">
<field name="available_machine_number"/>
</div>
<label for="single_machine_capacity"/>
<div class="o_row">
<field name="single_machine_capacity"/>
件/小时
</div>
<label for="production_line_hour_capacity"/>
<div class="o_row">
<field name="production_line_hour_capacity"/>
件/小时
</div>
<label for="effective_working_hours_day"/>
<div class="o_row">
<field name="effective_working_hours_day"/>
小时
</div>
</xpath>
<!-- <xpath expr='//group[@name="capacity"]//field[@name="default_capacity"])' position="after">-->
<!-- <group>-->
<!-- <field name="available_machine_number"/>-->
<!-- <field name="single_machine_capacity"/>-->
<!-- <field name="production_line_hour_capacity"/>-->
<!-- <field name="effective_working_hours_day"/>-->
<!-- </group>-->
<!-- </xpath>-->
</field>
</record>
@@ -45,12 +88,12 @@
<field name="arch" type="xml">
<!-- Desktop view -->
<xpath expr='(//field[@name="name"])[1]' position="after">
<field name="equipment_status" />
<field name="equipment_image" />
<field name="equipment_status"/>
<field name="equipment_image"/>
</xpath>
<xpath expr='(//field[@name="name"])[2]' position="after">
<field name="equipment_status" />
<field name="equipment_image" widget="image" />
<field name="equipment_status"/>
<field name="equipment_image" widget="image"/>
</xpath>
<xpath expr='(//a[@name="unblock"])' position="after">
<div class="czyg">绿色:正常,红色:故障,黄色:下线/暂停</div>
@@ -73,11 +116,11 @@
</xpath>
<xpath expr='(//a[@name="unblock"])' position="after">
<!-- <div class="czyg">绿色:正常,红色:故障,黄色:下线/暂停</div>-->
<!-- <div class="czyg">绿色:正常,红色:故障,黄色:下线/暂停</div>-->
</xpath>
</field>
</record>
<!--&#45;&#45;&#45;&#45;&#45;&#45;&#45;&#45;&#45;&#45;&#45;&#45;&#45;&#45;&#45;&#45;-->
<!--&#45;&#45;&#45;&#45;&#45;&#45;&#45;&#45;&#45;&#45;&#45;&#45;&#45;&#45;&#45;&#45;-->
<record id="mrp_workcenter_kanban_inherit1" model="ir.ui.view">
<field name="name">mrp.workcenter.kanban.inherit</field>
<field name="model">mrp.workcenter</field>
@@ -99,7 +142,7 @@
</field>
</record>
<!-- 继承原有的看板视图 -->
<!-- 继承原有的看板视图 -->
<record id="mrp_workcenter_kanban_inherit1" model="ir.ui.view">
<field name="name">mrp.workcenter.kanban.inherit</field>
<field name="model">mrp.workcenter</field>
@@ -391,9 +434,9 @@
<field name="model">mrp.production</field>
<field name="inherit_id" ref="mrp.mrp_production_form_view"/>
<field name="arch" type="xml">
<!-- <button name="action_cancel" position="before"> -->
<!-- <button name="button_maintenance_req" type="object" string="维修请求"/> -->
<!-- </button> -->
<!-- <button name="action_cancel" position="before"> -->
<!-- <button name="button_maintenance_req" type="object" string="维修请求"/> -->
<!-- </button> -->
<div name="button_box" position="inside">
<button name="open_maintenance_request_mo" type="object" class="oe_stat_button" icon="fa-wrench"
attrs="{'invisible': [('maintenance_count', '=', 0)]}"

View File

@@ -37,7 +37,7 @@
<field name="date_planned_start" string="计划开始日期" optional="show"/>
</xpath>
<xpath expr="//field[@name='date_planned_start']" position="before">
<field name="reserved_duration" string="计划预留时间" optional="show"/>
<field name="reserved_duration" string="计划预留时间" optional="hide"/>
</xpath>
<xpath expr="//field[@name='date_planned_finished']" position="replace">
<field name="date_planned_finished" string="计划结束日期" optional="hide"/>

View File

@@ -25,6 +25,7 @@ class ReworkWizard(models.TransientModel):
processing_panel_id = fields.Many2many('sf.processing.panel', string="加工面")
is_reprogramming = fields.Boolean(string='申请重新编程', default=False)
is_reprogramming_readonly = fields.Boolean(string='申请重新编程(只读)', default=False)
is_clamp_measure = fields.Boolean(string='保留装夹测量数据', default=True)
reprogramming_num = fields.Integer('重新编程次数', default=0)
programming_state = fields.Selection(
[('待编程', '待编程'), ('编程中', '编程中'), ('已编程', '已编程'), ('已编程未下发', '已编程未下发'),
@@ -35,6 +36,7 @@ class ReworkWizard(models.TransientModel):
def confirm(self):
if self.routing_type in ['装夹预调', 'CNC加工']:
self.is_clamp_measure = False
self.workorder_id.is_rework = True
self.production_id.write({'detection_result_ids': [(0, 0, {
'rework_reason': self.rework_reason,
@@ -58,19 +60,15 @@ class ReworkWizard(models.TransientModel):
if processing_panels_missing:
processing_panels_str = ','.join(processing_panels_missing)
raise UserError('您还有待处理的检测结果中为%s的加工面未选择' % processing_panels_str)
# processing_panels = set()
# for handle_item in handle_result:
# for dr_panel in self.processing_panel_id:
# if dr_panel.name == handle_item.processing_panel:
# processing_panels.add(dr_panel.name)
# if len(processing_panels) != len(handle_result):
# processing_panels_str = ','.join(processing_panels)
# return UserError(f'您还有待处理的检测结果中为{processing_panels_str}的加工面未选择')
for panel in self.processing_panel_id:
panel_workorder = self.production_id.workorder_ids.filtered(
lambda ap: ap.processing_panel == panel.name and ap.state != 'rework')
if panel_workorder:
panel_workorder.write({'state': 'rework'})
rework_clamp_workorder = max(panel_workorder.filtered(
lambda
rp: rp.processing_panel == panel.name and rp.routing_type == '装夹预调' and rp.state in [
'done', 'rework']))
# panel_workorder.filtered(
# lambda wo: wo.routing_type == '装夹预调').workpiece_delivery_ids.filtered(
# lambda wd: wd.status == '待下发').write({'status': '已取消'})
@@ -93,6 +91,43 @@ class ReworkWizard(models.TransientModel):
self.production_id.detection_result_ids.filtered(
lambda ap1: ap1.processing_panel == panel.name and ap1.handle_result == '待处理').write(
{'handle_result': '已处理'})
new_pre_workorder = self.production_id.workorder_ids.filtered(lambda
p: p.routing_type == '装夹预调' and p.processing_panel == panel.name and p.state not in (
'rework', 'done'))
if new_pre_workorder and rework_clamp_workorder and self.is_clamp_measure is True:
new_pre_workorder.write(
{'X1_axis': rework_clamp_workorder.X1_axis, 'Y1_axis': rework_clamp_workorder.Y1_axis
, 'Z1_axis': rework_clamp_workorder.Z1_axis,
'X2_axis': rework_clamp_workorder.X2_axis
, 'Y2_axis': rework_clamp_workorder.Y2_axis,
'Z2_axis': rework_clamp_workorder.Z2_axis
, 'X3_axis': rework_clamp_workorder.X3_axis,
'Y3_axis': rework_clamp_workorder.Y3_axis
, 'Z3_axis': rework_clamp_workorder.Z3_axis,
'X4_axis': rework_clamp_workorder.X4_axis
, 'Y4_axis': rework_clamp_workorder.Y4_axis,
'Z4_axis': rework_clamp_workorder.Z4_axis
, 'X5_axis': rework_clamp_workorder.X5_axis,
'Y5_axis': rework_clamp_workorder.Y5_axis
, 'Z5_axis': rework_clamp_workorder.Z5_axis,
'X6_axis': rework_clamp_workorder.X6_axis
, 'Y6_axis': rework_clamp_workorder.Y6_axis,
'Z6_axis': rework_clamp_workorder.Z6_axis
, 'X7_axis': rework_clamp_workorder.X7_axis,
'Y7_axis': rework_clamp_workorder.Y7_axis
, 'Z7_axis': rework_clamp_workorder.Z7_axis,
'X8_axis': rework_clamp_workorder.X8_axis
, 'Y8_axis': rework_clamp_workorder.Y8_axis,
'Z8_axis': rework_clamp_workorder.Z8_axis
, 'X9_axis': rework_clamp_workorder.X9_axis,
'Y9_axis': rework_clamp_workorder.Y9_axis
, 'Z9_axis': rework_clamp_workorder.Z9_axis,
'X10_axis': rework_clamp_workorder.X10_axis
, 'Y10_axis': rework_clamp_workorder.Y10_axis,
'Z10_axis': rework_clamp_workorder.Z10_axis
, 'X_deviation_angle': rework_clamp_workorder.X_deviation_angle,
'material_center_point': rework_clamp_workorder.material_center_point
})
if self.is_reprogramming is False:
if self.programming_state in ['已编程', '已下发']:
if self.reprogramming_num >= 1 and self.programming_state == '已编程':
@@ -149,9 +184,7 @@ class ReworkWizard(models.TransientModel):
'cmm_ids': new_cnc_workorder.cmm_ids.sudo()._json_cmm_program(panel.name,
ret),
'cnc_worksheet': cnc_rework.cnc_worksheet})
new_pre_workorder = self.production_id.workorder_ids.filtered(lambda
p: p.routing_type == '装夹预调' and p.processing_panel == panel.name and p.state not in (
'rework', 'done'))
if new_pre_workorder:
pre_rework = max(self.production_id.workorder_ids.filtered(
lambda pr: pr.processing_panel == panel.name and pr.state in (

View File

@@ -14,8 +14,15 @@
<group>
<field name="processing_panel_id" options="{'no_create': True}"
attrs='{"invisible": [("routing_type","=","装夹预调")]}' widget="many2many_tags"/>
</group>
<div attrs='{"invisible": [("routing_type","=","装夹预调")]}'>
<span style='font-weight:bold;'>保留装夹测量数据
<field name="is_clamp_measure" force_save="1"/>
</span>
</div>
<div attrs='{"invisible": [("reprogramming_num","=",0)]}'>
<span style='font-weight:bold;'>
注意: 该制造订单产品已申请重新编程次数为<field
name="reprogramming_num" string=""
readonly="1"
@@ -25,6 +32,7 @@
decoration-success="programming_state == '已下发'"
decoration-warning="programming_state =='编程中'"
decoration-danger="programming_state =='已编程'" readonly="1"/>
</span>
</div>
<div attrs='{"invisible": ["|",("routing_type","in",["装夹预调","CNC加工"]),("programming_state","not in",["已下发"])],"readonly": [("tool_state", "=", "2")]}'>
<span style='font-weight:bold;'>申请重新编程

View File

@@ -1,8 +1,11 @@
# -*- coding: utf-8 -*-
# Part of SmartGo. See LICENSE file for full copyright and licensing details.
import logging
import requests
from odoo import api, fields, models
from odoo.exceptions import ValidationError
from odoo.exceptions import ValidationError, UserError
_logger = logging.getLogger(__name__)
@@ -86,7 +89,7 @@ class ResConfigSettings(models.TransientModel):
_logger.info("同步刀具物料每齿走刀量完成")
except Exception as e:
_logger.info("捕获错误信息:%s" % e)
_logger.info("sf_all_sync error: %s" % e)
raise ValidationError("数据错误导致同步失败,请联系管理员")
@api.model
@@ -144,3 +147,68 @@ class ResConfigSettings(models.TransientModel):
ir_config.set_param("ftp_user", self.ftp_user or "")
ir_config.set_param("ftp_password", self.ftp_password or "")
ir_config.set_param("enable_tool_presetter", self.enable_tool_presetter or False)
def sync_sale_price(self):
self.get_page_all_records(self.sale_order_price_process)
def sale_order_price_process(self, datas):
if not datas:
return
try:
convert_sale_data = map(lambda data:
{'name': data.name, 'order_lines': {
str(i): str(value.price_unit) for i, value in enumerate(data.order_line)
}
},
datas)
config = self.env['res.config.settings'].get_values()
url = config['bfm_url_new'] + '/api/sync/order/price'
json_data = {
'params': {
'data': list(convert_sale_data),
},
}
response = requests.post(url, json=json_data, data=None)
response = response.json()
if not response.get('error'):
result = response.get('result')
for need_change_sale_data in result:
res_order_lines_map = need_change_sale_data.get('order_lines')
if not res_order_lines_map:
continue
need_change_sale_order = self.env['sale.order'].sudo().search([('name', '=', need_change_sale_data.get('name'))])
for index,need_change_sale_order_line in enumerate(need_change_sale_order.order_line):
if not res_order_lines_map.get(str(index)):
continue
order_line = self.env['sale.order.line'].browse(need_change_sale_order_line.id)
new_price = res_order_lines_map.get(str(index))
if order_line:
# 修改单价
order_line.write({'remark': new_price})
else:
logging.error('同步销售订单价格失败 {}'.format(response.text))
raise UserError('同步销售订单价格失败')
except Exception as e:
raise UserError(e)
def get_page_all_records(self, func, page_size=100):
# 获取模型对象
model = self.env['sale.order'].sudo()
# 初始化分页参数
page_number = 1
while True:
# 计算偏移量
offset = (page_number - 1) * page_size
# 获取当前页的数据
records = model.search([], limit=page_size, offset=offset)
# 如果没有更多记录,退出循环
if not records:
break
# 将当前页的数据添加到结果列表
func(records)
# 增加页码
page_number += 1

View File

@@ -2,6 +2,8 @@
import logging
import json
import base64
import traceback
import requests
from odoo import models
from odoo.exceptions import ValidationError
@@ -73,7 +75,8 @@ class MrStaticResourceDataSync(models.Model):
self.env['sf.feed.per.tooth'].sync_feed_per_tooth_yesterday()
_logger.info("同步刀具物料每齿走刀量完成")
except Exception as e:
logging.info("捕获错误信息:%s" % e)
traceback_error = traceback.format_exc()
logging.error("同步静态资源库失败:%s" % traceback_error)
raise ValidationError("数据错误导致同步失败,请联系管理员")
@@ -2759,8 +2762,9 @@ class CuttingToolBasicParameters(models.Model):
if result['status'] == 1:
if 'basic_parameters_integral_tool' in result['cutting_tool_basic_parameters_yesterday_list']:
if result['cutting_tool_basic_parameters_yesterday_list']['basic_parameters_integral_tool']:
basic_parameters_integral_tool_list = json.loads(
result['cutting_tool_basic_parameters_yesterday_list']['basic_parameters_integral_tool'])
cutting_tool_basic_parameters_yesterday_list= result['cutting_tool_basic_parameters_yesterday_list']
basic_parameters_integral_tool_list = cutting_tool_basic_parameters_yesterday_list['basic_parameters_integral_tool']
if basic_parameters_integral_tool_list:
for integral_tool_item in basic_parameters_integral_tool_list:
integral_tool = self.search(

View File

@@ -129,6 +129,21 @@
</div>
</div>
</div>
<div>
<h2>销售订单价格同步</h2>
<div class="row mt16 o_settings_container">
<div class="col-12 col-lg-6 o_setting_box">
<div class="o_setting_left_pane"/>
<div class="o_setting_right_pane">
<div class="col-12 col-lg-6 o_setting_box">
<button type="object" class="oe_highlight" name="sync_sale_price" confirm="确认同步"
string="同步销售订单价格"
/>
</div>
</div>
</div>
</div>
</div>
</xpath>
</field>
</record>

View File

@@ -191,7 +191,7 @@ class sf_production_plan(models.Model):
return num
def do_production_schedule(self):
def do_production_schedule(self, date_planned_start):
"""
排程方法
"""
@@ -199,6 +199,10 @@ class sf_production_plan(models.Model):
if not record.production_line_id:
raise ValidationError("未选择生产线")
else:
is_schedule = self.deal_processing_schedule(date_planned_start)
if not is_schedule:
raise ValidationError("排程失败")
workorder_id_list = record.production_id.workorder_ids.ids
if record.production_id:
if record.production_id.workorder_ids:
@@ -249,6 +253,26 @@ class sf_production_plan(models.Model):
'target': 'current', # 跳转的目标窗口,可以是'current'或'new'
}
# 处理是否可排程
def deal_processing_schedule(self, date_planned_start):
for record in self:
workcenter_ids = record.production_line_id.mrp_workcenter_ids
if not workcenter_ids:
raise UserError('生产线没有配置工作中心')
production_lines = workcenter_ids.filtered(lambda b: "自动生产线" in b.name)
if not production_lines: # 判断是否配置了自动生产线
raise UserError('生产线没有配置自动生产线')
if date_planned_start < datetime.now(): # 判断计划开始时间是否小于当前时间
raise UserError('计划开始时间不能小于当前时间')
if all(not production_line.deal_with_workcenter_calendar(date_planned_start) for production_line in
production_lines): # 判断计划开始时间是否在配置的工作中心的工作日历内
raise UserError('当前计划开始时间不能预约排程,请在工作时间内排程')
if not production_lines.deal_available_default_capacity(date_planned_start): # 判断生产线是否可排程
raise UserError('当前计划开始时间不能预约排程,生产线今日没有可排程的资源')
if not production_lines.deal_available_single_machine_capacity(date_planned_start): # 判断生产线是否可排程
raise UserError('当前计划开始时间不能预约排程,生产线该时间段没有可排程的资源')
return True
def calculate_plan_time_before(self, item, workorder_id_list):
"""
根据CNC工单的时间去计算之前的其他工单的开始结束时间

View File

@@ -291,6 +291,7 @@
<field name="type">ir.actions.act_window</field>
<field name="res_model">mrp.production</field>
<field name="view_mode">tree,form</field>
<field name="domain">[('picking_type_id.active', '=', True)]</field>
</record>
<record model="ir.actions.act_window" id="sale_custom_action">
<!-- 自定义额外的动作 -->

View File

@@ -36,7 +36,7 @@ class Action_Plan_All_Wizard(models.TransientModel):
plan_obj = self.env['sf.production.plan'].browse(plan.id)
plan_obj.production_line_id = self.production_line_id.id
plan.date_planned_start = self.date_planned_start
plan_obj.do_production_schedule()
plan_obj.do_production_schedule(self.date_planned_start)
# plan_obj.state = 'done'
print('处理计划:', plan.id, '完成')

4
sf_stock/__init__.py Normal file
View File

@@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
from . import controllers
from . import models

35
sf_stock/__manifest__.py Normal file
View File

@@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
{
'name': "sf_stock",
'summary': """
处理代发货业务""",
'description': """
Long description of module's purpose
""",
'author': "My Company",
'website': "https://www.yourcompany.com",
# Categories can be used to filter modules in modules listing
# Check https://github.com/odoo/odoo/blob/16.0/odoo/addons/base/data/ir_module_category_data.xml
# for the full list
'category': 'Uncategorized',
'version': '0.1',
# any module necessary for this one to work correctly
'depends': ['sf_sale', 'stock'],
# always loaded
'data': [
# 'security/ir.model.access.csv',
'views/stock_picking.xml',
],
# only loaded in demonstration mode
'demo': [
'demo/demo.xml',
],
'installable': True,
'application': True,
}

View File

@@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
from . import controllers

View File

@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# from odoo import http
# class SfStock(http.Controller):
# @http.route('/sf_stock/sf_stock', auth='public')
# def index(self, **kw):
# return "Hello, world"
# @http.route('/sf_stock/sf_stock/objects', auth='public')
# def list(self, **kw):
# return http.request.render('sf_stock.listing', {
# 'root': '/sf_stock/sf_stock',
# 'objects': http.request.env['sf_stock.sf_stock'].search([]),
# })
# @http.route('/sf_stock/sf_stock/objects/<model("sf_stock.sf_stock"):obj>', auth='public')
# def object(self, obj, **kw):
# return http.request.render('sf_stock.object', {
# 'object': obj
# })

30
sf_stock/demo/demo.xml Normal file
View File

@@ -0,0 +1,30 @@
<odoo>
<data>
<!--
<record id="object0" model="sf_stock.sf_stock">
<field name="name">Object 0</field>
<field name="value">0</field>
</record>
<record id="object1" model="sf_stock.sf_stock">
<field name="name">Object 1</field>
<field name="value">10</field>
</record>
<record id="object2" model="sf_stock.sf_stock">
<field name="name">Object 2</field>
<field name="value">20</field>
</record>
<record id="object3" model="sf_stock.sf_stock">
<field name="name">Object 3</field>
<field name="value">30</field>
</record>
<record id="object4" model="sf_stock.sf_stock">
<field name="name">Object 4</field>
<field name="value">40</field>
</record>
-->
</data>
</odoo>

View File

@@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
from . import stock_picking

View File

@@ -0,0 +1,110 @@
# -*- coding: utf-8 -*-
import json
import requests
from odoo import models, fields, api
from odoo.exceptions import UserError
import logging
from odoo.tools import date_utils
_logger = logging.getLogger(__name__)
class StockPicking(models.Model):
_inherit = 'stock.picking'
cancel_backorder_ids = fields.Boolean(default=False, string='是否取消后置单据')
# 重写验证下发发货到bfm
def button_validate(self):
info = super(StockPicking, self).button_validate()
if self.picking_type_code == 'outgoing':
self.send_to_bfm()
return info
def deal_move_ids(self, send_move_ids, send_move_line_ids):
move_ids = [] # 本次发货单
move_line_ids = [] # 本次发货单行
if send_move_ids:
for item in send_move_ids:
val = {
'name': item.product_id.upload_model_file.display_name,
'quantity_done': item.quantity_done,
'date': date_utils.json_default(item.date) if item.date else None,
'description_picking': item.description_picking,
'date_deadline': date_utils.json_default(item.date_deadline) if item.date_deadline else None,
'product_uom_qty': item.product_uom_qty,
'sequence': item.sequence,
'price_unit': item.price_unit,
'priority': item.priority,
'state': item.state,
}
move_ids.append(val)
for item in send_move_line_ids:
val = {
'qty_done': item.qty_done,
'reserved_qty': item.reserved_qty,
'reserved_uom_qty': item.reserved_uom_qty,
'date': date_utils.json_default(item.date) if item.date else None,
'description_picking': item.description_picking,
'state': item.state,
}
move_line_ids.append(val)
return move_ids, move_line_ids
def deal_send_backorder_id(self, backorder_ids1):
backorder_ids = []
if backorder_ids1:
for item in backorder_ids1:
move_ids, move_line_ids = self.deal_move_ids(item.move_ids, item.move_line_ids)
val = {
'receiverName': item.receiverName,
'name': item.sale_id.default_code,
'send_no': item.name,
'scheduled_date': date_utils.json_default(item.scheduled_date) if item.scheduled_date else None,
'date': date_utils.json_default(item.date) if item.date else None,
'date_deadline': date_utils.json_default(item.date_deadline) if item.date_deadline else None,
'date_done': date_utils.json_default(item.date_done) if item.date_done else None,
'move_ids': move_ids,
'move_line_ids': move_line_ids,
'state': item.state,
'move_type': item.move_type,
}
backorder_ids.append(val)
return backorder_ids
def send_to_bfm(self):
skip_backorder = self.env.context.get('skip_backorder')
# 下发发货到bfm
config = self.env['res.config.settings'].get_values()
move_ids, move_line_ids = self.deal_move_ids(self.move_ids, self.move_line_ids)
data = {
'params': {
'receiverName': self.receiverName,
'priority': self.priority,
'name': self.sale_id.default_code,
'send_no': self.name,
'scheduled_date': date_utils.json_default(self.scheduled_date) if self.scheduled_date else None,
'date': date_utils.json_default(self.date) if self.date else None,
'date_deadline': date_utils.json_default(self.date_deadline) if self.date_deadline else None,
'date_done': date_utils.json_default(self.date_done) if self.date_done else None,
'move_ids': move_ids,
'move_line_ids': move_line_ids,
'state': self.state,
'backorder_id': self.deal_send_backorder_id(self.backorder_id),
'backorder_ids': self.deal_send_backorder_id(self.backorder_ids),
'cancel_backorder_ids': skip_backorder,
'move_type': self.move_type,
},
}
url1 = config['bfm_url_new'] + '/api/stock/deliver_goods'
json_str = json.dumps(data)
print('json_str', json_str)
r = requests.post(url1, json=data, data=None)
if r.status_code == 200:
result = json.loads(r.json()['result'])
if result['code'] != 200:
raise UserError(result['message'] or '工厂发货下发bfm失败')
else:
raise UserError('工厂发货下发bfm失败')

View File

@@ -0,0 +1,2 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_sf_stock_sf_stock,sf_stock.sf_stock,model_sf_stock_sf_stock,base.group_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_sf_stock_sf_stock sf_stock.sf_stock model_sf_stock_sf_stock base.group_user 1 1 1 1

View File

@@ -0,0 +1,5 @@
<odoo>
<data>
</data>
</odoo>

View File

@@ -10,7 +10,7 @@
""",
'category': 'sf',
'website': 'https://www.sf.jikimo.com',
'depends': ['sf_manufacturing'],
'depends': ['sf_manufacturing', 'sf_base'],
'data': [
'security/group_security.xml',
'security/ir.model.access.csv',
@@ -24,6 +24,11 @@
'views/menu_view.xml',
'views/stock.xml',
'data/tool_data.xml',
'wizard/jikimo_bom_wizard.xml',
'views/tool_inventory.xml',
'views/jikimo_bom.xml',
'views/tool_views.xml',
],
'demo': [
],

View File

@@ -8,4 +8,6 @@ from . import fixture_material_search
from . import fixture_enroll
from . import temporary_data_processing_methods
from . import stock
from . import jikimo_bom
from . import tool_inventory
from . import functional_cutting_tool_model

View File

@@ -0,0 +1,6 @@
from odoo import models, fields
class SyncFunctionalCuttingToolModel(models.Model):
_inherit = 'sf.functional.cutting.tool.model'
cutting_tool_type_ids = fields.Many2many('sf.cutting.tool.type', string='刀具物料类型')

View File

@@ -50,7 +50,7 @@ class ToolDatasync(models.Model):
# self.env['sf.real.time.distribution.of.functional.tools'].sudo().sync_enroll_functional_tool_real_time_distribution_all()
# logging.info("功能刀具安全库存每日同步成功")
except Exception as e:
logging.info("捕获错误信息:%s" % e)
logging.info("刀具物料、刀具信息同步失败:%s" % e)
raise ValidationError("数据错误导致同步失败,请联系管理员")
@@ -312,7 +312,7 @@ class FunctionalToolWarning(models.Model):
else:
logging.info('没有注册功能刀具预警信息')
except Exception as e:
logging.info("捕获错误信息:%s" % e)
logging.info("功能刀具预警同步失败:%s" % e)
class StockMoveLine(models.Model):
@@ -373,7 +373,7 @@ class StockMoveLine(models.Model):
else:
logging.info('没有注册功能刀具出入库记录信息')
except Exception as e:
logging.info("捕获错误信息:%s" % e)
logging.info("出入库记录信息同步失败:%s" % e)
class RealTimeDistributionFunctionalTools(models.Model):
@@ -446,4 +446,4 @@ class RealTimeDistributionFunctionalTools(models.Model):
else:
logging.info('没有注册功能刀具出入库记录信息')
except Exception as e:
logging.info("捕获错误信息:%s" % e)
logging.info("实时功能刀具同步失败:%s" % e)

View File

@@ -0,0 +1,102 @@
# -*- coding: utf-8 -*-
from xml import etree
from odoo import models, fields, api, Command
from odoo.exceptions import UserError
from odoo.http import request
class jikimo_bom(models.Model):
_name = 'jikimo.bom'
_description = '功能刀具物料清单'
tool_inventory_id = fields.Many2one('sf.tool.inventory', '功能刀具清单')
tool_name = fields.Char(related="tool_inventory_id.name", string='功能刀具名称')
functional_cutting_tool_model_id = fields.Many2one(related='tool_inventory_id.functional_cutting_tool_model_id',
string='功能刀具类型')
tool_groups_id = fields.Many2one(related='tool_inventory_id.tool_groups_id', string='刀具组')
tool_length = fields.Float(related='tool_inventory_id.tool_length', string='刀具总长(mm)')
diameter = fields.Float(related='tool_inventory_id.diameter', string='直径(mm)')
angle = fields.Float(related='tool_inventory_id.angle', string='R角(mm)')
extension = fields.Float(related='tool_inventory_id.extension', string='伸出长度(mm)')
product_ids = fields.Many2many('product.product', string='产品')
knife_handle_model = fields.Selection(related='tool_inventory_id.knife_handle_model', string='使用刀柄型号')
options = fields.Char('产品清单')
def name_get(self):
result = []
for bom in self:
result.append((bom.id, '功能刀具物料清单'))
return result
def bom_product_domains(self, assembly_options):
self.options = assembly_options
cutting_tool_materials = self.env['sf.cutting.tool.material'].search(
[('name', 'in', assembly_options.split('+'))])
domains = []
for index, option in enumerate(cutting_tool_materials):
domain = ['&', ('cutting_tool_material_id', '=', option.id),
("cutting_tool_type_id", "in",
self.tool_inventory_id.functional_cutting_tool_model_id.cutting_tool_type_ids.ids)]
if option.name == '刀柄':
domain = ['&'] + domain + [
("cutting_tool_taper_shank_model", "=", self.tool_inventory_id.knife_handle_model)]
if option.name == '整体式刀具':
domain = ['&'] + domain + [
'|',
# 刀具直径
('cutting_tool_blade_diameter', '=', self.tool_inventory_id.diameter),
# r角
('cutting_tool_blade_tip_working_size', '=', self.tool_inventory_id.angle)]
if option.name == '刀杆':
domain = ['&'] + domain + [
("cutting_tool_cutter_arbor_diameter", "=", self.tool_inventory_id.diameter)]
if option.name == '刀片':
domain = ['&'] + domain + [
("cutting_tool_blade_tip_circular_arc_radius", "=", self.tool_inventory_id.angle)]
if option.name == '刀盘':
domain = ['&'] + domain + [
("cutting_tool_cutter_head_diameter", "=", self.tool_inventory_id.diameter)]
domains = domains + domain
if index != 0:
domains = ['|'] + domains
# wqwqwe = self.env['product.product'].search(ddd)
# product = self.env['product.product'].search(domain)
# if product:
# products = products + product
return domains
def generate_bill_materials(self, assembly_options):
domains = self.bom_product_domains(assembly_options)
products = self.env['product.product'].search(domains)
if products:
self.product_ids = [Command.set(products.ids)]
# if option.name == '刀盘':
# hilt = self.env['product.product'].search(
# [('cutting_tool_blade_diameter', '=', self.tool_inventory_id.diameter),
# ('cutting_tool_material_id', '=', option.id)])
# self.product_ids = [Command.set(hilt.ids)]k
class jikimo_bom_line(models.Model):
_name = 'jikimo.bom.line'
_description = 'jikimo.bom.line'
name = fields.Char()
class ProductProduct(models.Model):
_inherit = 'product.product'
def search(self, args, offset=0, limit=None, order=None, count=False):
# 你可以在这里修改 `args` 以调整搜索条件
# 例如,添加额外的搜索条件
if self.env.context.get('jikimo_bom_product'):
bom_id = self.env['jikimo.bom'].browse(request.session.get('jikimo_bom_product').get('bom_id'))
if not bom_id.options:
raise UserError('请先选择组装方式')
domains = bom_id.bom_product_domains(bom_id.options)
args = args + domains
return super(ProductProduct, self).search(args, offset=offset, limit=limit, order=order, count=count)

View File

@@ -0,0 +1,34 @@
from odoo import models, fields
from odoo.http import request
class ToolInventory(models.Model):
_inherit = 'sf.tool.inventory'
_description = '功能刀具清单'
knife_handle_model = fields.Selection([('BT30', 'BT30'), ('BT40', 'BT40'), ('BT50', 'BT50'), ('GSK30', 'GSK30'), ('GSK40', 'GSK40'), ('GSK50', 'GSK50')], string='使用刀柄型号')
jikimo_bom_ids = fields.One2many('jikimo.bom','tool_inventory_id', 'bom单')
def bom_mainfest(self):
jikimo_bom_ids = self.mapped('jikimo_bom_ids')
if not jikimo_bom_ids:
self._bom_mainfest()
return self.bom_mainfest()
request.session['jikimo_bom_product'] = {'bom_id': int(self.jikimo_bom_ids)}
# context = dict(self.env.context)
# context.update({'jikimo_bom_product': self.jikimo_bom_ids.options})
# if self.functional_cutting_tool_model_id.cutting_tool_type_ids:
# context.update({'jikimo_bom_product_cutting_tool_type': self.functional_cutting_tool_model_id.cutting_tool_type_ids.ids})
return {
'type': 'ir.actions.act_window',
'name': '刀具组装清单',
'res_model': 'jikimo.bom',
'view_mode': 'form',
'view_id': self.env.ref('sf_tool_management.view_jikimo_bom_form').id,
'res_id': int(self.jikimo_bom_ids),
'target': 'current', # Use 'new' to open in a new window/tab
# {'jikimo_bom_product': self.jikimo_bom_ids.options}
}
# 创建bom单
def _bom_mainfest(self):
self.env['jikimo.bom'].create({'tool_inventory_id':self.id})

View File

@@ -38,3 +38,6 @@ access_sf_fixture_material_search_group_plan_dispatch,sf.fixture.material.search
access_sf_functional_tool_dismantle,sf.functional.tool.dismantle,model_sf_functional_tool_dismantle,base.group_user,1,1,1,0
access_sf_functional_tool_dismantle_group_sf_tool_user,sf.functional.tool.dismantle_group_sf_tool_user,model_sf_functional_tool_dismantle,sf_base.group_sf_tool_user,1,1,1,0
access_sf_functional_tool_dismantle_group_plan_dispatch,sf.functional.tool.dismantle_group_plan_dispatch,model_sf_functional_tool_dismantle,sf_base.group_plan_dispatch,1,0,0,0
access_jikimo_bom,jikimo.bom,model_jikimo_bom,base.group_user,1,1,1,1
access_jikimo_bom_wizard,jikimo.bom.wizard,model_jikimo_bom_wizard,base.group_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
38
39
40
41
42
43

View File

@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record model="ir.actions.act_window" id="view_jikimo_bom_form_act">
<field name="name">bom物料清单</field>
<field name="res_model">jikimo.bom</field>
<field name="view_mode">tree,form</field>
</record>
<record id="view_jikimo_bom_form" model="ir.ui.view">
<field name="name">jikimo.bom.form</field>
<field name="model">jikimo.bom</field>
<field name="arch" type="xml">
<form>
<header>
<button type="action" name="%(action_jikimo_bom_wizard)d"
class="btn btn-info" string="组装方式.." context="{'default_bom_id':id}"
/>
</header>
<sheet>
<group>
<field name="tool_name"/>
<field name="functional_cutting_tool_model_id"/>
<field name="tool_groups_id"/>
<field name="diameter"/>
</group>
<group>
<field name="angle"/>
<field name="tool_length"/>
<field name="extension"/>
<field name="knife_handle_model"/>
</group>
<notebook colspan="4">
<page string="物料清单">
<field name="product_ids" context="{'jikimo_bom_product': True}">
<tree>
<field name="name"/>
<!-- <field name="categ_id"/>-->
<field name="cutting_tool_material_id"/>
<field name="cutting_tool_model_id"/>
<field name="specification_id"/>
<field name="brand_id"/>
<field name="qty_available"/>
</tree>
</field>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_tool_inventory_inherit_tree" model="ir.ui.view">
<field name="name">sf.tool.inventory.inherit.tree</field>
<field name="model">sf.tool.inventory</field>
<field name="inherit_id" ref="sf_base.view_tool_inventory_tree"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='type']" position="before">
<field name="knife_handle_model" />
<button name="bom_mainfest" string="bom清单" type="object" class="btn-link"
icon="fa-refresh" />
</xpath>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<!-- ================================================功能刀具类型================================================ -->
<record id="view_cutter_function_inherit_tree" model="ir.ui.view">
<field name="name">sf.cutter.function.inherit.tree</field>
<field name="model">sf.functional.cutting.tool.model</field>
<field name="inherit_id" ref="sf_base.view_cutter_function_tree"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='name']" position="after">
<field name="cutting_tool_type_ids" widget="many2many_tags"/>
</xpath>
</field>
</record>
<!-- 功能刀具类型搜索 -->
</odoo>

View File

@@ -1 +1,2 @@
from . import wizard
from . import jikimo_bom_wizard

View File

@@ -0,0 +1,28 @@
import logging
from datetime import timedelta, datetime, date
from odoo import fields, models, api
from odoo.exceptions import ValidationError, UserError
class JikimoBomWizard(models.TransientModel):
_name = 'jikimo.bom.wizard'
_description = '组装方式选择'
bom_id = fields.Many2one('jikimo.bom', '刀具组装清单')
assembly_options = fields.Selection([
('刀柄+整体式刀具', '刀柄+整体式刀具'),
('刀柄+刀杆+刀片', '刀柄+刀杆+刀片'),
('刀柄+刀盘+刀片', '刀柄+刀盘+刀片')
], string='组装方式', required=True)
# assembly_options_ids = fields.Many2many('sf.cutting.tool.material', string="组装方式")
is_ok = fields.Boolean('确认上述信息正确无误。')
def submit(self):
if not self.bom_id:
raise UserError('缺少bom信息')
if self.bom_id.tool_inventory_id.functional_cutting_tool_model_id.name == '飞刀' and self.assembly_options == '刀柄+整体式刀具':
raise UserError('飞刀只可选 刀柄+刀杆+刀片 或 刀柄+刀盘+刀片')
if self.bom_id.tool_inventory_id.functional_cutting_tool_model_id.name in['中心钻','合金钻','合金刀','整体刀','倒角刀','丝锥'] and self.assembly_options != '刀柄+整体式刀具':
raise UserError('此功能刀具只可选 刀柄+整体式刀具')
self.bom_id.generate_bill_materials(self.assembly_options)

View File

@@ -0,0 +1,33 @@
<openerp>
<data>
<record id="action_jikimo_bom_wizard" model="ir.actions.act_window">
<field name="name">组装方式..</field>
<field name="res_model">jikimo.bom.wizard</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
<record model="ir.ui.view" id="jikimo_bom_wizard_form_view">
<field name="name">jikimo.bom.wizard.form.view</field>
<field name="model">jikimo.bom.wizard</field>
<field name="arch" type="xml">
<form string="组装方式..">
<group>
<field name="assembly_options"/>
<!-- <field name="factory_no" required="1"/>-->
</group>
<div>
<field name="is_ok"/>
确认上述信息正确无误.
</div>
<footer>
<button string="确认组装方式" name="submit" type="object" class="oe_highlight"
attrs="{'invisible':[('is_ok','=',False)]}"/>
<button string="取消" class="btn btn-secondary" special="cancel"/>
</footer>
</form>
</field>
</record>
</data>
</openerp>

View File

@@ -139,5 +139,5 @@ class MrsShelfLocationDataSync(models.Model):
location_id.product_sn_id = False
except Exception as e:
logging.info("捕获错误信息:%s" % e)
logging.info("库区信息同步失败:%s" % e)
raise ValidationError("数据错误导致同步失败,请联系管理员")