Merge branch 'refs/heads/develop' into feature/tax_sync

This commit is contained in:
liaodanlong
2024-09-09 17:37:21 +08:00
31 changed files with 659 additions and 119 deletions

View File

@@ -0,0 +1,61 @@
/** @odoo-module **/
import { registry } from "@web/core/registry";
import { barcodeGenericHandlers } from '@barcodes/barcode_handlers';
import { patch } from "@web/core/utils/patch";
// 定义新的 clickOnButton 函数
function customClickOnButton(selector) {
console.log("This is the custom clickOnButton function!");
const buttons = document.body.querySelectorAll(selector);
let length = buttons.length;
if (length > 0) {
buttons[length - 1].click();
} else {
console.warn(`Button with selector ${selector} not found`);
}
}
patch(barcodeGenericHandlers, "start", {
start(env, { ui, barcode, notification }) {
// 使用新定义的 clickOnButton 函数
const COMMANDS = {
"O-CMD.EDIT": () => customClickOnButton(".o_form_button_edit"),
"O-CMD.DISCARD": () => customClickOnButton(".o_form_button_cancel"),
"O-CMD.SAVE": () => customClickOnButton(".o_form_button_save"),
"O-CMD.PREV": () => customClickOnButton(".o_pager_previous"),
"O-CMD.NEXT": () => customClickOnButton(".o_pager_next"),
"O-CMD.PAGER-FIRST": () => updatePager("first"),
"O-CMD.PAGER-LAST": () => updatePager("last"),
"O-CMD.CONFIRM": () => customClickOnButton(".jikimo_button_confirm"),
};
barcode.bus.addEventListener("barcode_scanned", (ev) => {
const barcode = ev.detail.barcode;
if (barcode.startsWith("O-BTN.")) {
let targets = [];
try {
targets = getVisibleElements(ui.activeElement, `[barcode_trigger=${barcode.slice(6)}]`);
} catch (_e) {
console.warn(`Barcode '${barcode}' is not valid`);
}
for (let elem of targets) {
elem.click();
}
}
if (barcode.startsWith("O-CMD.")) {
const fn = COMMANDS[barcode];
if (fn) {
fn();
} else {
notification.add(env._t("Barcode: ") + `'${barcode}'`, {
title: env._t("Unknown barcode command"),
type: "danger"
});
}
}
});
}
})

View File

@@ -5,9 +5,12 @@ import { registry } from '@web/core/registry';
import { formView } from '@web/views/form/form_view';
import { FormController } from '@web/views/form/form_controller';
import { listView } from '@web/views/list/list_view';
import { ListController } from '@web/views/list/list_controller'
import { onRendered, onMounted } from "@odoo/owl";
export class RemoveFocusController extends FormController {
export class RemoveFocusFormController extends FormController {
setup() {
super.setup();
@@ -17,7 +20,23 @@ export class RemoveFocusController extends FormController {
}
}
registry.category('views').add('remove_focus_view', {
registry.category('views').add('remove_focus_form_view', {
...formView,
Controller: RemoveFocusController,
Controller: RemoveFocusFormController,
});
export class RemoveFocusListController extends ListController {
setup() {
super.setup();
onMounted(() => {
this.__owl__.bdom.el.querySelectorAll(':focus').forEach(element => element.blur());
})
}
}
registry.category('views').add('remove_focus_list_view', {
...listView,
Controller: RemoveFocusListController,
});

View File

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

View File

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

View File

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

View File

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

View File

@@ -45,6 +45,7 @@
'sf_manufacturing/static/src/scss/kanban_change.scss',
'sf_manufacturing/static/src/xml/button_show_on_tree.xml',
'sf_manufacturing/static/src/js/workpiece_delivery_wizard_confirm.js',
'sf_manufacturing/static/src/js/custom_barcode_handlers.js',
]
},

View File

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

View File

@@ -265,3 +265,17 @@ class ResMrpWorkOrder(models.Model):
'sf_agv_scheduling_mrp_workorder_ref',
string='AGV调度',
domain=[('state', '!=', '已取消')])
def get_down_product_agv_scheduling(self):
"""
获取关联的制造订单下产线的agv任务
"""
workorder_ids = self.production_id.workorder_ids
cnc_workorder = workorder_ids.filtered(
lambda w: w.routing_type == 'CNC加工' and w.state == 'done' and w.processing_panel == self.processing_panel
)
if cnc_workorder:
agv_schedulingss = cnc_workorder.agv_scheduling_ids
return agv_schedulingss.filtered(lambda a: a.state == '已配送' and a.agv_route_type == '下产线')
else:
return None

View File

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

View File

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

View File

@@ -1100,7 +1100,7 @@ class ResMrpWorkOrder(models.Model):
[('barcode', 'ilike', 'VL-SPOC')]).id),
('origin', '=', self.production_id.name)])
if move_out.state != 'done':
move_out.write({'state': 'assigned'})
move_out.write({'state': 'assigned', 'production_id': False})
self.env['stock.move.line'].create(move_out.get_move_line(self.production_id, self))
# move_out._action_assign()
@@ -1340,7 +1340,7 @@ class ResMrpWorkOrder(models.Model):
'name': 'button_delivery',
'type': 'object',
'string': '解除装夹',
'class': 'btn-primary',
'class': 'btn-primary jikimo_button_confirm',
# 'className': 'btn-primary',
'modifiers': '{"force_show": 1}'
})
@@ -1355,6 +1355,7 @@ class ResMrpWorkOrder(models.Model):
workorder_ids = []
delivery_type = '运送空料架'
max_num = 4 # 最大配送数量
feeder_station_start_id = False
if len(self) > max_num:
raise UserError('仅限于拆卸1-4个制造订单请重新选择')
for item in self:
@@ -1363,6 +1364,10 @@ class ResMrpWorkOrder(models.Model):
production_ids.append(item.production_id.id)
workorder_ids.append(item.id)
if not feeder_station_start_id:
down_product_agv_scheduling = self.get_down_product_agv_scheduling()
if down_product_agv_scheduling:
feeder_station_start_id = down_product_agv_scheduling.end_site_id.id
return {
'name': _('确认'),
'type': 'ir.actions.act_window',
@@ -1375,7 +1380,8 @@ class ResMrpWorkOrder(models.Model):
'default_delivery_type': delivery_type,
'default_workorder_ids': [(6, 0, workorder_ids)],
'default_workcenter_id': self.env.context.get('default_workcenter_id'),
'default_confirm_button': '确认解除'
'default_confirm_button': '确认解除',
'default_feeder_station_start_id': feeder_station_start_id,
}}

View File

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

View File

@@ -10,7 +10,7 @@ odoo.define('sf_manufacturing.action_dispatch_confirm', function (require) {
title: "确认",
$content: $('<div>').append("请确认是否仅配送" + params.workorder_count + "个工件?"),
buttons: [
{ text: "确认", classes: 'btn-primary', close: true, click: () => dispatchConfirmed(parent, params) },
{ text: "确认", classes: 'btn-primary jikimo_button_confirm', close: true, click: () => dispatchConfirmed(parent, params) },
{ text: "取消", close: true },
],
});

View File

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

View File

@@ -659,9 +659,9 @@
<field name="name">工件配送</field>
<field name="model">sf.workpiece.delivery</field>
<field name="arch" type="xml">
<tree string="工件配送" class="center" create="0" delete="0">
<tree string="工件配送" class="center" create="0" delete="0" js_class="remove_focus_list_view">
<header>
<button name="button_delivery" type="object" string="工件配送" class="btn-primary" attrs="{'force_show':1}"/>
<button name="button_delivery" type="object" string="工件配送" class="btn-primary jikimo_button_confirm" attrs="{'force_show':1}"/>
</header>
<field name="status" widget="badge"
decoration-success="status == '已配送'"

View File

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

View File

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

View File

@@ -4,7 +4,7 @@
<field name="name">sf.workpiece.delivery.wizard.form.view</field>
<field name="model">sf.workpiece.delivery.wizard</field>
<field name="arch" type="xml">
<form js_class="remove_focus_view">
<form js_class="remove_focus_form_view">
<sheet>
<field name="delivery_ids" invisible="True"/>
<field name="workorder_ids" invisible="True"/>
@@ -18,8 +18,8 @@
<field name="workcenter_id" options="{'no_create': True}"/>
</group>
<footer>
<button string="确认配送" name="dispatch_confirm" type="object" class="oe_highlight o_wizard_confirm_button" attrs="{'invisible': [('confirm_button', '!=', '确认配送')]}"/>
<button string="确认解除" name="dispatch_confirm" type="object" class="oe_highlight o_wizard_confirm_button" attrs="{'invisible': [('confirm_button', '!=', '确认解除')]}"/>
<button string="确认配送" name="dispatch_confirm" type="object" class="oe_highlight o_wizard_confirm_button jikimo_button_confirm" attrs="{'invisible': [('confirm_button', '!=', '确认配送')]}"/>
<button string="确认解除" name="dispatch_confirm" type="object" class="oe_highlight o_wizard_confirm_button jikimo_button_confirm" attrs="{'invisible': [('confirm_button', '!=', '确认解除')]}"/>
<button string="取消" class="btn btn-secondary" special="cancel"/>
</footer>
</sheet>

View File

@@ -180,6 +180,13 @@ class WorkpieceDeliveryWizard(models.TransientModel):
self.feeder_station_destination_id = self.route_id.end_site_id.id
def on_barcode_scanned(self, barcode):
# 判断barcode是否是数字
if not barcode.isdigit():
# 判断是否是AGV接驳站名称
agv_site = self.env['sf.agv.site'].search([('name', '=', barcode)])
if agv_site:
self.feeder_station_start_id = agv_site.id
return
delivery_type = self.env.context.get('default_delivery_type')
if delivery_type == '上产线':
workorder = self.env['mrp.workorder'].search(
@@ -203,6 +210,13 @@ class WorkpieceDeliveryWizard(models.TransientModel):
# 将对象添加到对应的同模型且是多对多类型里
self.production_ids |= workorder.production_id
self.workorder_ids |= workorder
if not self.feeder_station_start_id:
down_product_agv_scheduling = self.get_down_product_agv_scheduling()
if down_product_agv_scheduling:
self.feeder_station_start_id = down_product_agv_scheduling.end_site_id.id
else:
raise UserError('该rfid码对应的工单不存在')
return

View File

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

View File

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

4
sf_stock/__init__.py Normal file
View File

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

36
sf_stock/__manifest__.py Normal file
View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

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