diff --git a/sf_demand_plan/views/demand_plan.xml b/sf_demand_plan/views/demand_plan.xml index b70e37db..f403e5e6 100644 --- a/sf_demand_plan/views/demand_plan.xml +++ b/sf_demand_plan/views/demand_plan.xml @@ -8,6 +8,8 @@
diff --git a/sf_manufacturing/__manifest__.py b/sf_manufacturing/__manifest__.py index f30c1300..e9ccfef0 100644 --- a/sf_manufacturing/__manifest__.py +++ b/sf_manufacturing/__manifest__.py @@ -31,6 +31,7 @@ 'wizard/sf_programming_reason_views.xml', 'wizard/sale_order_cancel_views.xml', 'wizard/process_outsourcing.xml', + 'wizard/create_technology_design_task_wizard_views.xml', 'views/mrp_views_menus.xml', 'views/agv_scheduling_views.xml', 'views/stock_lot_views.xml', @@ -48,6 +49,8 @@ 'views/mrp_workorder_batch_replan.xml', 'views/purchase_order_view.xml', 'views/product_template_views.xml', + 'views/sf_technology_design_task_views.xml', + 'views/sf_technology_design_task_dashboard.xml', # 'views/stock_warehouse_orderpoint.xml', ], 'assets': { diff --git a/sf_manufacturing/data/stock_data.xml b/sf_manufacturing/data/stock_data.xml index 9220d827..ff7c069a 100644 --- a/sf_manufacturing/data/stock_data.xml +++ b/sf_manufacturing/data/stock_data.xml @@ -36,6 +36,14 @@ 5 + + 工艺设计任务编码规则 + sf.technology.design.task + TD/%(year)s%(month)s%(day)s/ + 4 + + + 外协 diff --git a/sf_manufacturing/models/__init__.py b/sf_manufacturing/models/__init__.py index 8af783e7..9e34c3b8 100644 --- a/sf_manufacturing/models/__init__.py +++ b/sf_manufacturing/models/__init__.py @@ -18,4 +18,5 @@ from . import quick_easy_order from . import purchase_order from . import quality_check from . import purchase_request_line +from . import sf_technology_design_task # from . import stock_warehouse_orderpoint \ No newline at end of file diff --git a/sf_manufacturing/models/sf_technology_design_task.py b/sf_manufacturing/models/sf_technology_design_task.py new file mode 100644 index 00000000..09af404d --- /dev/null +++ b/sf_manufacturing/models/sf_technology_design_task.py @@ -0,0 +1,251 @@ +# -*- coding: utf-8 -*- +from odoo import fields, models, api, _ +from odoo.exceptions import ValidationError + + +class SfTechnologyDesignTask(models.Model): + _name = 'sf.technology.design.task' + _description = '工艺设计任务' + _order = 'create_date desc' + _inherit = ['mail.thread', 'mail.activity.mixin'] + + # 基本信息 + name = fields.Char('任务编号', required=True, copy=False, readonly=True, + default=lambda self: _('New')) + state = fields.Selection([ + ('pending', '待工艺设计'), + ('in_progress', '进行中'), + ('completed', '已完成'), + ('cancelled', '已取消'), + ], string='状态', default='pending', tracking=True) + + # 关联需求计划 + demand_plan_id = fields.Many2one('sf.production.demand.plan', string='需求计划', required=True, ondelete='cascade') + + # 销售订单信息 + sale_order_id = fields.Many2one('sale.order', string='销售订单', compute='_compute_sale_order_info', store=True) + sale_order_line_id = fields.Many2one('sale.order.line', string='销售订单明细', compute='_compute_sale_order_info', store=True) + customer_name = fields.Char('客户名称', compute='_compute_sale_order_info', store=True) + + # 产品信息 + product_id = fields.Many2one('product.product', string='产品名称', compute='_compute_product_info', store=True) + part_name = fields.Char('零件名称', compute='_compute_product_info', store=True) + part_number = fields.Char('零件图号', compute='_compute_product_info', store=True) + model_id = fields.Char('模型ID', compute='_compute_product_info', store=True) + + # 产品类型和文件 + product_type = fields.Selection([ + ('standard', '标准件'), + ('custom', '定制件'), + ('prototype', '样件'), + ], string='零件类型', default='custom', store=True) + model_file = fields.Binary('模型文件', compute='_compute_model_info', store=True) + model_filename = fields.Char('模型文件名', compute='_compute_model_info', store=True) + + # 材料信息 + materials_id = fields.Many2one('sf.production.materials', string='材料及型号', compute='_compute_material_info', store=True) + blank_type = fields.Selection([('圆料', '圆料'), ('方料', '方料')], string='坯料分类', compute='_compute_material_info', store=True) + blank_precision = fields.Selection([('精坯', '精坯'), ('粗坯', '粗坯')], string='坯料类型', compute='_compute_material_info', store=True) + embryo_long = fields.Char('坯料尺寸', compute='_compute_embryo_long', store=True) + + # 加工信息 + machining_precision = fields.Selection([ + ('0.10', '±0.10mm'), + ('0.05', '±0.05mm'), + ('0.03', '±0.03mm'), + ('0.02', '±0.02mm'), + ('0.01', '±0.01mm') + ], string='加工精度', compute='_compute_machining_info', store=True) + machining_panel = fields.Char('加工面', compute='_compute_machining_info', store=True) + + # 订单信息 + product_uom_qty = fields.Float('订单数量', compute='_compute_order_info', store=True) + is_incoming_material = fields.Boolean('客供料', compute='_compute_order_info', store=True) + + # 工艺参数 + clamping_times = fields.Integer('装夹次数', default=1) + single_clamping_duration = fields.Float('单次装夹时长(分钟)', default=30.0) + cnc_processing_duration = fields.Float('CNC加工时长(分钟)', default=0.0) + + # 质量要求 + customer_quality_requirements = fields.Text('客户质量要求') + processing_drawing_2d = fields.Binary('2D加工图纸') + processing_drawing_filename = fields.Char('2D图纸文件名') + + # 时间信息 + create_date = fields.Datetime('创建时间', default=fields.Datetime.now) + start_date = fields.Datetime('开始时间') + complete_date = fields.Datetime('完成时间') + deadline = fields.Datetime('截止时间') + + # 负责人 + assigned_to = fields.Many2one('res.users', string='负责人', tracking=True) + created_by = fields.Many2one('res.users', string='创建人', default=lambda self: self.env.user, readonly=True) + + # 备注 + notes = fields.Text('备注') + + @api.depends('demand_plan_id') + def _compute_sale_order_info(self): + for record in self: + if record.demand_plan_id: + record.sale_order_id = record.demand_plan_id.sale_order_id + record.sale_order_line_id = record.demand_plan_id.sale_order_line_id + record.customer_name = record.demand_plan_id.customer_name + else: + record.sale_order_id = False + record.sale_order_line_id = False + record.customer_name = "" + + @api.depends('demand_plan_id') + def _compute_product_info(self): + for record in self: + if record.demand_plan_id and record.demand_plan_id.product_id: + record.product_id = record.demand_plan_id.product_id + if record.product_id.product_tmpl_id: + record.part_name = record.product_id.product_tmpl_id.part_name + record.part_number = record.product_id.product_tmpl_id.part_number + record.model_id = record.product_id.product_tmpl_id.model_id + else: + record.part_name = "" + record.part_number = "" + record.model_id = "" + else: + record.product_id = False + record.part_name = "" + record.part_number = "" + record.model_id = "" + + @api.depends('product_id') + def _compute_model_info(self): + for record in self: + if record.product_id and record.product_id.product_tmpl_id: + template = record.product_id.product_tmpl_id + record.model_file = template.model_file + record.model_filename = template.model_name + else: + record.model_file = False + record.model_filename = "" + + @api.depends('product_id') + def _compute_material_info(self): + for record in self: + if record.product_id and record.product_id.product_tmpl_id: + template = record.product_id.product_tmpl_id + record.materials_id = template.materials_id + record.blank_type = template.blank_type + record.blank_precision = template.blank_precision + else: + record.materials_id = False + record.blank_type = False + record.blank_precision = False + + @api.depends('product_id') + def _compute_machining_info(self): + for record in self: + if record.product_id and record.product_id.product_tmpl_id: + template = record.product_id.product_tmpl_id + record.machining_precision = template.model_machining_precision + record.machining_panel = template.model_processing_panel + else: + record.machining_precision = False + record.machining_panel = "" + + @api.depends('demand_plan_id') + def _compute_order_info(self): + for record in self: + if record.demand_plan_id: + record.product_uom_qty = record.demand_plan_id.product_uom_qty + record.is_incoming_material = record.demand_plan_id.is_incoming_material + else: + record.product_uom_qty = 0.0 + record.is_incoming_material = False + + @api.depends('product_id') + def _compute_embryo_long(self): + for record in self: + if record.product_id and record.product_id.product_tmpl_id: + template = record.product_id.product_tmpl_id + record.embryo_long = f"{template.model_long}×{template.model_width}×{template.model_height}" + else: + record.embryo_long = "" + + @api.model + def create(self, vals): + if vals.get('name', _('New')) == _('New'): + vals['name'] = self.env['ir.sequence'].next_by_code('sf.technology.design.task') or _('New') + return super(SfTechnologyDesignTask, self).create(vals) + + def action_start_design(self): + """开始工艺设计""" + self.ensure_one() + if self.state != 'pending': + raise ValidationError(_('只有待工艺设计的任务才能开始设计')) + self.write({ + 'state': 'in_progress', + 'start_date': fields.Datetime.now(), + }) + self.message_post(body=_('工艺设计任务已开始')) + + def action_complete_design(self): + """完成工艺设计""" + self.ensure_one() + if self.state != 'in_progress': + raise ValidationError(_('只有进行中的任务才能完成')) + self.write({ + 'state': 'completed', + 'complete_date': fields.Datetime.now(), + }) + self.message_post(body=_('工艺设计任务已完成')) + + def action_cancel_task(self): + """取消任务""" + self.ensure_one() + if self.state in ['completed']: + raise ValidationError(_('已完成的任务不能取消')) + self.write({ + 'state': 'cancelled', + }) + self.message_post(body=_('工艺设计任务已取消')) + + def action_reset_to_pending(self): + """重置为待工艺设计""" + self.ensure_one() + if self.state not in ['in_progress', 'cancelled']: + raise ValidationError(_('只有进行中或已取消的任务才能重置')) + self.write({ + 'state': 'pending', + 'start_date': False, + 'complete_date': False, + }) + self.message_post(body=_('工艺设计任务已重置为待设计状态')) + + @api.model + def create_from_demand_plan(self, demand_plan_ids): + """从需求计划创建工艺设计任务""" + tasks = self.env['sf.technology.design.task'] + for demand_plan in demand_plan_ids: + # 检查是否已存在工艺设计任务 + existing_task = self.search([ + ('demand_plan_id', '=', demand_plan.id), + ('state', 'not in', ['cancelled']) + ], limit=1) + + if not existing_task: + task_vals = { + 'demand_plan_id': demand_plan.id, + 'deadline': fields.Datetime.now() + fields.timedelta(days=3), # 默认3天期限 + } + task = self.create(task_vals) + tasks |= task + + return tasks + + def get_task_count_by_state(self): + """获取各状态的任务数量""" + return { + 'pending': self.search_count([('state', '=', 'pending')]), + 'in_progress': self.search_count([('state', '=', 'in_progress')]), + 'completed': self.search_count([('state', '=', 'completed')]), + 'cancelled': self.search_count([('state', '=', 'cancelled')]), + } \ No newline at end of file diff --git a/sf_manufacturing/security/ir.model.access.csv b/sf_manufacturing/security/ir.model.access.csv index 852aa05f..d8b5f2aa 100644 --- a/sf_manufacturing/security/ir.model.access.csv +++ b/sf_manufacturing/security/ir.model.access.csv @@ -195,4 +195,10 @@ access_sf_work_individuation_page_group_plan_dispatch,sf_work_individuation_page access_sf_sale_order_cancel_wizard,sf_sale_order_cancel_wizard,model_sf_sale_order_cancel_wizard,sf_base.group_sf_order_user,1,1,1,0 access_sf_sale_order_cancel_line,sf_sale_order_cancel_line,model_sf_sale_order_cancel_line,sf_base.group_sf_order_user,1,0,1,1 -access_product_creation_wizard,product_creation_wizard,model_product_creation_wizard,base.group_user,1,1,1,0 \ No newline at end of file +access_product_creation_wizard,product_creation_wizard,model_product_creation_wizard,base.group_user,1,1,1,0 +access_sf_technology_design_task_group_sf_mrp_user,sf_technology_design_task_group_sf_mrp_user,model_sf_technology_design_task,sf_base.group_sf_mrp_user,1,1,1,0 +access_sf_technology_design_task_group_sf_mrp_manager,sf_technology_design_task_group_sf_mrp_manager,model_sf_technology_design_task,sf_base.group_sf_mrp_manager,1,1,1,1 +access_sf_technology_design_task_group_plan_dispatch,sf_technology_design_task_group_plan_dispatch,model_sf_technology_design_task,sf_base.group_plan_dispatch,1,1,1,0 +access_sf_technology_design_task_group_production_engineer,sf_technology_design_task_group_production_engineer,model_sf_technology_design_task,sf_base.group_production_engineer,1,1,1,0 +access_sf_create_technology_design_task_wizard_group_sf_mrp_user,sf_create_technology_design_task_wizard_group_sf_mrp_user,model_sf_create_technology_design_task_wizard,sf_base.group_sf_mrp_user,1,1,1,0 +access_sf_create_technology_design_task_wizard_group_sf_mrp_manager,sf_create_technology_design_task_wizard_group_sf_mrp_manager,model_sf_create_technology_design_task_wizard,sf_base.group_sf_mrp_manager,1,1,1,0 \ No newline at end of file diff --git a/sf_manufacturing/views/sf_technology_design_task_dashboard.xml b/sf_manufacturing/views/sf_technology_design_task_dashboard.xml new file mode 100644 index 00000000..7b52fdfa --- /dev/null +++ b/sf_manufacturing/views/sf_technology_design_task_dashboard.xml @@ -0,0 +1,76 @@ + + + + + sf.technology.design.task.dashboard + sf.technology.design.task + + + + + + + + + + + +
+
+
+
+ + + +
+
+
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+ + + + 工艺设计任务仪表板 + ir.actions.act_window + sf.technology.design.task + kanban + + {'search_default_filter_pending': 1} + +

+ 创建第一个工艺设计任务 +

+

+ 工艺设计任务用于管理产品工艺设计流程,包括从需求计划到工艺完成的整个过程。 +

+
+
+ + + + +
\ No newline at end of file diff --git a/sf_manufacturing/views/sf_technology_design_task_views.xml b/sf_manufacturing/views/sf_technology_design_task_views.xml new file mode 100644 index 00000000..5095f8c8 --- /dev/null +++ b/sf_manufacturing/views/sf_technology_design_task_views.xml @@ -0,0 +1,280 @@ + + + + + sf.technology.design.task.tree + sf.technology.design.task + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + + sf.technology.design.task.search + sf.technology.design.task + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + sf.technology.design.task.form + sf.technology.design.task + +
+
+
+ + +
+

+ +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+
+
+
+ + + + 工艺设计任务 + ir.actions.act_window + sf.technology.design.task + tree,form + {'search_default_filter_pending': 1} + +

+ 创建第一个工艺设计任务 +

+

+ 工艺设计任务用于管理产品工艺设计流程,包括从需求计划到工艺完成的整个过程。 +

+
+
+ + + + + + + sf.technology.design.task.kanban + sf.technology.design.task + + + + + + + + + + +
+
+
+
+ + + +
+
+
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+ +
\ No newline at end of file diff --git a/sf_manufacturing/wizard/__init__.py b/sf_manufacturing/wizard/__init__.py index 43b9bcf7..581299ec 100644 --- a/sf_manufacturing/wizard/__init__.py +++ b/sf_manufacturing/wizard/__init__.py @@ -6,4 +6,5 @@ from . import production_technology_re_adjust_wizard from . import mrp_workorder_batch_replan_wizard from . import sf_programming_reason from . import sale_order_cancel -from . import process_outsourcing \ No newline at end of file +from . import process_outsourcing +from . import create_technology_design_task_wizard \ No newline at end of file diff --git a/sf_manufacturing/wizard/create_technology_design_task_wizard.py b/sf_manufacturing/wizard/create_technology_design_task_wizard.py new file mode 100644 index 00000000..c725011d --- /dev/null +++ b/sf_manufacturing/wizard/create_technology_design_task_wizard.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +from odoo import fields, models, api, _ +from odoo.exceptions import UserError + + +class CreateTechnologyDesignTaskWizard(models.TransientModel): + _name = 'sf.create.technology.design.task.wizard' + _description = '创建工艺设计任务向导' + + demand_plan_ids = fields.Many2many('sf.production.demand.plan', string='需求计划', required=True) + assigned_to = fields.Many2one('res.users', string='负责人', default=lambda self: self.env.user) + deadline = fields.Datetime('截止时间', required=True) + notes = fields.Text('备注') + + @api.model + def default_get(self, fields_list): + res = super(CreateTechnologyDesignTaskWizard, self).default_get(fields_list) + if self.env.context.get('active_model') == 'sf.production.demand.plan': + demand_plan_ids = self.env.context.get('active_ids', []) + res['demand_plan_ids'] = [(6, 0, demand_plan_ids)] + return res + + def action_create_tasks(self): + """创建工艺设计任务""" + if not self.demand_plan_ids: + raise UserError(_('请选择需求计划')) + + # 检查是否已存在工艺设计任务 + existing_tasks = self.env['sf.technology.design.task'].search([ + ('demand_plan_id', 'in', self.demand_plan_ids.ids), + ('state', 'not in', ['cancelled']) + ]) + + if existing_tasks: + raise UserError(_('以下需求计划已存在工艺设计任务:\n%s') % + '\n'.join([task.demand_plan_id.name for task in existing_tasks])) + + # 创建工艺设计任务 + tasks = self.env['sf.technology.design.task'] + for demand_plan in self.demand_plan_ids: + task_vals = { + 'demand_plan_id': demand_plan.id, + 'assigned_to': self.assigned_to.id, + 'deadline': self.deadline, + 'notes': self.notes, + } + task = self.env['sf.technology.design.task'].create(task_vals) + tasks |= task + + # 返回工艺设计任务列表视图 + return { + 'type': 'ir.actions.act_window', + 'name': _('工艺设计任务'), + 'res_model': 'sf.technology.design.task', + 'view_mode': 'tree,form', + 'domain': [('id', 'in', tasks.ids)], + 'context': {'search_default_filter_pending': 1}, + } \ No newline at end of file diff --git a/sf_manufacturing/wizard/create_technology_design_task_wizard_views.xml b/sf_manufacturing/wizard/create_technology_design_task_wizard_views.xml new file mode 100644 index 00000000..8fd432d9 --- /dev/null +++ b/sf_manufacturing/wizard/create_technology_design_task_wizard_views.xml @@ -0,0 +1,29 @@ + + + + sf.create.technology.design.task.wizard.form + sf.create.technology.design.task.wizard + +
+ + + + + + +
+
+
+
+
+ + + 创建工艺设计任务 + ir.actions.act_window + sf.create.technology.design.task.wizard + form + new + +
\ No newline at end of file