Compare commits

..

1 Commits

Author SHA1 Message Date
huziyang@jikimo.com
22f36d095c 跟新 2025-07-18 16:18:09 +08:00
52 changed files with 1124 additions and 1921 deletions

View File

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

View File

@@ -1,18 +0,0 @@
# -*- coding: utf-8 -*-
{
'name': '机企猫 需求计划排程队列',
'version': '1.0',
'summary': """ 使用队列进行排程 """,
'author': 'fox',
'website': '',
'category': '',
'depends': ['queue_job_batch', 'sf_demand_plan'],
'data': [
],
'application': True,
'installable': True,
'auto_install': False,
'license': 'LGPL-3',
}

View File

@@ -1,2 +0,0 @@
# -*- coding: utf-8 -*-
from . import production_demand_plan

View File

@@ -1,20 +0,0 @@
from odoo import models, fields
class ProductionDemandPlan(models.Model):
_inherit = 'sf.production.demand.plan'
def _do_production_schedule(self, pro_plan_list):
"""使用队列进行排程"""
batch_size = 10
current_time = fields.Datetime.now().strftime('%Y%m%d%H%M%S')
index = 1
for i in range(0, len(pro_plan_list), batch_size):
batch = self.env['queue.job.batch'].get_new_batch('plan-%s-%s' % (current_time, index))
pro_plans = pro_plan_list[i:i+batch_size]
pro_plans.with_context(
job_batch=batch
).with_delay().do_production_schedule()
index += 1
batch.enqueue()

View File

@@ -10,7 +10,6 @@
'data': [ 'data': [
'security/ir.model.access.csv', 'security/ir.model.access.csv',
'views/sale_order_view.xml', 'views/sale_order_view.xml',
'views/purchase_order.xml',
'views/mrp_production.xml', 'views/mrp_production.xml',
'views/purchase_request_view.xml', 'views/purchase_request_view.xml',
'wizard/purchase_request_line_make_purchase_order_view.xml', 'wizard/purchase_request_line_make_purchase_order_view.xml',

View File

@@ -16,69 +16,6 @@ class PurchaseOrder(models.Model):
('rejected', '已驳回') ('rejected', '已驳回')
], string='Status', readonly=True, index=True, copy=False, default='draft', tracking=True) ], string='Status', readonly=True, index=True, copy=False, default='draft', tracking=True)
# 成品采购订单对应的坯料采购申请单和采购订单数量
purchase_request_count = fields.Integer('子·采购申请数量', compute='_compute_purchase_request')
purchase_order_count = fields.Integer('子·采购订单数量', compute='_compute_purchase_request')
@api.depends('state')
def _compute_purchase_request(self):
for record in self:
purchase_request_ids, purchase_order_ids = record.get_purchase_request_order()
record.purchase_request_count = len(purchase_request_ids)
record.purchase_order_count = len(purchase_order_ids)
def action_view_preform_body_purchase_request(self):
self.ensure_one()
name_list = self._get_pinking_name()
purchase_request_ids = self.env['purchase.request'].search([('origin', 'in', name_list)])
action = {
'res_model': 'purchase.request',
'type': 'ir.actions.act_window',
}
if len(purchase_request_ids) == 1:
action.update({
'view_mode': 'form',
'res_id': purchase_request_ids[0].id,
})
else:
action.update({
'name': _("子·采购申请"),
'domain': [('id', 'in', purchase_request_ids.ids)],
'view_mode': 'tree,form',
})
return action
def action_view_preform_body_purchase_order(self):
self.ensure_one()
name_list = self._get_pinking_name()
purchase_order_ids = self.env['purchase.order'].search([('origin', 'in', name_list)])
action = {
'res_model': 'purchase.order',
'type': 'ir.actions.act_window',
}
if len(purchase_order_ids) == 1:
action.update({
'view_mode': 'form',
'res_id': purchase_order_ids[0].id,
})
else:
action.update({
'name': _("子·采购订单"),
'domain': [('id', 'in', purchase_order_ids.ids)],
'view_mode': 'tree,form',
})
return action
def get_purchase_request_order(self):
name_list = self._get_pinking_name()
purchase_request_ids = self.env['purchase.request'].search([('origin', 'in', name_list)])
purchase_order_ids = self.env['purchase.order'].search([('origin', 'in', name_list)])
return purchase_request_ids, purchase_order_ids
def _get_pinking_name(self):
return [picking_id.name for picking_id in self.picking_ids if picking_id.name]
def button_confirm(self): def button_confirm(self):
res = super(PurchaseOrder, self).button_confirm() res = super(PurchaseOrder, self).button_confirm()

View File

@@ -16,7 +16,6 @@ class PurchaseRequest(models.Model):
) )
rule_new_add = fields.Boolean('采购请求为规则创建', default=False, compute='_compute_state', store=True) rule_new_add = fields.Boolean('采购请求为规则创建', default=False, compute='_compute_state', store=True)
rule_purchase_to_request = fields.Boolean('采购单根据规则创建坯料采购申请', default=False)
@api.depends('state') @api.depends('state')
def _compute_state(self): def _compute_state(self):

View File

@@ -45,14 +45,3 @@ class StockPicking(models.Model):
(4, x.id) for x in backorder_ids.move_ids if x.product_id.id in purchase_request_lines.mapped('product_id.id') (4, x.id) for x in backorder_ids.move_ids if x.product_id.id in purchase_request_lines.mapped('product_id.id')
] ]
return res return res
def _subcontracted_produce(self, subcontract_details):
super()._subcontracted_produce(subcontract_details)
# 判断是否根据规则生成新的采购申请单据,如果生成则修改状态为 approved
if self:
pr_ids = self.env["purchase.request"].sudo().search(
[('origin', 'like', self.name), ('rule_purchase_to_request', '=', True), ('state', '=', 'draft')])
if pr_ids:
pr_ids.write({'need_validation': False})
pr_ids.write({"state": "approved", 'need_validation': True, 'rule_new_add': False})

View File

@@ -26,7 +26,7 @@ class StockRule(models.Model):
request_data = rule._prepare_purchase_request( request_data = rule._prepare_purchase_request(
procurement.origin, procurement.values procurement.origin, procurement.values
) )
request_data = self._update_request_data(procurement, request_data) request_data.update({'rule_new_add': True})
pr = purchase_request_model.create(request_data) pr = purchase_request_model.create(request_data)
cache[domain] = pr cache[domain] = pr
elif ( elif (
@@ -44,18 +44,6 @@ class StockRule(models.Model):
request_line_data.update({'origin': procurement.origin}) request_line_data.update({'origin': procurement.origin})
purchase_request_line_model.create(request_line_data) purchase_request_line_model.create(request_line_data)
def _update_request_data(self, procurement, request_data):
sp = self.env['stock.picking'].sudo().search([('name', '=', procurement.origin)])
if len(sp) == 1:
po = self.env['purchase.order'].sudo().search(
[('name', '=', sp.origin), ('purchase_type', '=', 'outsourcing')])
if po:
request_data.update({'rule_purchase_to_request': True})
else:
request_data.update({'rule_new_add': True})
return request_data
def _run_buy(self, procurements): def _run_buy(self, procurements):
# 如果补货组相同,并且产品相同,则合并 # 如果补货组相同,并且产品相同,则合并
procurements_dict = defaultdict() procurements_dict = defaultdict()

View File

@@ -1,28 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="purchase_order_form_jikimo_purchase_request" model="ir.ui.view">
<field name="name">purchase.order.inherited.form.jikimo.purchase.request</field>
<field name="model">purchase.order</field>
<field name="inherit_id" ref="mrp_subcontracting_purchase.purchase_order_form_mrp_subcontracting_purchase"/>
<field name="arch" type="xml">
<xpath expr="//div[hasclass('oe_button_box')]/button[@name='action_view_subcontracting_resupply']" position="before">
<button
class="oe_stat_button" name="action_view_preform_body_purchase_order"
type="object" icon="fa-truck" attrs="{'invisible': [('purchase_order_count', '=', 0)]}" groups="stock.group_stock_user">
<div class="o_field_widget o_stat_info">
<span class="o_stat_value"><field name="purchase_order_count"/></span>
<span class="o_stat_text">子·采购订单</span>
</div>
</button>
<button
class="oe_stat_button" name="action_view_preform_body_purchase_request"
type="object" icon="fa-truck" attrs="{'invisible': [('purchase_request_count', '=', 0)]}" groups="stock.group_stock_user">
<div class="o_field_widget o_stat_info">
<span class="o_stat_value"><field name="purchase_request_count"/></span>
<span class="o_stat_text">子·采购申请</span>
</div>
</button>
</xpath>
</field>
</record>
</odoo>

View File

@@ -67,16 +67,6 @@
<field name="part_number"/> <field name="part_number"/>
<field name="part_name" invisible="1"/> <field name="part_name" invisible="1"/>
</xpath> </xpath>
<xpath expr="//tree" position="inside">
<header>
<button
name="%(purchase_request.action_purchase_request_line_make_purchase_order)d"
string="创建询价单"
type="action"
class="btn-primary"
/>
</header>
</xpath>
</field> </field>
</record> </record>

View File

@@ -10,21 +10,11 @@
""", """,
'category': 'sf', 'category': 'sf',
'website': 'https://www.sf.jikimo.com', 'website': 'https://www.sf.jikimo.com',
'depends': ['sf_plan'], 'depends': ['sf_plan', 'jikimo_printing'],
'data': [ 'data': [
'security/ir.model.access.csv', 'security/ir.model.access.csv',
'data/stock_route_group.xml',
<<<<<<< HEAD
'views/sale_order_views.xml',
'views/demand_plan.xml', 'views/demand_plan.xml',
'views/demand_main_plan.xml',
=======
'views/demand_plan.xml',
>>>>>>> develop
'views/stock_route.xml',
'wizard/sf_demand_plan_print_wizard_view.xml', 'wizard/sf_demand_plan_print_wizard_view.xml',
'wizard/sf_release_plan_wizard_views.xml',
'views/menu_view.xml',
], ],
'demo': [ 'demo': [
], ],

View File

@@ -1,21 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<data noupdate="0">
<record id="stock_route_group_automation_sf" model="stock.route.group">
<field name="name">自动化产线加工</field>
<field name="code">automation</field>
</record>
<record id="stock_route_group_manual_sf" model="stock.route.group">
<field name="name">人工线下加工</field>
<field name="code">manual</field>
</record>
<record id="stock_route_group_purchase_sf" model="stock.route.group">
<field name="name">外购</field>
<field name="code">purchase</field>
</record>
<record id="stock_route_group_outsourcing_sf" model="stock.route.group">
<field name="name">委外加工</field>
<field name="code">outsourcing</field>
</record>
</data>
</odoo>

View File

@@ -1,25 +0,0 @@
# migrations/1.1.0/post-migrate.py
import os
import csv
import logging
from odoo import api, SUPERUSER_ID
_logger = logging.getLogger(__name__)
#需求计划模块升级的时候,同步处理存量需求明细数据,关联上新加的需求计划主表
def migrate(cr, version):
# 获取环境
env = api.Environment(cr, SUPERUSER_ID, {})
ProductionLine = env['sf.production.demand.plan']
DemandPlan = env['sf.demand.main.plan']
lines = ProductionLine.search([('demand_plan_id', '=', False)])
for line in lines:
vals = {
'sale_order_id': line.sale_order_id.id,
'sale_order_line_id': line.sale_order_line_id.id,
'line_ids': line.ids
}
new_plan = DemandPlan.create(vals)
line.write({'demand_plan_id': new_plan.id})

View File

@@ -1,11 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from . import mrp_bom
from . import mrp_production
from . import sf_demand_main_plan
from . import sf_production_demand_plan from . import sf_production_demand_plan
from . import sale_order from . import sale_order
from . import stock_route
from . import stock_rule
from . import purchase_request
from . import purchase_order

View File

@@ -1,20 +0,0 @@
from odoo import models, fields
class MrpBom(models.Model):
_inherit = 'mrp.bom'
# 业务平台分配工厂后在智能工厂先创建销售订单再创建该产品后再次进行创建bom
def bom_create(self, product, bom_type, product_type, code=None):
bom_id = self.env['mrp.bom'].create({
'product_tmpl_id': product.product_tmpl_id.id,
'type': bom_type,
# 'subcontractor_id': '' or subcontract.partner_id.id,
'product_qty': 1,
'product_uom_id': 1,
'code': code
})
if bom_type == 'subcontract' and product_type is not False:
subcontract = self.get_supplier(product.materials_type_id)
bom_id.subcontractor_id = subcontract.partner_id.id
return bom_id

View File

@@ -1,26 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models, api
class MrpProduction(models.Model):
_inherit = 'mrp.production'
demand_plan_line_id = fields.Many2one(comodel_name="sf.production.demand.plan",
string="需求计划明细", readonly=True)
@api.depends('demand_plan_line_id')
def _compute_production_type(self):
for production in self:
if production.demand_plan_line_id.supply_method == 'automation':
production.production_type = '自动化产线加工'
elif production.demand_plan_line_id.supply_method == 'manual':
production.production_type = '人工线下加工'
else:
production.production_type = None
def _get_purchase_request(self):
"""获取跟制造订单相关的采购申请单"""
pr_ids = self.env['purchase.request'].sudo().search([('origin', '=', self.name)])
return pr_ids

View File

@@ -1,22 +0,0 @@
from odoo import api, fields, models, _
from odoo.tools import float_compare
class PurchaseOrder(models.Model):
_inherit = 'purchase.order'
demand_plan_line_id = fields.Many2one(comodel_name="sf.production.demand.plan",
string="需求计划明细", readonly=True)
@api.depends('origin', 'demand_plan_line_id')
def _compute_purchase_type(self):
for purchase in self:
if purchase.order_line[0].product_id.categ_id.name == '坯料':
if purchase.order_line[0].product_id.materials_type_id.gain_way == '外协':
purchase.purchase_type = 'outsourcing'
else:
if purchase.demand_plan_line_id.supply_method == 'outsourcing':
purchase.purchase_type = 'outsourcing'
elif purchase.demand_plan_line_id.supply_method == 'purchase':
purchase.purchase_type = 'outside'

View File

@@ -1,110 +0,0 @@
from odoo import models, fields, api, _
from odoo.exceptions import UserError, ValidationError
class PurchaseRequestLine(models.Model):
_inherit = 'purchase.request.line'
_description = '采购申请明细'
supply_method = fields.Selection([
('automation', "自动化产线加工"),
('manual', "人工线下加工"),
('purchase', "外购"),
('outsourcing', "委外加工"),
], string='供货方式', readonly=True)
demand_plan_line_id = fields.Many2one(comodel_name="sf.production.demand.plan",
string="需求计划明细", readonly=True)
@api.depends('demand_plan_line_id')
def _compute_supply_method(self):
for prl in self:
if prl.demand_plan_line_id:
prl.supply_method = prl.demand_plan_line_id.supply_method
else:
prl.supply_method = None
class PurchaseRequestLineMakePurchaseOrder(models.TransientModel):
_inherit = "purchase.request.line.make.purchase.order"
def make_purchase_order(self):
res = []
purchase_obj = self.env["purchase.order"]
po_line_obj = self.env["purchase.order.line"]
purchase = False
if len(set([item_id.line_id.supply_method for item_id in self.item_ids])) > 1:
raise ValidationError('不同供货方式不可合并创建询价单!')
for item in self.item_ids:
line = item.line_id
if item.product_qty <= 0.0:
raise UserError(_("Enter a positive quantity."))
if self.purchase_order_id:
purchase = self.purchase_order_id
if not purchase:
po_data = self._prepare_purchase_order(
line.request_id.picking_type_id,
line.request_id.group_id,
line.company_id,
line.request_id.origin,
)
po_data['demand_plan_line_id'] = item.line_id.demand_plan_line_id.id
# po_data.update({'related_product':line.related_product.id})
purchase = purchase_obj.create(po_data)
# Look for any other PO line in the selected PO with same
# product and UoM to sum quantities instead of creating a new
# po line
domain = self._get_order_line_search_domain(purchase, item)
available_po_lines = po_line_obj.search(domain)
new_pr_line = True
# If Unit of Measure is not set, update from wizard.
if not line.product_uom_id:
line.product_uom_id = item.product_uom_id
# Allocation UoM has to be the same as PR line UoM
alloc_uom = line.product_uom_id
wizard_uom = item.product_uom_id
if available_po_lines and not item.keep_description:
new_pr_line = False
po_line = available_po_lines[0]
po_line.purchase_request_lines = [(4, line.id)]
po_line.move_dest_ids |= line.move_dest_ids
po_line_product_uom_qty = po_line.product_uom._compute_quantity(
po_line.product_uom_qty, alloc_uom
)
wizard_product_uom_qty = wizard_uom._compute_quantity(
item.product_qty, alloc_uom
)
all_qty = min(po_line_product_uom_qty, wizard_product_uom_qty)
self.create_allocation(po_line, line, all_qty, alloc_uom)
else:
po_line_data = self._prepare_purchase_order_line(purchase, item)
if item.keep_description:
po_line_data["name"] = item.name
if line.related_product:
po_line_data.update({'related_product': line.related_product.id})
po_line = po_line_obj.create(po_line_data)
po_line_product_uom_qty = po_line.product_uom._compute_quantity(
po_line.product_uom_qty, alloc_uom
)
wizard_product_uom_qty = wizard_uom._compute_quantity(
item.product_qty, alloc_uom
)
all_qty = min(po_line_product_uom_qty, wizard_product_uom_qty)
self.create_allocation(po_line, line, all_qty, alloc_uom)
self._post_process_po_line(item, po_line, new_pr_line)
res.append(purchase.id)
purchase_requests = self.item_ids.mapped("request_id")
purchase_requests.button_in_progress()
return {
"domain": [("id", "in", res)],
"name": _("RFQ"),
"view_mode": "tree,form",
"res_model": "purchase.order",
"view_id": False,
"context": False,
"type": "ir.actions.act_window",
}

View File

@@ -9,53 +9,14 @@ class ReSaleOrder(models.Model):
compute='_compute_mrp_production_ids', compute='_compute_mrp_production_ids',
string='与此销售订单相关联的制造订单', string='与此销售订单相关联的制造订单',
groups='mrp.group_mrp_user', store=True) groups='mrp.group_mrp_user', store=True)
demand_plan_ids = fields.Many2many(comodel_name="sf.demand.main.plan",
string="需求计划", readonly=True)
demand_plan_count = fields.Integer(
"需求计划生成计数",
compute='_compute_demand_plan_count',
)
#暂时不知道哪里用到了
@api.depends('demand_plan_ids.line_ids.status')
def _compute_purchase_request_count(self):
for record in self:
pr_ids = self.env['purchase.request'].sudo().search([('origin', 'like', record.name)])
if pr_ids:
record.purchase_request_purchase_order_count = len(pr_ids)
else:
record.purchase_request_purchase_order_count = 0
#计算需求计划生成计数
@api.depends('demand_plan_ids.line_ids')
def _compute_demand_plan_count(self):
for line in self:
demand_plan = self.env['sf.production.demand.plan'].sudo().search([('sale_order_id', '=', line.id)])
line.demand_plan_count = len(demand_plan)
def sale_order_create_line(self, product, item): def sale_order_create_line(self, product, item):
ret = super(ReSaleOrder, self).sale_order_create_line(product, item) ret = super(ReSaleOrder, self).sale_order_create_line(product, item)
# vals = { vals = {
# 'sale_order_id': ret.order_id.id,
# 'sale_order_line_id': ret.id,
# }
# demand_plan_info = self.env['sf.demand.main.plan'].sudo().create(vals)
# vals.update({'demand_plan_id': demand_plan_info.id, 'plan_uom_qty': ret.product_uom_qty})
# demand_plan = self.env['sf.production.demand.plan'].sudo().create(vals)
# demand_plan_info.write({'line_ids': demand_plan.ids})
# 优化方案1使用事务确保数据一致性
with self.env.cr.savepoint():
# 1. 先创建主计划
demand_plan_info = self.env['sf.demand.main.plan'].sudo().create({
'sale_order_id': ret.order_id.id, 'sale_order_id': ret.order_id.id,
'sale_order_line_id': ret.id, 'sale_order_line_id': ret.id,
}) }
# 2. 创建明细计划时直接建立关联利用One2many的inverse特性 demand_plan = self.env['sf.production.demand.plan'].sudo().create(vals)
demand_plan = self.env['sf.production.demand.plan'].sudo().create({
'demand_plan_id': demand_plan_info.id,
'plan_uom_qty': ret.product_uom_qty,
'sale_order_id': ret.order_id.id,
'sale_order_line_id': ret.id,
})
# 3. 不需要手动更新line_idsOdoo的ORM会自动处理One2many关系
if demand_plan.product_id.machining_drawings_name: if demand_plan.product_id.machining_drawings_name:
filename_url = demand_plan.product_id.machining_drawings_name.rsplit('.', 1)[0] filename_url = demand_plan.product_id.machining_drawings_name.rsplit('.', 1)[0]
wizard_vals = { wizard_vals = {
@@ -65,23 +26,4 @@ class ReSaleOrder(models.Model):
'type': '1', 'type': '1',
} }
self.env['sf.demand.plan.print.wizard'].sudo().create(wizard_vals) self.env['sf.demand.plan.print.wizard'].sudo().create(wizard_vals)
ret.order_id.demand_plan_ids = [(4, demand_plan_info.id)]
return ret return ret
#从sf工厂调过来时调用了这里重写
def confirm_to_supply_method(self):
self.state = 'sale'
for line in self.order_line:
if line.product_id.auto_machining:
line.supply_method = 'automation'
#在销售订单打开需求计划列表
def action_view_demand_plan(self):
self.ensure_one()
demand_plan_ids = self.env['sf.production.demand.plan'].sudo().search([('sale_order_id', '=', self.id)]).ids
return {
'res_model': 'sf.production.demand.plan',
'type': 'ir.actions.act_window',
'name': _("需求计划"),
'domain': [('id', 'in', demand_plan_ids)],
'view_mode': 'tree',
}

View File

@@ -1,206 +0,0 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api, _
from odoo.tools import float_compare
from odoo.exceptions import ValidationError
class SfDemandMainPlan(models.Model):
_name = 'sf.demand.main.plan'
_description = 'sf_demand_main_plan'
def _get_machining_precision(self):
machinings = self.env['sf.machining.accuracy'].sudo().search([])
list = [(m.sync_id, m.name) for m in machinings]
return list
state = fields.Selection([
('10', '需求确认'),
('20', '待工艺设计'),
('30', '部分下达'),
('40', '已下达'),
('50', '取消'),
], string='状态', default='10', compute='_compute_state', store=True)
line_ids = fields.One2many(comodel_name='sf.production.demand.plan',
inverse_name='demand_plan_id', string="需求计划", copy=True)
sale_order_id = fields.Many2one(comodel_name="sale.order",
string="销售订单", readonly=True)
sale_order_line_id = fields.Many2one(comodel_name="sale.order.line",
string="销售订单明细", readonly=True)
product_id = fields.Many2one(
comodel_name='product.product',
related='sale_order_line_id.product_id',
string='产品', store=True, index=True)
part_name = fields.Char('零件名称', related='product_id.part_name')
part_number = fields.Char('零件图号', compute='_compute_part_number', store=True)
materials_id = fields.Char('材料', compute='_compute_materials_id', store=True)
blank_type = fields.Selection([('圆料', '圆料'), ('方料', '方料')], string='坯料分类',
related='product_id.blank_type')
embryo_long = fields.Char('坯料尺寸(mm)', compute='_compute_embryo_long', store=True)
is_incoming_material = fields.Boolean('客供料', related='sale_order_line_id.is_incoming_material', store=True)
pending_qty = fields.Float(
string="待计划",
compute='_compute_pending_qty', store=True)
planned_qty = fields.Float(
string="已计划",
compute='_compute_planned_qty', store=True)
model_id = fields.Char('模型ID', related='product_id.model_id')
customer_name = fields.Char('客户', related='sale_order_id.customer_name')
product_uom_qty = fields.Float(
string="需求数量",
related='sale_order_line_id.product_uom_qty', store=True)
deadline_of_delivery = fields.Date('客户交期', related='sale_order_line_id.delivery_end_date', store=True)
contract_date = fields.Date('合同日期', related='sale_order_id.contract_date')
contract_code = fields.Char('合同号', related='sale_order_id.contract_code', store=True)
model_process_parameters_ids = fields.Many2many('sf.production.process.parameter',
'demand_plan_process_parameter_rel',
string='表面工艺',
compute='_compute_model_process_parameters_ids'
, store=True
)
model_machining_precision = fields.Selection(selection=_get_machining_precision, string='精度',
related='product_id.model_machining_precision')
inventory_quantity_auto_apply = fields.Float(
string="成品库存",
compute='_compute_inventory_quantity_auto_apply', store=True
)
priority = fields.Selection([
('1', '紧急'),
('2', ''),
('3', ''),
('4', ''),
], string='优先级', default='3')
hide_button_release_plan = fields.Boolean(
string='显示下达计划按钮',
compute='_compute_hide_button_release_plan',
default=False
)
@api.depends('product_id.part_number', 'product_id.model_name')
def _compute_part_number(self):
for line in self:
if line.product_id:
if line.product_id.part_number:
line.part_number = line.product_id.part_number
else:
if line.product_id.model_name:
line.part_number = line.product_id.model_name.rsplit('.', 1)[0]
else:
line.part_number = None
@api.depends('product_id.materials_id')
def _compute_materials_id(self):
for line in self:
if line.product_id:
line.materials_id = f"{line.product_id.materials_id.name}/{line.product_id.materials_type_id.name}"
else:
line.materials_id = None
@api.depends('product_id.model_long', 'product_id.model_width', 'product_id.model_height')
def _compute_embryo_long(self):
for line in self:
if line.product_id:
line.embryo_long = f"{round(line.product_id.model_long, 3)}*{round(line.product_id.model_width, 3)}*{round(line.product_id.model_height, 3)}"
else:
line.embryo_long = None
@api.depends('product_id.model_process_parameters_ids')
def _compute_model_process_parameters_ids(self):
for line in self:
if line.product_id and line.product_id.model_process_parameters_ids:
line.model_process_parameters_ids = [(6, 0, line.product_id.model_process_parameters_ids.ids)]
else:
line.model_process_parameters_ids = [(5, 0, 0)]
def _compute_inventory_quantity_auto_apply(self):
location_id = self.env['stock.location'].search([('name', '=', '成品存货区')], limit=1).id
product_ids = self.mapped('product_id').ids
if product_ids:
quant_data = self.env['stock.quant'].read_group(
domain=[
('product_id', 'in', product_ids),
('location_id', '=', location_id)
],
fields=['product_id', 'inventory_quantity_auto_apply'],
groupby=['product_id']
)
quantity_map = {item['product_id'][0]: item['inventory_quantity_auto_apply'] for item in quant_data}
else:
quantity_map = {}
for line in self:
if line.product_id:
line.inventory_quantity_auto_apply = quantity_map.get(line.product_id.id, 0.0)
else:
line.inventory_quantity_auto_apply = 0.0
@api.depends('product_uom_qty', 'line_ids.plan_uom_qty')
def _compute_pending_qty(self):
for line in self:
sum_plan_uom_qty = sum(line.line_ids.mapped('plan_uom_qty'))
pending_qty = line.product_uom_qty - sum_plan_uom_qty
rounding = line.product_id.uom_id.rounding or 0.01#设置默认精度
# 如果精度小于等于0则设置为0.01
if rounding<=0:
rounding = 0.01
# 如果计划量小于等于0则设置为0
if float_compare(pending_qty, 0, precision_rounding=rounding) == -1:
line.pending_qty = 0
else:
line.pending_qty = pending_qty
@api.depends('line_ids.plan_uom_qty')
def _compute_planned_qty(self):
for line in self:
line.planned_qty = sum(line.line_ids.mapped('plan_uom_qty'))
@api.depends('line_ids.status')
def _compute_hide_button_release_plan(self):
for line in self:
line.hide_button_release_plan = bool(line.line_ids.filtered(lambda p: p.status != '60'))
def button_release_plan(self):
pass
@api.depends('line_ids.status', 'sale_order_id.state')
def _compute_state(self):
for line in self:
#已下达的订单
status_line = line.line_ids.filtered(lambda p: p.status == '60')
if line.sale_order_id.state == 'cancel':
line.state = '50'
line.line_ids.status = '100'
elif len(line.line_ids) == len(status_line):
line.state = '40'
#排除上面全部下达的情形后,如果有已下达订单,则状态为部分
elif bool(status_line):
line.state = '30'
else:
line.state = '10'
def write(self, vals):
res = super(SfDemandMainPlan, self).write(vals)
line_ids = self.line_ids.filtered(lambda p: p.plan_uom_qty == 0 or p.plan_uom_qty < 0)
if line_ids:
raise ValidationError(f"计划量不能小于等于0")
if 'line_ids' in vals:
for line in self.line_ids:
if not line.sale_order_id:
line.sale_order_id = self.sale_order_id
if not line.sale_order_line_id:
line.sale_order_line_id = self.sale_order_line_id
return res
def name_get(self):
result = []
for plan in self:
result.append((plan.id, plan.sale_order_id.name))
return result

File diff suppressed because it is too large Load Diff

View File

@@ -1,53 +0,0 @@
from odoo import models, fields, api, _
class SfStockRoute(models.Model):
_inherit = 'stock.route'
demand_plan_selectable = fields.Boolean("需求计划行")
stock_route_group_ids = fields.Many2many('stock.route.group', 'route_to_group', string='路线组')
demand_plan_ids = fields.Many2many('sf.production.demand.plan', 'stock_route_demand_plan', 'route_id',
'demand_plan_id', '需求计划', copy=False, compute='_compute_demand_plan_ids',
store=True)
@api.depends('demand_plan_selectable', 'stock_route_group_ids')
def _compute_demand_plan_ids(self):
for sr in self:
if sr.demand_plan_selectable:
stock_route_group = [srg.code for srg in sr.stock_route_group_ids]
demand_plan_ids = self.env['sf.production.demand.plan'].sudo().search(
[('supply_method', 'in', stock_route_group)])
if demand_plan_ids:
sr.demand_plan_ids = demand_plan_ids.ids
<<<<<<< HEAD
continue
=======
break
>>>>>>> develop
sr.demand_plan_ids = None
# def name_get(self):
# res = super().name_get()
# if self.env.context.get('demand_plan_search_stock_route_id'):
# demand_plan_id = self.env['sf.production.demand.plan'].sudo().browse(
# int(self.env.context.get('demand_plan_search_stock_route_id')))
# if demand_plan_id and demand_plan_id.supply_method:
# supply_method = self._set_supply_method(demand_plan_id.supply_method)
# res = [(item[0], f'{item[1]}-{supply_method}') for item in res if len(item) == 2]
# return res
#
# def _set_supply_method(self, supply_method):
# return {
# 'automation': "自动化产线加工",
# 'manual': "人工线下加工",
# 'purchase': "外购",
# 'outsourcing': "委外加工"
# }.get(supply_method)
class SfStockRouteGroup(models.Model):
_name = 'stock.route.group'
_description = '路线组'
name = fields.Char('名称')
code = fields.Char('编码')

View File

@@ -1,22 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
class StockRule(models.Model):
_inherit = 'stock.rule'
def _prepare_mo_vals(self, product_id, product_qty, product_uom, location_id, name, origin, company_id, values,
bom):
res = super()._prepare_mo_vals(product_id, product_qty, product_uom, location_id, name, origin, company_id,
values, bom)
if self.env.context.get('demand_plan_line_id'):
res['demand_plan_line_id'] = self.env.context.get('demand_plan_line_id')
return res
@api.model
def _prepare_purchase_request_line(self, request_id, procurement):
res = super()._prepare_purchase_request_line(request_id, procurement)
if self.env.context.get('demand_plan_line_id'):
res['demand_plan_line_id'] = self.env.context.get('demand_plan_line_id')
return res

View File

@@ -1,20 +1,6 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_sf_production_demand_plan,sf.production.demand.plan,model_sf_production_demand_plan,base.group_user,1,0,0,0 access_sf_production_demand_plan,sf.production.demand.plan,model_sf_production_demand_plan,base.group_user,1,0,0,0
access_sf_production_demand_plan_for_dispatch,sf.production.demand.plan for dispatch,model_sf_production_demand_plan,sf_base.group_plan_dispatch,1,1,1,1 access_sf_production_demand_plan_for_dispatch,sf.production.demand.plan for dispatch,model_sf_production_demand_plan,sf_base.group_plan_dispatch,1,1,0,0
access_sf_demand_plan_print_wizard,sf.demand.plan.print.wizard,model_sf_demand_plan_print_wizard,base.group_user,1,0,0,0 access_sf_demand_plan_print_wizard,sf.demand.plan.print.wizard,model_sf_demand_plan_print_wizard,base.group_user,1,0,0,0
access_sf_demand_plan_print_wizard_for_dispatch,sf.demand.plan.print.wizard for dispatch,model_sf_demand_plan_print_wizard,sf_base.group_plan_dispatch,1,1,0,0 access_sf_demand_plan_print_wizard_for_dispatch,sf.demand.plan.print.wizard for dispatch,model_sf_demand_plan_print_wizard,sf_base.group_plan_dispatch,1,1,0,0
<<<<<<< HEAD
access_sf_demand_main_plan,sf.demand.main.plan,model_sf_demand_main_plan,base.group_user,1,0,0,0
access_sf_demand_main_plan_for_dispatch,sf.demand.main.plan for dispatch,model_sf_demand_main_plan,sf_base.group_plan_dispatch,1,1,0,0
access_stock_route_group,stock.route.group,model_stock_route_group,base.group_user,1,0,0,0
access_stock_route_group_dispatch,stock.route.group.dispatch,model_stock_route_group,sf_base.group_plan_dispatch,1,1,0,0
access_sf_release_plan_wizard,sf.release.plan.wizard,model_sf_release_plan_wizard,base.group_user,1,0,0,0
access_sf_release_plan_wizard_for_dispatch,sf.release.plan.wizard for dispatch,model_sf_release_plan_wizard,sf_base.group_plan_dispatch,1,1,1,1
=======
access_stock_route_group,stock.route.group,model_stock_route_group,base.group_user,1,0,0,0
access_stock_route_group_dispatch,stock.route.group.dispatch,model_stock_route_group,sf_base.group_plan_dispatch,1,1,0,0
>>>>>>> develop
1 id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_sf_production_demand_plan,sf.production.demand.plan,model_sf_production_demand_plan,base.group_user,1,0,0,0 access_sf_production_demand_plan sf.production.demand.plan model_sf_production_demand_plan base.group_user 1 0 0 0
3 access_sf_production_demand_plan_for_dispatch,sf.production.demand.plan for dispatch,model_sf_production_demand_plan,sf_base.group_plan_dispatch,1,1,1,1 access_sf_production_demand_plan_for_dispatch sf.production.demand.plan for dispatch model_sf_production_demand_plan sf_base.group_plan_dispatch 1 1 0 0
4 access_sf_demand_plan_print_wizard,sf.demand.plan.print.wizard,model_sf_demand_plan_print_wizard,base.group_user,1,0,0,0 access_sf_demand_plan_print_wizard sf.demand.plan.print.wizard model_sf_demand_plan_print_wizard base.group_user 1 0 0 0
5 access_sf_demand_plan_print_wizard_for_dispatch,sf.demand.plan.print.wizard for dispatch,model_sf_demand_plan_print_wizard,sf_base.group_plan_dispatch,1,1,0,0 access_sf_demand_plan_print_wizard_for_dispatch sf.demand.plan.print.wizard for dispatch model_sf_demand_plan_print_wizard sf_base.group_plan_dispatch 1 1 0 0
6 <<<<<<< HEAD
access_sf_demand_main_plan,sf.demand.main.plan,model_sf_demand_main_plan,base.group_user,1,0,0,0
access_sf_demand_main_plan_for_dispatch,sf.demand.main.plan for dispatch,model_sf_demand_main_plan,sf_base.group_plan_dispatch,1,1,0,0
access_stock_route_group,stock.route.group,model_stock_route_group,base.group_user,1,0,0,0
access_stock_route_group_dispatch,stock.route.group.dispatch,model_stock_route_group,sf_base.group_plan_dispatch,1,1,0,0
access_sf_release_plan_wizard,sf.release.plan.wizard,model_sf_release_plan_wizard,base.group_user,1,0,0,0
access_sf_release_plan_wizard_for_dispatch,sf.release.plan.wizard for dispatch,model_sf_release_plan_wizard,sf_base.group_plan_dispatch,1,1,1,1
=======
access_stock_route_group,stock.route.group,model_stock_route_group,base.group_user,1,0,0,0
access_stock_route_group_dispatch,stock.route.group.dispatch,model_stock_route_group,sf_base.group_plan_dispatch,1,1,0,0
>>>>>>> develop

View File

@@ -1,70 +0,0 @@
<odoo>
<record id="view_sf_demand_main_plan_form" model="ir.ui.view">
<field name="name">sf.demand.main.plan.form</field>
<field name="model">sf.demand.main.plan</field>
<field name="arch" type="xml">
<form>
<header>
<field name="hide_button_release_plan" invisible="1"/>
<!-- <button string="下达计划" name="button_release_plan" type="object"-->
<!-- class="btn-primary" attrs="{'invisible': [('hide_button_release_plan', '=', False)]}"/>-->
<field name="state" widget="statusbar"/>
</header>
<sheet>
<group>
<group>
<field name="product_id"/>
<field name="part_name"/>
<field name="part_number"/>
<field name="materials_id"/>
<field name="blank_type"/>
<field name="embryo_long"/>
<field name="is_incoming_material"/>
<field name="pending_qty"/>
<field name="planned_qty"/>
<field name="model_id"/>
</group>
<group>
<field name="customer_name"/>
<field name="product_uom_qty"/>
<field name="deadline_of_delivery"/>
<field name="contract_date"/>
<field name="contract_code"/>
<field name="model_process_parameters_ids" widget="many2many_tags"/>
<field name="model_machining_precision"/>
<field name="inventory_quantity_auto_apply"/>
<field name="priority" attrs="{'readonly': [('state', 'in', ('40','50'))]}"/>
</group>
</group>
<notebook>
<page string="计划">
<field name="line_ids" attrs="{'readonly': [('state', 'in', ('40','50'))]}">
<tree editable="bottom" delete="false">
<field name="status"/>
<field name="supply_method" attrs="{'readonly': [('status', '!=', '30')]}"/>
<field name="route_ids" widget="many2many_tags" optional="hide"/>
<field name="location_id" optional="hide"/>
<field name="bom_id" optional="hide"/>
<field name="plan_uom_qty"/>
<field name="blank_arrival_date"/>
<field name="finished_product_arrival_date"/>
<field name="planned_start_date"/>
<field name="actual_start_date"/>
<field name="actual_end_date"/>
<field name="plan_remark"/>
<field name="procurement_reason"/>
<field name="write_date" string="更新时间"/>
<field name="hide_release_production_order" invisible="1"/>
<button string="下达计划" name="button_release_plan" type="object" class="btn-primary" attrs="{'invisible': [('status', 'in', ('50','60','100'))]}" />
<button name="button_release_production" type="object" string="下发生产" class="btn-primary" attrs="{'invisible': [('hide_release_production_order', '=', False)]}" />
<button name="button_delete" type="object" string="删除" class="btn-primary" attrs="{'invisible': [('status', 'not in', ('10','20','30'))]}" confirm='是否确认删除?'/>
</tree>
</field>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
</odoo>

View File

@@ -4,10 +4,12 @@
<field name="model">sf.production.demand.plan</field> <field name="model">sf.production.demand.plan</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<tree string="需求计划" default_order="sequence desc,id desc" editable="bottom" <tree string="需求计划" default_order="sequence desc,id desc" editable="bottom"
class="demand_plan_tree freeze-columns-before-part_number" create="false" delete="false"> class="demand_plan_tree freeze-columns-before-part_number">
<header> <header>
<button string="打印" name="button_action_print" type="object" <button string="打印" name="button_action_print" type="object"
class="btn-primary"/> class="btn-primary"/>
<button string="创建工艺设计任务" name="%(sf_manufacturing.action_create_technology_design_task_wizard)d"
type="action" class="btn-secondary" context="{'active_model': 'sf.production.demand.plan', 'active_ids': active_ids}"/>
</header> </header>
<field name="sequence" widget="handle"/> <field name="sequence" widget="handle"/>
<field name="id" optional="hide"/> <field name="id" optional="hide"/>
@@ -20,7 +22,7 @@
<field name="part_name"/> <field name="part_name"/>
<field name="part_number"/> <field name="part_number"/>
<field name="is_incoming_material"/> <field name="is_incoming_material"/>
<field name="supply_method" attrs="{'readonly': [('status', '!=', '30')]}"/> <field name="supply_method"/>
<field name="product_uom_qty"/> <field name="product_uom_qty"/>
<field name="deadline_of_delivery"/> <field name="deadline_of_delivery"/>
<field name="inventory_quantity_auto_apply"/> <field name="inventory_quantity_auto_apply"/>
@@ -30,35 +32,39 @@
<field name="blank_type" optional="hide"/> <field name="blank_type" optional="hide"/>
<field name="blank_precision"/> <field name="blank_precision"/>
<field name="embryo_long"/> <field name="embryo_long"/>
<field name="materials_id"/>
<field name="model_machining_precision"/>
<field name="model_process_parameters_ids" widget="many2many_tags"/>
<field name="product_remark" optional="hide"/> <field name="product_remark" optional="hide"/>
<field name="order_code" optional="hide"/> <field name="order_code" optional="hide"/>
<field name="sale_order_id" optional="hide"/> <field name="sale_order_id" optional="hide"/>
<field name="sale_order_line_number" optional="hide"/> <field name="sale_order_line_number" optional="hide"/>
<field name="order_state"/> <field name="order_state"/>
<field name="route_id" optional="hide"/>
<field name="contract_date"/> <field name="contract_date"/>
<field name="date_order"/> <field name="date_order"/>
<field name="contract_code"/> <field name="contract_code"/>
<field name="plan_remark" attrs="{'readonly': [('status', 'in', ('60','100'))]}"/> <field name="plan_remark"/>
<field name="priority" decoration-danger="priority == '1'" <field name="priority" decoration-danger="priority == '1'"
decoration-warning="priority == '2'" decoration-warning="priority == '2'"
decoration-info="priority == '3'" decoration-info="priority == '3'"
decoration-success="priority == '4'"/> decoration-success="priority == '4'"/>
<field name="material_check" optional="hide"/> <field name="material_check" optional="hide"/>
<!-- <field name="hide_action_open_mrp_production" invisible="1"/>--> <field name="hide_action_open_mrp_production" invisible="1"/>
<!-- <field name="hide_action_purchase_orders" invisible="1"/>--> <field name="hide_action_purchase_orders" invisible="1"/>
<!-- <field name="hide_action_stock_picking" invisible="1"/>--> <field name="hide_action_stock_picking" invisible="1"/>
<!-- <field name="hide_action_view_programming" invisible="1"/>--> <field name="hide_action_view_programming" invisible="1"/>
<!-- <button name="action_open_sale_order" type="object" string="供货方式待确认" class="btn-secondary"--> <button name="action_open_sale_order" type="object" string="供货方式待确认" class="btn-secondary"
<!-- attrs="{'invisible': [('supply_method', '!=', False)]}"/>--> attrs="{'invisible': [('supply_method', '!=', False)]}"/>
<!-- <button name="action_open_mrp_production" type="object" string="待工艺确认" class="btn-secondary"--> <button name="action_open_mrp_production" type="object" string="待工艺确认" class="btn-secondary"
<!-- attrs="{'invisible': [('hide_action_open_mrp_production', '=', False)]}"/>--> attrs="{'invisible': [('hide_action_open_mrp_production', '=', False)]}"/>
<!-- <button name="action_view_purchase_request" type="object" string="采购申请" class="btn-secondary"--> <button name="action_view_purchase_request" type="object" string="采购申请" class="btn-secondary"
<!-- attrs="{'invisible': [('hide_action_purchase_orders', '=', False)]}"/>--> attrs="{'invisible': [('hide_action_purchase_orders', '=', False)]}"/>
<!-- <button name="action_view_stock_picking" type="object" string="调拨单" class="btn-secondary"--> <button name="action_view_stock_picking" type="object" string="调拨单" class="btn-secondary"
<!-- attrs="{'invisible': [('hide_action_stock_picking', '=', False)]}"/>--> attrs="{'invisible': [('hide_action_stock_picking', '=', False)]}"/>
<!-- <button name="action_view_programming" type="object" string="编程单" class="btn-secondary"--> <button name="action_view_programming" type="object" string="编程单" class="btn-secondary"
<!-- attrs="{'invisible': [('hide_action_view_programming', '=', False)]}"/>--> attrs="{'invisible': [('hide_action_view_programming', '=', False)]}"/>
<field name="planned_start_date" attrs="{'readonly': [('status', 'in', ('60','100'))]}"/> <field name="planned_start_date"/>
<field name="actual_start_date"/> <field name="actual_start_date"/>
<field name="actual_end_date"/> <field name="actual_end_date"/>
<field name="processing_time"/> <field name="processing_time"/>
@@ -67,11 +73,8 @@
<field name="write_date" string="更新时间"/> <field name="write_date" string="更新时间"/>
<field name="write_uid" optional="hide" string="更新人"/> <field name="write_uid" optional="hide" string="更新人"/>
<field name="print_count"/> <field name="print_count"/>
<field name="hide_release_production_order" invisible="1"/> <button name="release_production_order" type="object" string="下达生产" class="btn-primary"
<button name="button_release_production" type="object" string="下发生产" class="btn-primary" attrs="{'invisible': ['|',('status', '!=', '50'), ('supply_method', 'not in', ['automation', 'manual'])]}"/>
attrs="{'invisible': [('hide_release_production_order', '=', False)]}"
/>
<button name="action_edit_demand_plan_form" type="object" string="编辑" class="btn-primary"/>
</tree> </tree>
</field> </field>
</record> </record>
@@ -110,6 +113,7 @@
</search> </search>
</field> </field>
</record> </record>
<record id="sf_production_demand_plan_action" model="ir.actions.act_window"> <record id="sf_production_demand_plan_action" model="ir.actions.act_window">
<field name="name">需求计划</field> <field name="name">需求计划</field>
<field name="type">ir.actions.act_window</field> <field name="type">ir.actions.act_window</field>
@@ -117,4 +121,17 @@
<field name="view_mode">tree</field> <field name="view_mode">tree</field>
</record> </record>
<menuitem
id="demand_plan_menu"
name="需求计划"
sequence="140"
action="sf_production_demand_plan_action"
parent="sf_plan.sf_production_plan_menu"
/>
<!-- 调拨动作中屏蔽验证-->
<record id="stock.action_validate_picking" model="ir.actions.server">
<field name="binding_model_id" eval="False"/>
</record>
</odoo> </odoo>

View File

@@ -1,18 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<menuitem
id="demand_plan_menu"
name="需求计划"
sequence="140"
action="sf_demand_plan.sf_production_demand_plan_action"
parent="sf_plan.sf_production_plan_menu"
/>
<!-- 调拨动作中屏蔽验证-->
<record id="stock.action_validate_picking" model="ir.actions.server">
<field name="binding_model_id" eval="False"/>
</record>
</data>
</odoo>

View File

@@ -1,29 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="view_order_form_inherit_plan" model="ir.ui.view">
<field name="name">view.sale.order.form.inherit.plan</field>
<field name="inherit_id" ref="sf_manufacturing.view_order_form_inherit_supply_method"/>
<field name="model">sale.order</field>
<field name="arch" type="xml">
<xpath expr="//header/button[@name='action_confirm'][last()]" position="attributes">
<attribute name="invisible">True</attribute>
</xpath>
<xpath expr="//page/field[@name='order_line']/tree/field[@name='supply_method']" position="attributes">
<attribute name="invisible">True</attribute>
</xpath>
<xpath expr="//div[@name='button_box']" position="inside">
<button class="oe_stat_button" name="action_view_demand_plan" type="object" icon="fa-pencil-square-o"
attrs="{'invisible': [('demand_plan_count', '=', 0)]}">
<div class="o_field_widget o_stat_info">
<span class="o_stat_value">
<field name="demand_plan_count"/>
</span>
<span class="o_stat_text">需求计划</span>
</div>
</button>
</xpath>
</field>
</record>
</odoo>

View File

@@ -1,21 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="sf_stock_location_route_form_view" model="ir.ui.view">
<field name="name">stock.route.form</field>
<field name="model">stock.route</field>
<field name="inherit_id" ref="stock.stock_location_route_form_view"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='packaging_selectable']" position="after">
<field name="demand_plan_selectable"/>
</xpath>
<xpath expr="//group[@name='route_selector']" position="after">
<group name="group_category" string="组类">
<group>
<field name="stock_route_group_ids" options="{'no_create': True}" widget="many2many_tags"/>
<field name="demand_plan_ids" invisible="1" options="{'no_create': True}" widget="many2many_tags"/>
</group>
</group>
</xpath>
</field>
</record>
</odoo>

View File

@@ -1,2 +1 @@
from . import sf_demand_plan_print_wizard from . import sf_demand_plan_print_wizard
from . import sf_release_plan_wizard

View File

@@ -4,12 +4,12 @@
<field name="name">sf.demand.plan.print.wizard.tree</field> <field name="name">sf.demand.plan.print.wizard.tree</field>
<field name="model">sf.demand.plan.print.wizard</field> <field name="model">sf.demand.plan.print.wizard</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<tree string="打印" class="print_demand" js_class="print_demand"> <tree string="打印" class="print_demand" js_class="print_demand" >
<field name="model_id"/> <field name="model_id"/>
<field name="filename_url"/> <field name="filename_url"/>
<field name="type"/> <field name="type"/>
<field name="machining_drawings" attrs="{'column_invisible': True }"/> <field name="machining_drawings" attrs="{'column_invisible': True }"/>
<field name="cnc_worksheet" attrs="{'column_invisible': True }"/> <field name="cnc_worksheet" attrs="{'column_invisible': True }" />
<field name="status"/> <field name="status"/>
</tree> </tree>
</field> </field>

View File

@@ -1,22 +0,0 @@
# -*- coding: utf-8 -*-
import logging
from odoo import models, fields, api, _
_logger = logging.getLogger(__name__)
class SfReleasePlanWizard(models.TransientModel):
_name = 'sf.release.plan.wizard'
_description = u'下达计划向导'
demand_plan_id = fields.Many2one(comodel_name="sf.demand.main.plan",
string="需求计划", readonly=True)
demand_plan_line_id = fields.Many2one(comodel_name="sf.production.demand.plan",
string="需求计划明细", readonly=True)
release_message = fields.Char(string='提示', readonly=True)
def confirm(self):
if self.demand_plan_line_id:
self.demand_plan_line_id.action_confirm()

View File

@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record model="ir.ui.view" id="sf_release_plan_wizard_form">
<field name="name">sf.release.plan.wizard.form</field>
<field name="model">sf.release.plan.wizard</field>
<field name="arch" type="xml">
<form>
<sheet>
<div>
<div style="white-space: pre-wrap;">
<field name="release_message"/>
</div>
</div>
<footer>
<button string="确认" name="confirm" type="object" class="oe_highlight"/>
<button string="取消" class="btn btn-secondary" special="cancel"/>
</footer>
</sheet>
</form>
</field>
</record>
</odoo>

View File

@@ -437,7 +437,7 @@ class Sf_Dashboard_Connect(http.Controller):
('state', 'in', ['ready', 'progress', 'done']) ('state', 'in', ['ready', 'progress', 'done'])
]) ])
plan_data_total_counts = sum(plan_data_total.mapped('qty_production')) plan_data_total_counts = sum(plan_data_total.mapped('qty_produced'))
# # 工单完成量 # # 工单完成量
# plan_data_finish_counts = plan_obj.search_count( # plan_data_finish_counts = plan_obj.search_count(
@@ -601,11 +601,8 @@ class Sf_Dashboard_Connect(http.Controller):
line_list = ast.literal_eval(kw['line_list']) line_list = ast.literal_eval(kw['line_list'])
begin_time_str = kw['begin_time'].strip('"') begin_time_str = kw['begin_time'].strip('"')
end_time_str = kw['end_time'].strip('"') end_time_str = kw['end_time'].strip('"')
# 将时间减去8小时UTC+8转UTC begin_time = datetime.strptime(begin_time_str, '%Y-%m-%d %H:%M:%S')
begin_time = (datetime.strptime(begin_time_str, '%Y-%m-%d %H:%M:%S') - timedelta(hours=8)) end_time = datetime.strptime(end_time_str, '%Y-%m-%d %H:%M:%S')
end_time = (datetime.strptime(end_time_str, '%Y-%m-%d %H:%M:%S') - timedelta(hours=8))
# begin_time = datetime.strptime(begin_time_str, '%Y-%m-%d %H:%M:%S')
# end_time = datetime.strptime(end_time_str, '%Y-%m-%d %H:%M:%S')
# print('line_list: %s' % line_list) # print('line_list: %s' % line_list)
print('kw', kw) print('kw', kw)
time_unit = kw.get('time_unit', 'day').strip('"') # 默认单位为天 time_unit = kw.get('time_unit', 'day').strip('"') # 默认单位为天
@@ -640,15 +637,6 @@ class Sf_Dashboard_Connect(http.Controller):
if time_unit == 'hour': if time_unit == 'hour':
# 计划量目前只能从mail.message中筛选出
plan_order_messages = request.env['mail.message'].sudo().search([
('model', '=', 'mrp.workorder'),
('create_date', '>=', begin_time.strftime('%Y-%m-%d %H:%M:%S')),
('create_date', '<=', end_time.strftime('%Y-%m-%d %H:%M:%S')),
('tracking_value_ids.field_desc', '=', '状态'),
('tracking_value_ids.new_value_char', '=', '就绪')
])
for line in line_list: for line in line_list:
date_field_name = 'date_finished' # 替换为你模型中的实际字段名 date_field_name = 'date_finished' # 替换为你模型中的实际字段名
order_counts = [] order_counts = []
@@ -690,10 +678,19 @@ class Sf_Dashboard_Connect(http.Controller):
) )
# 使用小时和分钟作为键,确保每个小时的数据有独立的键 # 使用小时和分钟作为键,确保每个小时的数据有独立的键
key = (start_time + timedelta(hours=8)).strftime('%H:%M:%S') # 只取小时:分钟:秒作为键 key = start_time.strftime('%H:%M:%S') # 只取小时:分钟:秒作为键
# time_count_dict[key] = len(orders) # time_count_dict[key] = len(orders)
time_count_dict[key] = sum(interval_orders.mapped('qty_produced')) time_count_dict[key] = sum(interval_orders.mapped('qty_produced'))
# 计划量目前只能从mail.message中筛选出
plan_order_messages = request.env['mail.message'].sudo().search([
('model', '=', 'mrp.workorder'),
('create_date', '>=', begin_time.strftime('%Y-%m-%d %H:%M:%S')),
('create_date', '<=', end_time.strftime('%Y-%m-%d %H:%M:%S')),
('tracking_value_ids.field_desc', '=', '状态'),
('tracking_value_ids.new_value_char', '=', '就绪')
])
for time_interval in time_intervals: for time_interval in time_intervals:
start_time, end_time = time_interval start_time, end_time = time_interval
@@ -709,9 +706,7 @@ class Sf_Dashboard_Connect(http.Controller):
and o.create_date <= end_time and o.create_date <= end_time
) )
interval_order_ids = set(interval_plan_orders.mapped('res_id')) interval_orders = request.env['mrp.workorder'].sudo().browse(interval_plan_orders.mapped('res_id'))
interval_orders = request.env['mrp.workorder'].sudo().browse(interval_order_ids)
if line == '业绩总览': if line == '业绩总览':
interval_orders = interval_orders.filtered(lambda o: o.routing_type in ['人工线下加工', 'CNC加工']) interval_orders = interval_orders.filtered(lambda o: o.routing_type in ['人工线下加工', 'CNC加工'])
elif line == '人工线下加工中心': elif line == '人工线下加工中心':
@@ -720,9 +715,9 @@ class Sf_Dashboard_Connect(http.Controller):
interval_orders = interval_orders.filtered(lambda o: o.routing_type == 'CNC加工' and o.production_line_id.name == line) interval_orders = interval_orders.filtered(lambda o: o.routing_type == 'CNC加工' and o.production_line_id.name == line)
# 使用小时和分钟作为键,确保每个小时的数据有独立的键 # 使用小时和分钟作为键,确保每个小时的数据有独立的键
key = (start_time + timedelta(hours=8)).strftime('%H:%M:%S') # 只取小时:分钟:秒作为键 key = start_time.strftime('%H:%M:%S') # 只取小时:分钟:秒作为键
# time_count_dict[key] = len(orders) # time_count_dict[key] = len(orders)
plan_count_dict[key] = sum(interval_orders.mapped('qty_production')) plan_count_dict[key] = sum(interval_orders.mapped('qty_produced'))
# order_counts.append() # order_counts.append()
res['data'][line] = { res['data'][line] = {
@@ -864,38 +859,22 @@ class Sf_Dashboard_Connect(http.Controller):
""" """
# res = {'status': 1, 'message': '成功', 'not_done_data': [], 'done_data': []} # res = {'status': 1, 'message': '成功', 'not_done_data': [], 'done_data': []}
res = {'status': 1, 'message': '成功', 'data': {}} res = {'status': 1, 'message': '成功', 'data': {}}
# 解决产品名称取到英文的问题
request.update_context(lang='zh_CN')
plan_obj = request.env['sf.production.plan'].sudo() plan_obj = request.env['sf.production.plan'].sudo()
work_order_obj = request.env['mrp.workorder'].sudo() work_order_obj = request.env['mrp.workorder'].sudo()
# 获取mrp.workorder的state字段的selection内容
state_dict = dict(request.env['mrp.workorder'].sudo()._fields['state'].selection)
line_list = ast.literal_eval(kw['line_list']) line_list = ast.literal_eval(kw['line_list'])
begin_time_str = kw['begin_time'].strip('"') begin_time_str = kw['begin_time'].strip('"')
end_time_str = kw['end_time'].strip('"') end_time_str = kw['end_time'].strip('"')
begin_time = datetime.strptime(begin_time_str, '%Y-%m-%d %H:%M:%S') begin_time = datetime.strptime(begin_time_str, '%Y-%m-%d %H:%M:%S')
end_time = datetime.strptime(end_time_str, '%Y-%m-%d %H:%M:%S') end_time = datetime.strptime(end_time_str, '%Y-%m-%d %H:%M:%S')
# print('line_list: %s' % line_list) # print('line_list: %s' % line_list)
final_data = {}
# 获取当前时间并计算24小时前的时间
current_time = datetime.now()
time_48_hours_ago = current_time - timedelta(hours=48)
# # 计划量目前只能从mail.message中筛选出
# plan_order_messages = request.env['mail.message'].sudo().search([
# ('model', '=', 'mrp.workorder'),
# ('create_date', '>=', time_48_hours_ago.strftime('%Y-%m-%d %H:%M:%S')),
# ('tracking_value_ids.field_desc', '=', '状态'),
# ('tracking_value_ids.new_value_char', 'in', ['就绪', '生产中'])
# ])
for line in line_list:
not_done_data = [] not_done_data = []
done_data = [] done_data = []
final_data = {}
not_done_index = 1 not_done_index = 1
done_index = 1 done_index = 1
for line in line_list:
if line == '业绩总览': if line == '业绩总览':
work_order_domain = [('routing_type', 'in', ['人工线下加工', 'CNC加工'])] work_order_domain = [('routing_type', 'in', ['人工线下加工', 'CNC加工'])]
elif line == '人工线下加工中心': elif line == '人工线下加工中心':
@@ -910,24 +889,21 @@ class Sf_Dashboard_Connect(http.Controller):
# [('production_line_id.name', '=', line), ('state', 'not in', ['finished']), # [('production_line_id.name', '=', line), ('state', 'not in', ['finished']),
# ('production_id.state', 'not in', ['cancel', 'done']), ('active', '=', True) # ('production_id.state', 'not in', ['cancel', 'done']), ('active', '=', True)
# ]) # ])
not_done_orders = work_order_obj.search(work_order_domain + [ not_done_orders = work_order_obj.search(work_order_domain +
('state', 'in', ['ready', 'progress']), [('state', 'in', ['ready', 'progress'])], order='id asc'
('date_planned_start', '>=', time_48_hours_ago),
('date_planned_start', '<=', current_time)
], order='id asc'
) )
# 完成订单 # 完成订单
# 获取当前时间并计算24小时前的时间 # 获取当前时间并计算24小时前的时间
# current_time = datetime.now() current_time = datetime.now()
# time_24_hours_ago = current_time - timedelta(hours=24) time_24_hours_ago = current_time - timedelta(hours=24)
finish_orders = work_order_obj.search(work_order_domain + [ finish_orders = work_order_obj.search(work_order_domain + [
('state', 'in', ['done']), ('state', 'in', ['finished']),
('production_id.state', 'not in', ['cancel']), ('production_id.state', 'not in', ['cancel']),
('date_finished', '>=', time_48_hours_ago) ('date_finished', '>=', time_24_hours_ago)
], order='id asc') ], order='id asc')
# logging.info('完成订单: %s' % finish_orders) # print(finish_orders)
# 获取所有未完成订单的ID列表 # 获取所有未完成订单的ID列表
order_ids = [order.id for order in not_done_orders] order_ids = [order.id for order in not_done_orders]
@@ -963,6 +939,14 @@ class Sf_Dashboard_Connect(http.Controller):
material_match = re.search(material_pattern, blank_name) material_match = re.search(material_pattern, blank_name)
material = material_match.group(1) if material_match else 'No match found' material = material_match.group(1) if material_match else 'No match found'
state_dict = {
'draft': '待排程',
'done': '已排程',
'processing': '生产中',
'finished': '已完成',
'ready': '待加工',
'progress': '生产中',
}
line_dict = { line_dict = {
'sequence': not_done_index, 'sequence': not_done_index,
@@ -978,6 +962,8 @@ class Sf_Dashboard_Connect(http.Controller):
not_done_index += 1 not_done_index += 1
for finish_order in finish_orders: for finish_order in finish_orders:
if not finish_order.actual_end_time:
continue
blank_name = '' blank_name = ''
try: try:
blank_name = finish_order.production_id.move_raw_ids[0].product_id.name blank_name = finish_order.production_id.move_raw_ids[0].product_id.name
@@ -993,13 +979,13 @@ class Sf_Dashboard_Connect(http.Controller):
line_dict = { line_dict = {
'sequence': done_index, 'sequence': done_index,
'workorder_name': finish_order.production_id.name, 'workorder_name': finish_order.name,
'blank_name': blank_name, 'blank_name': blank_name,
'material': material, 'material': material,
'dimensions': dimensions, 'dimensions': dimensions,
'order_qty': finish_order.qty_produced, 'order_qty': order.qty_produced,
'finish_time': finish_order.date_finished.strftime( 'finish_time': finish_order.actual_end_time.strftime(
'%Y-%m-%d %H:%M:%S') if finish_order.date_finished else ' ' '%Y-%m-%d %H:%M:%S') if finish_order.actual_end_time else ' '
} }
done_data.append(line_dict) done_data.append(line_dict)

View File

@@ -31,6 +31,7 @@
'wizard/sf_programming_reason_views.xml', 'wizard/sf_programming_reason_views.xml',
'wizard/sale_order_cancel_views.xml', 'wizard/sale_order_cancel_views.xml',
'wizard/process_outsourcing.xml', 'wizard/process_outsourcing.xml',
'wizard/create_technology_design_task_wizard_views.xml',
'views/mrp_views_menus.xml', 'views/mrp_views_menus.xml',
'views/agv_scheduling_views.xml', 'views/agv_scheduling_views.xml',
'views/stock_lot_views.xml', 'views/stock_lot_views.xml',
@@ -48,6 +49,8 @@
'views/mrp_workorder_batch_replan.xml', 'views/mrp_workorder_batch_replan.xml',
'views/purchase_order_view.xml', 'views/purchase_order_view.xml',
'views/product_template_views.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', # 'views/stock_warehouse_orderpoint.xml',
], ],
'assets': { 'assets': {

View File

@@ -36,6 +36,14 @@
<field name="padding">5</field> <field name="padding">5</field>
</record> </record>
<record id="sequence_technology_design_task" model="ir.sequence">
<field name="name">工艺设计任务编码规则</field>
<field name="code">sf.technology.design.task</field>
<field name="prefix">TD/%(year)s%(month)s%(day)s/</field>
<field name="padding">4</field>
<field name="company_id" eval="False"/>
</record>
<record id="stock_location_locations_virtual_outcontract" model="stock.location"> <record id="stock_location_locations_virtual_outcontract" model="stock.location">
<field name="name">外协</field> <field name="name">外协</field>
<field name="location_id" ref="stock.stock_location_locations_virtual"/> <field name="location_id" ref="stock.stock_location_locations_virtual"/>

View File

@@ -18,5 +18,5 @@ from . import quick_easy_order
from . import purchase_order from . import purchase_order
from . import quality_check from . import quality_check
from . import purchase_request_line from . import purchase_request_line
from . import bom from . import sf_technology_design_task
# from . import stock_warehouse_orderpoint # from . import stock_warehouse_orderpoint

View File

@@ -1,16 +0,0 @@
from odoo import models
from odoo.osv.expression import AND
class MrpBom(models.Model):
_inherit = 'mrp.bom'
def _bom_subcontract_find(self, product, picking_type=None, company_id=False, bom_type='subcontract', subcontractor=False):
domain = self._bom_find_domain(product, picking_type=picking_type, company_id=company_id, bom_type=bom_type)
if self.env.context.get('stock_picking') == 'outsourcing':
return self.search(domain, order='sequence, product_id, id', limit=1)
if subcontractor:
domain = AND([domain, [('subcontractor_ids', 'parent_of', subcontractor.ids)]])
return self.search(domain, order='sequence, product_id, id', limit=1)
else:
return self.env['mrp.bom']

View File

@@ -1709,7 +1709,6 @@ class MrpProduction(models.Model):
vals['procurement_group_id'] = product_group_id[product_id.id] vals['procurement_group_id'] = product_group_id[product_id.id]
else: else:
vals['procurement_group_id'] = is_custemer_group_id[key] vals['procurement_group_id'] = is_custemer_group_id[key]
return super(MrpProduction, self).create(vals_list) return super(MrpProduction, self).create(vals_list)
@api.depends('procurement_group_id.stock_move_ids.created_purchase_line_id.order_id', @api.depends('procurement_group_id.stock_move_ids.created_purchase_line_id.order_id',

View File

@@ -35,7 +35,7 @@ class ResProductMo(models.Model):
model_volume = fields.Float('模型体积(m³)') model_volume = fields.Float('模型体积(m³)')
model_area = fields.Float('模型表面积(m²)') model_area = fields.Float('模型表面积(m²)')
model_machining_precision = fields.Selection(selection=_get_machining_precision, string='加工精度') model_machining_precision = fields.Selection(selection=_get_machining_precision, string='加工精度')
model_processing_panel = fields.Char('模型加工面板', default='') model_processing_panel = fields.Char('模型加工面板')
model_remark = fields.Char('模型备注说明') model_remark = fields.Char('模型备注说明')
length = fields.Float('长(mm)', digits=(16, 3)) length = fields.Float('长(mm)', digits=(16, 3))
width = fields.Float('宽(mm)', digits=(16, 3)) width = fields.Float('宽(mm)', digits=(16, 3))
@@ -913,7 +913,7 @@ class ResProductMo(models.Model):
(item['model_height'] + embryo_redundancy_id.height))) if not blank_bool else ( (item['model_height'] + embryo_redundancy_id.height))) if not blank_bool else (
item.get('blank_length') * item.get('blank_width') * item.get('blank_height')), item.get('blank_length') * item.get('blank_width') * item.get('blank_height')),
'product_model_type_id': model_type.id, 'product_model_type_id': model_type.id,
'model_processing_panel': item['processing_panel_detail'] if item['processing_panel_detail'] else '', 'model_processing_panel': item['processing_panel_detail'],
'model_machining_precision': item['model_machining_precision'], 'model_machining_precision': item['model_machining_precision'],
'model_code': item['barcode'], 'model_code': item['barcode'],
'length': item['model_long'], 'length': item['model_long'],

View File

@@ -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')]),
}

View File

@@ -21,7 +21,6 @@ from odoo.addons.sf_base.commons.common import Common
from odoo.exceptions import UserError from odoo.exceptions import UserError
from io import BytesIO from io import BytesIO
from odoo.exceptions import ValidationError from odoo.exceptions import ValidationError
from dateutil.relativedelta import relativedelta
class stockWarehouse(models.Model): class stockWarehouse(models.Model):
@@ -96,36 +95,35 @@ class StockRule(models.Model):
precision_rounding=proc[ precision_rounding=proc[
0].product_uom.rounding) > 0) 0].product_uom.rounding) > 0)
list2 = [] list2 = []
for procurement, rule in procurements: for item in procurements:
num = int(procurement.product_qty) num = int(item[0].product_qty)
warehouse_id = rule.warehouse_id product = self.env['product.product'].search(
if not warehouse_id: [("id", '=', item[0].product_id.id)])
warehouse_id = rule.location_dest_id.warehouse_id product_tmpl = self.env['product.template'].search(
manu_rule = rule.route_id.rule_ids.filtered(lambda r: r.action == 'manufacture' and r.warehouse_id == warehouse_id) ["&", ("id", '=', product.product_tmpl_id.id), ('single_manufacturing', "!=", False)])
if product_tmpl:
if procurement.product_id.product_tmpl_id.single_manufacturing and manu_rule:
if num > 1: if num > 1:
for no in range(1, num + 1): for no in range(1, num + 1):
Procurement = namedtuple('Procurement', ['product_id', 'product_qty', Procurement = namedtuple('Procurement', ['product_id', 'product_qty',
'product_uom', 'location_id', 'name', 'origin', 'product_uom', 'location_id', 'name', 'origin',
'company_id', 'company_id',
'values']) 'values'])
s = Procurement(product_id=procurement.product_id, product_qty=1.0, product_uom=procurement.product_uom, s = Procurement(product_id=item[0].product_id, product_qty=1.0, product_uom=item[0].product_uom,
location_id=procurement.location_id, location_id=item[0].location_id,
name=procurement.name, name=item[0].name,
origin=procurement.origin, origin=item[0].origin,
company_id=procurement.company_id, company_id=item[0].company_id,
values=procurement.values, values=item[0].values,
) )
# item1 = list(item) item1 = list(item)
# item1[0] = s item1[0] = s
list2.append((s, rule)) list2.append(tuple(item1))
else: else:
list2.append((procurement, rule)) list2.append(item)
else: else:
list2.append((procurement, rule)) list2.append(item)
for procurement, rule in list2: for procurement, rule in list2:
procure_method = rule.procure_method procure_method = rule.procure_method
@@ -185,6 +183,18 @@ class StockRule(models.Model):
'''创建制造订单''' '''创建制造订单'''
productions = self.env['mrp.production'].with_user(SUPERUSER_ID).sudo().with_company(company_id).create( productions = self.env['mrp.production'].with_user(SUPERUSER_ID).sudo().with_company(company_id).create(
productions_values) productions_values)
# 将这一批制造订单的采购组根据成品设置为不同的采购组
# product_group_id = {}
# for index, production in enumerate(productions):
# if production.product_id.id not in product_group_id.keys():
# product_group_id[production.product_id.id] = production.procurement_group_id.id
# else:
# productions_values[index].update({'name': production.name})
# procurement_group_vals = production._prepare_procurement_group_vals(productions_values[index])
# production.procurement_group_id = self.env["procurement.group"].create(procurement_group_vals).id
# self.env['stock.move'].sudo().create(productions._get_moves_raw_values())
# self.env['stock.move'].sudo().create(productions._get_moves_finished_values())
''' '''
创建工单 创建工单
@@ -728,33 +738,6 @@ class StockPicking(models.Model):
production.workorder_ids.write({'back_button_display': False}) production.workorder_ids.write({'back_button_display': False})
return res return res
def _prepare_subcontract_mo_vals(self, subcontract_move, bom):
subcontract_move.ensure_one()
group = self.env['procurement.group'].sudo().search([('name', '=', self.name)])
if not group:
group = self.env['procurement.group'].create({
'name': self.name,
'partner_id': self.partner_id.id,
})
product = subcontract_move.product_id
warehouse = self._get_warehouse(subcontract_move)
vals = {
'company_id': subcontract_move.company_id.id,
'procurement_group_id': group.id,
'subcontractor_id': subcontract_move.picking_id.partner_id.commercial_partner_id.id,
'picking_ids': [subcontract_move.picking_id.id],
'product_id': product.id,
'product_uom_id': subcontract_move.product_uom.id,
'bom_id': bom.id,
'location_src_id': subcontract_move.picking_id.partner_id.with_company(subcontract_move.company_id).property_stock_subcontractor.id,
'location_dest_id': subcontract_move.picking_id.partner_id.with_company(subcontract_move.company_id).property_stock_subcontractor.id,
'product_qty': subcontract_move.product_uom_qty,
'picking_type_id': warehouse.subcontracting_type_id.id,
'date_planned_start': subcontract_move.date - relativedelta(days=product.produce_delay)
}
return vals
# 创建 外协出库入单 # 创建 外协出库入单
def create_outcontract_picking(self, workorders, item, sorted_workorders): def create_outcontract_picking(self, workorders, item, sorted_workorders):
production = workorders[0].production_id production = workorders[0].production_id
@@ -1233,20 +1216,6 @@ class ReStockMove(models.Model):
res['lot_id'] = self.subcontract_workorder_id.production_id.move_raw_ids.move_line_ids[0].lot_id.id res['lot_id'] = self.subcontract_workorder_id.production_id.move_raw_ids.move_line_ids[0].lot_id.id
return res return res
def _get_subcontract_bom(self):
self.ensure_one()
purchase_type = getattr(self.picking_id.purchase_id, 'purchase_type', False)
if purchase_type:
self = self.with_context(stock_picking=purchase_type)
bom = self.env['mrp.bom'].sudo()._bom_subcontract_find(
self.product_id,
picking_type=self.picking_type_id,
company_id=self.company_id.id,
bom_type='subcontract',
subcontractor=self.picking_id.partner_id
)
return bom
class ReStockQuant(models.Model): class ReStockQuant(models.Model):
_inherit = 'stock.quant' _inherit = 'stock.quant'

View File

@@ -196,3 +196,9 @@ access_sf_sale_order_cancel_wizard,sf_sale_order_cancel_wizard,model_sf_sale_ord
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_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 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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
196
197
198
199
200
201
202
203
204

View File

@@ -0,0 +1,76 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- 工艺设计任务仪表板视图 -->
<record id="view_sf_technology_design_task_dashboard" model="ir.ui.view">
<field name="name">sf.technology.design.task.dashboard</field>
<field name="model">sf.technology.design.task</field>
<field name="arch" type="xml">
<kanban class="o_kanban_small_column" default_group_by="state">
<field name="name"/>
<field name="state"/>
<field name="product_id"/>
<field name="part_name"/>
<field name="assigned_to"/>
<field name="deadline"/>
<field name="create_date"/>
<templates>
<t t-name="kanban-box">
<div class="oe_kanban_global_click">
<div class="oe_kanban_content">
<div class="o_kanban_record_top">
<div class="o_kanban_record_headings">
<strong class="o_kanban_record_title">
<field name="name"/>
</strong>
</div>
</div>
<div class="o_kanban_record_body">
<div class="o_kanban_primary_left">
<field name="product_id"/>
</div>
<div class="o_kanban_primary_right">
<field name="part_name"/>
</div>
</div>
<div class="o_kanban_record_bottom">
<div class="oe_kanban_bottom_left">
<field name="assigned_to"/>
</div>
<div class="oe_kanban_bottom_right">
<field name="deadline"/>
</div>
</div>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
<!-- 工艺设计任务仪表板动作 -->
<record id="action_sf_technology_design_task_dashboard" model="ir.actions.act_window">
<field name="name">工艺设计任务仪表板</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">sf.technology.design.task</field>
<field name="view_mode">kanban</field>
<field name="view_id" ref="view_sf_technology_design_task_dashboard"/>
<field name="context">{'search_default_filter_pending': 1}</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
创建第一个工艺设计任务
</p>
<p>
工艺设计任务用于管理产品工艺设计流程,包括从需求计划到工艺完成的整个过程。
</p>
</field>
</record>
<!-- 工艺设计任务仪表板菜单 -->
<menuitem id="menu_sf_technology_design_task_dashboard"
name="工艺设计仪表板"
sequence="15"
action="action_sf_technology_design_task_dashboard"
parent="mrp.menu_mrp_manufacturing"/>
</odoo>

View File

@@ -0,0 +1,280 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- 工艺设计任务列表视图 -->
<record id="view_sf_technology_design_task_tree" model="ir.ui.view">
<field name="name">sf.technology.design.task.tree</field>
<field name="model">sf.technology.design.task</field>
<field name="arch" type="xml">
<tree string="工艺设计任务" default_order="create_date desc"
class="technology_design_task_tree freeze-columns-before-part_number">
<header>
<button string="开始设计" name="action_start_design" type="object"
class="btn-primary" attrs="{'invisible': [('state', '!=', 'pending')]}"/>
<button string="完成设计" name="action_complete_design" type="object"
class="btn-success" attrs="{'invisible': [('state', '!=', 'in_progress')]}"/>
<button string="取消任务" name="action_cancel_task" type="object"
class="btn-secondary" attrs="{'invisible': [('state', 'in', ['completed'])]}"/>
<button string="重置任务" name="action_reset_to_pending" type="object"
class="btn-warning" attrs="{'invisible': [('state', 'not in', ['in_progress', 'cancelled'])]}"/>
</header>
<!-- 主要字段 -->
<field name="name" string="任务编号"/>
<field name="state" widget="badge"
decoration-success="state == 'completed'"
decoration-warning="state == 'in_progress'"
decoration-danger="state == 'cancelled'"/>
<field name="sale_order_id" string="销售单号"/>
<field name="product_id" string="产品名称"/>
<field name="part_name" string="零件名称"/>
<field name="part_number" string="零件图号"/>
<field name="product_type" string="零件类型"/>
<field name="model_id" string="模型ID" optional="hide"/>
<field name="model_filename" string="模型文件" optional="hide"/>
<field name="materials_id" string="材料及型号"/>
<field name="blank_type" string="坯料分类" optional="hide"/>
<field name="blank_precision" string="坯料类型" optional="hide"/>
<field name="embryo_long" string="坯料尺寸" optional="hide"/>
<field name="machining_precision" string="加工精度" optional="hide"/>
<field name="machining_panel" string="加工面" optional="hide"/>
<field name="product_uom_qty" string="订单数量"/>
<field name="is_incoming_material" string="客供料"/>
<field name="clamping_times" string="装夹次数" optional="hide"/>
<field name="single_clamping_duration" string="单次装夹时长" optional="hide"/>
<field name="cnc_processing_duration" string="CNC加工时长" optional="hide"/>
<field name="customer_quality_requirements" string="客户质量要求" optional="hide"/>
<field name="processing_drawing_filename" string="2D加工图纸" optional="hide"/>
<field name="customer_name" string="客户名称" optional="hide"/>
<!-- 时间字段 -->
<field name="create_date" string="创建时间" optional="hide"/>
<field name="start_date" string="开始时间" optional="hide"/>
<field name="complete_date" string="完成时间" optional="hide"/>
<field name="deadline" string="截止时间" optional="hide"/>
<!-- 负责人字段 -->
<field name="assigned_to" string="负责人" optional="hide"/>
<field name="created_by" string="创建人" optional="hide"/>
<!-- 备注 -->
<field name="notes" string="备注" optional="hide"/>
</tree>
</field>
</record>
<!-- 工艺设计任务搜索视图 -->
<record id="view_sf_technology_design_task_search" model="ir.ui.view">
<field name="name">sf.technology.design.task.search</field>
<field name="model">sf.technology.design.task</field>
<field name="arch" type="xml">
<search>
<field name="name"/>
<field name="sale_order_id"/>
<field name="product_id"/>
<field name="part_name"/>
<field name="part_number"/>
<field name="customer_name"/>
<field name="materials_id"/>
<field name="assigned_to"/>
<field name="created_by"/>
<filter name="filter_pending" string="待工艺设计" domain="[('state', '=', 'pending')]"/>
<filter name="filter_in_progress" string="进行中" domain="[('state', '=', 'in_progress')]"/>
<filter name="filter_completed" string="已完成" domain="[('state', '=', 'completed')]"/>
<filter name="filter_cancelled" string="已取消" domain="[('state', '=', 'cancelled')]"/>
<separator/>
<filter name="filter_my_tasks" string="我的任务" domain="[('assigned_to', '=', uid)]"/>
<filter name="filter_overdue" string="已逾期" domain="[('deadline', '&lt;', context_today()), ('state', 'not in', ['completed', 'cancelled'])]"/>
<group expand="0" string="Group By">
<filter name="group_by_state" string="状态" domain="[]" context="{'group_by': 'state'}"/>
<filter name="group_by_sale_order" string="销售单号" domain="[]" context="{'group_by': 'sale_order_id'}"/>
<filter name="group_by_product" string="产品" domain="[]" context="{'group_by': 'product_id'}"/>
<filter name="group_by_customer" string="客户" domain="[]" context="{'group_by': 'customer_name'}"/>
<filter name="group_by_assigned_to" string="负责人" domain="[]" context="{'group_by': 'assigned_to'}"/>
<filter name="group_by_created_by" string="创建人" domain="[]" context="{'group_by': 'created_by'}"/>
<filter name="group_by_create_date" string="创建日期" domain="[]" context="{'group_by': 'create_date:day'}"/>
<filter name="group_by_deadline" string="截止日期" domain="[]" context="{'group_by': 'deadline:day'}"/>
</group>
</search>
</field>
</record>
<!-- 工艺设计任务表单视图 -->
<record id="view_sf_technology_design_task_form" model="ir.ui.view">
<field name="name">sf.technology.design.task.form</field>
<field name="model">sf.technology.design.task</field>
<field name="arch" type="xml">
<form string="工艺设计任务">
<header>
<button name="action_start_design" string="开始设计" type="object"
class="btn-primary" attrs="{'invisible': [('state', '!=', 'pending')]}"/>
<button name="action_complete_design" string="完成设计" type="object"
class="btn-success" attrs="{'invisible': [('state', '!=', 'in_progress')]}"/>
<button name="action_cancel_task" string="取消任务" type="object"
class="btn-secondary" attrs="{'invisible': [('state', 'in', ['completed'])]}"/>
<button name="action_reset_to_pending" string="重置任务" type="object"
class="btn-warning" attrs="{'invisible': [('state', 'not in', ['in_progress', 'cancelled'])]}"/>
<field name="state" widget="statusbar"
statusbar_visible="pending,in_progress,completed"/>
</header>
<sheet>
<div class="oe_title">
<h1>
<field name="name" readonly="1"/>
</h1>
</div>
<group>
<group string="基本信息">
<field name="demand_plan_id"/>
<field name="sale_order_id"/>
<field name="customer_name"/>
<field name="assigned_to"/>
<field name="created_by"/>
</group>
<group string="时间信息">
<field name="create_date"/>
<field name="start_date"/>
<field name="complete_date"/>
<field name="deadline"/>
</group>
</group>
<notebook>
<page string="产品信息">
<group>
<group string="产品详情">
<field name="product_id"/>
<field name="part_name"/>
<field name="part_number"/>
<field name="product_type"/>
<field name="model_id"/>
<field name="model_file" filename="model_filename"/>
<field name="model_filename" invisible="1"/>
</group>
<group string="材料信息">
<field name="materials_id"/>
<field name="blank_type"/>
<field name="blank_precision"/>
<field name="embryo_long"/>
</group>
</group>
</page>
<page string="加工信息">
<group>
<group string="加工参数">
<field name="machining_precision"/>
<field name="machining_panel"/>
<field name="product_uom_qty"/>
<field name="is_incoming_material"/>
</group>
<group string="工艺参数">
<field name="clamping_times"/>
<field name="single_clamping_duration"/>
<field name="cnc_processing_duration"/>
</group>
</group>
</page>
<page string="质量要求">
<group>
<field name="customer_quality_requirements" nolabel="1"/>
</group>
<group string="图纸文件">
<field name="processing_drawing_2d" filename="processing_drawing_filename"/>
<field name="processing_drawing_filename" invisible="1"/>
</group>
</page>
<page string="备注">
<field name="notes" nolabel="1"/>
</page>
</notebook>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids"/>
<field name="activity_ids"/>
<field name="message_ids"/>
</div>
</form>
</field>
</record>
<!-- 工艺设计任务动作 -->
<record id="action_sf_technology_design_task" model="ir.actions.act_window">
<field name="name">工艺设计任务</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">sf.technology.design.task</field>
<field name="view_mode">tree,form</field>
<field name="context">{'search_default_filter_pending': 1}</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
创建第一个工艺设计任务
</p>
<p>
工艺设计任务用于管理产品工艺设计流程,包括从需求计划到工艺完成的整个过程。
</p>
</field>
</record>
<!-- 工艺设计任务菜单 -->
<menuitem id="menu_sf_technology_design_task"
name="工艺设计任务"
sequence="20"
action="action_sf_technology_design_task"
parent="mrp.menu_mrp_manufacturing"/>
<!-- 工艺设计任务看板视图 -->
<record id="view_sf_technology_design_task_kanban" model="ir.ui.view">
<field name="name">sf.technology.design.task.kanban</field>
<field name="model">sf.technology.design.task</field>
<field name="arch" type="xml">
<kanban class="o_kanban_small_column" default_group_by="state">
<field name="name"/>
<field name="state"/>
<field name="product_id"/>
<field name="part_name"/>
<field name="assigned_to"/>
<field name="deadline"/>
<templates>
<t t-name="kanban-box">
<div class="oe_kanban_global_click">
<div class="oe_kanban_content">
<div class="o_kanban_record_top">
<div class="o_kanban_record_headings">
<strong class="o_kanban_record_title">
<field name="name"/>
</strong>
</div>
</div>
<div class="o_kanban_record_body">
<div class="o_kanban_primary_left">
<field name="product_id"/>
</div>
<div class="o_kanban_primary_right">
<field name="part_name"/>
</div>
</div>
<div class="o_kanban_record_bottom">
<div class="oe_kanban_bottom_left">
<field name="assigned_to"/>
</div>
<div class="oe_kanban_bottom_right">
<field name="deadline"/>
</div>
</div>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
</odoo>

View File

@@ -7,3 +7,4 @@ from . import mrp_workorder_batch_replan_wizard
from . import sf_programming_reason from . import sf_programming_reason
from . import sale_order_cancel from . import sale_order_cancel
from . import process_outsourcing from . import process_outsourcing
from . import create_technology_design_task_wizard

View File

@@ -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},
}

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_create_technology_design_task_wizard_form" model="ir.ui.view">
<field name="name">sf.create.technology.design.task.wizard.form</field>
<field name="model">sf.create.technology.design.task.wizard</field>
<field name="arch" type="xml">
<form string="创建工艺设计任务">
<group>
<field name="demand_plan_ids" widget="many2many_tags"/>
<field name="assigned_to"/>
<field name="deadline"/>
<field name="notes"/>
</group>
<footer>
<button name="action_create_tasks" string="创建任务" type="object" class="btn-primary"/>
<button string="取消" class="btn-secondary" special="cancel"/>
</footer>
</form>
</field>
</record>
<record id="action_create_technology_design_task_wizard" model="ir.actions.act_window">
<field name="name">创建工艺设计任务</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">sf.create.technology.design.task.wizard</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
</odoo>

View File

@@ -200,8 +200,8 @@
<p></p> <p></p>
</div> </div>
</div> --> </div> -->
<!-- 页脚固定在底部 -->
<!-- 页脚固定在底部 -->
<!-- <div style="position: absolute; bottom: 0; left: 0; right: 0;"> --> <!-- <div style="position: absolute; bottom: 0; left: 0; right: 0;"> -->
<t t-call="sf_quality.report_quality_footer"/> <t t-call="sf_quality.report_quality_footer"/>
<!-- </div> --> <!-- </div> -->
@@ -329,11 +329,9 @@
</div> --> </div> -->
<!-- 页脚固定在底部 --> <!-- 页脚固定在底部 -->
<!-- <t t-if="loop.index == len(docs) - 1">-->
<!-- <div style="position: absolute; bottom: 0; left: 0; right: 0;"> --> <!-- <div style="position: absolute; bottom: 0; left: 0; right: 0;"> -->
<t t-call="sf_quality.html_report_quality_footer"/> <t t-call="sf_quality.html_report_quality_footer"/>
<!-- </div> --> <!-- </div> -->
<!-- </t>-->
</div> </div>
</t> </t>
</t> </t>

View File

@@ -1,81 +1,49 @@
import logging
from odoo import api, models from odoo import api, models
from odoo.exceptions import ValidationError, UserError
class StockPicking(models.Model): class StockPicking(models.Model):
_inherit = 'stock.picking' _inherit = 'stock.picking'
def _compute_check(self):
super()._compute_check()
for picking in self:
picking_to_quality = picking.get_picking_to_quality()
if not picking_to_quality:
picking.quality_check_todo = False
break
else:
need_quality_line = picking.get_need_quality_line(picking_to_quality)
if not need_quality_line or all(not line.get('need_done_check_ids') for line in need_quality_line):
picking.quality_check_todo = False
def check_quality(self):
self.ensure_one()
# checkable_products = self.mapped('move_line_ids').mapped('product_id')
# checks = self.check_ids.filtered(lambda check: check.quality_state == 'none' and (
# check.product_id in checkable_products or check.measure_on == 'operation'))
checks = self.env['quality.check']
picking_to_quality = self._get_picking_to_quality()
need_quality_line = self.get_need_quality_line(picking_to_quality)
if need_quality_line and any(line.get('need_done_check_ids') for line in need_quality_line):
for item in need_quality_line:
checks += item.get('need_done_check_ids')
if checks:
return checks.action_open_quality_check_wizard()
return False
def button_validate(self): def button_validate(self):
"""= """
出厂检验报告上传 出厂检验报告上传
""" """
out_quality_checks = self.env['quality.check'].search( out_quality_checks = self.env['quality.check'].search(
[('picking_id', '=', self.id), ('test_type_id.name', '=', '出厂检验报告'), [('picking_id', '=', self.id), ('test_type_id.name', '=', '出厂检验报告')])
('quality_state', '=', 'pass')])
# out_quality_checks 可能存在多个 # out_quality_checks 可能存在多个
if out_quality_checks: if out_quality_checks:
for out_quality_check in out_quality_checks: for out_quality_check in out_quality_checks:
if not out_quality_check.is_factory_report_uploaded: if not out_quality_check.is_factory_report_uploaded:
if out_quality_check and self.state == 'assigned': if out_quality_check and self.state == 'assigned':
out_quality_check.upload_factory_report() out_quality_check.upload_factory_report()
quality_action = self.pinking_checkout_quality()
if quality_action:
return quality_action
res = super(StockPicking, self).button_validate()
return res
def pinking_checkout_quality(self):
""" """
调拨单若关联了质量检查单,验证调拨单时,应校验是否有不合格品,若存在,应弹窗提示: 调拨单若关联了质量检查单,验证调拨单时,应校验是否有不合格品,若存在,应弹窗提示:
“警告存在不合格产品XXXX n 件、YYYYY m件继续调拨请点“确认”否则请取消 “警告存在不合格产品XXXX n 件、YYYYY m件继续调拨请点“确认”否则请取消
""" """
try:
self.ensure_one()
context = self.env.context context = self.env.context
if not context.get('pinking_checkout_quality'): if not context.get('again_validate') and self.quality_check_ids.filtered(lambda qc: qc.quality_state == 'fail'):
picking_to_quality = self._get_picking_to_quality()
if not picking_to_quality: return False
need_quality_val = self.get_need_quality_line(picking_to_quality)
if any(line.get('fail_check_ids') for line in need_quality_val):
# 回滚事务,为二次确认/取消做准备 # 回滚事务,为二次确认/取消做准备
self.env.cr.rollback() self.env.cr.rollback()
# 获取存在失败的 质检单 调拨单明细行 quality_check_ids = self.quality_check_ids.filtered(lambda qc: qc.quality_state == 'fail')
check_list = [item for item in need_quality_val if item.get('fail_check_ids')] product_list = list(set([quality_check_id.product_id for quality_check_id in quality_check_ids]))
fail_check_text = '' fail_check_text = ''
for item in check_list: for product_id in product_list:
move_id, pre_done_qty = item.get('move_id'), item.get('pre_done_qty') check_ids = quality_check_ids.filtered(lambda qc: qc.product_id == product_id)
fail_check_text = (f'{fail_check_text}{move_id.product_id.display_name} {pre_done_qty}' if all(check_id.measure_on == 'move_line' for check_id in check_ids):
if fail_check_text != '' else f'{move_id.product_id.display_name} {pre_done_qty}') number = sum(check_ids.mapped('qty_line'))
else:
number = sum(self.move_ids_without_package.filtered(
lambda ml: ml.product_id == product_id).mapped('quantity_done'))
if number == 0:
number = sum(self.move_ids_without_package.filtered(
lambda ml: ml.product_id == product_id).mapped('reserved_availability'))
if number == 0:
number = sum(self.move_ids_without_package.filtered(
lambda ml: ml.product_id == product_id).mapped('product_uom_qty'))
fail_check_text = (f'{fail_check_text}{product_id.display_name} {number}'
if fail_check_text != '' else f'{product_id.display_name} {number}')
return { return {
'type': 'ir.actions.act_window', 'type': 'ir.actions.act_window',
'res_model': 'picking.validate.check.wizard', 'res_model': 'picking.validate.check.wizard',
@@ -85,96 +53,7 @@ class StockPicking(models.Model):
'context': { 'context': {
'default_picking_id': self.id, 'default_picking_id': self.id,
'default_fail_check_text': f'警告:存在不合格产品{fail_check_text},继续调拨请点“确认”,否则请取消?', 'default_fail_check_text': f'警告:存在不合格产品{fail_check_text},继续调拨请点“确认”,否则请取消?',
'pinking_checkout_quality': True} 'again_validate': True}
} }
else: res = super(StockPicking, self).button_validate()
return False
except Exception as e:
logging.info('pinking_checkout_quality()方法报错:%s' % e)
raise ValidationError('调拨单验证质检单是否合格时报错,请联系管理员处理!!')
def get_need_quality_line(self, picking_to_quality):
"""
# 对需要进行质检,还没有质检完成的明细行进行统计
# 1、当【质量标准_控制方式】=“产品、作业”,仅校验“预完成数量”>0的产品行对应的质检单必须处理。
2、当【质量标准_控制方式】=“数量”时,仅校验“预完成数量”>0的产品行对应的质检单必须处理
1每一类的“总单数”=【调拨单_需求】时则已处理的质检单“单数”≥“预完成数量”时可执行调拨单验证
2每一类的“总单数”<【调拨单_需求】时则已处理的质检单“单数”≥0时可执行调拨单验证
"""
res = []
for item in picking_to_quality:
need_done_check_ids = self.env['quality.check']
fail_check_ids = self.env['quality.check']
move_id, pre_done_qty, check_ids = item.values()
check_ids_1 = check_ids.filtered(lambda qc: qc.measure_on in ('operation', 'product'))
if check_ids_1:
check_ids_1_done = check_ids_1.filtered(lambda qc: qc.quality_state in ('pass', 'fail'))
check_ids_1_fail = check_ids_1.filtered(lambda qc: qc.quality_state == 'fail')
check_ids_1_none = check_ids_1.filtered(lambda qc: qc.quality_state == 'none')
if check_ids_1 and not check_ids_1_done:
need_done_check_ids += check_ids_1_none
if check_ids_1_fail:
fail_check_ids += check_ids_1_fail
check_ids_2 = check_ids.filtered(lambda qc: qc.measure_on == 'move_line')
if check_ids_2:
check_ids_2_done = check_ids_2.filtered(lambda qc: qc.quality_state in ('pass', 'fail'))
check_ids_2_fail = check_ids_2.filtered(lambda qc: qc.quality_state == 'fail')
check_ids_2_none = check_ids_2.filtered(lambda qc: qc.quality_state == 'none')
# 每一类的“总单数”=【调拨单_需求】时则已处理的质检单“单数”≥“预完成数量”时可执行调拨单验证
if len(check_ids_2) >= move_id.product_uom_qty and len(check_ids_2_done) < pre_done_qty:
need_done_check_ids += check_ids_2_none
# 每一类的“总单数”<【调拨单_需求】时则已处理的质检单“单数”≥0时可执行调拨单验证
elif len(check_ids_2) < move_id.product_uom_qty and len(check_ids_2_done) == 0:
need_done_check_ids += check_ids_2_none
if check_ids_2_fail:
fail_check_ids += check_ids_2_fail
if need_done_check_ids or fail_check_ids:
res.append({'move_id': move_id,
'pre_done_qty': pre_done_qty,
'check_ids': check_ids,
'fail_check_ids': fail_check_ids,
'need_done_check_ids': need_done_check_ids})
return res return res
def get_picking_to_quality(self):
self.ensure_one()
return self._get_picking_to_quality()
def _get_picking_to_quality(self):
"""
对需要质检的明细行进行统计(针对“预完成数量”>0的行)
"""
quality_piking_line_list = []
pre_done_qty_lines = self._get_pinking_pre_done_qty()
for line in pre_done_qty_lines:
move_id, pre_done_qty = line.values()
if pre_done_qty == 0:
continue
product_id = move_id.product_id
check_ids = self.check_ids.filtered(lambda c: c.product_id == product_id)
quality_piking_line_list.append({'move_id': move_id, 'pre_done_qty': pre_done_qty, 'check_ids': check_ids})
return quality_piking_line_list
def _get_pinking_pre_done_qty(self):
"""
return: 明细行 及 预完成数量
1、若调拨单所有明细行的【完成】=0且任意行的【预留】<【需求】,则验证时将会话是否需创建欠单。
---->此时“预完成数量”=【预留】
2、若调拨单任意行的0<【完成】<【需求】,且则验证时将会话是否需创建欠单
---->此时“预完成数量”=【完成】
"""
# if all(move_id.quantity_done == 0 for move_id in self.move_ids_without_package):
# pre_done_qty = [{'move_id': move_id, 'pre_done_qty': move_id.reserved_availability} for move_id in
# self.move_ids_without_package]
# else:
# pre_done_qty = [{'move_id': move_id, 'pre_done_qty': move_id.quantity_done} for move_id in
# self.move_ids_without_package]
pre_done_qty = []
for move_id in self.move_ids_without_package:
if move_id.quantity_done > 0:
pre_done_qty.append({'move_id': move_id, 'pre_done_qty': move_id.quantity_done})
else:
pre_done_qty.append({'move_id': move_id, 'pre_done_qty': move_id.reserved_availability})
return pre_done_qty