Merge branch 'feature/销售订单和工单逾期消息推送' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/销售订单和工单逾期消息推送

This commit is contained in:
jinling.yang
2024-10-15 09:27:16 +08:00
46 changed files with 876 additions and 352 deletions

View File

@@ -3,8 +3,8 @@
'name': "jikimo_account_process", 'name': "jikimo_account_process",
'summary': """ 'summary': """
Short (1 phrase/line) summary of the module's purpose, used as 处理会计凭证生成重复名称报错问题
subtitle on modules listing or apps.openerp.com""", """,
'description': """ 'description': """
Long description of module's purpose Long description of module's purpose

View File

@@ -1,41 +1,99 @@
.zoomed {
position: fixed !important; .processing-capabilities-grid {
top: 50%; display: grid;
left: 50%; grid-template-columns: repeat(6, 1fr);
transform: translate(-50%, -50%) scale(10); gap: 10px;
width: 100%;
} }
.many2many_flex { .grid-item {
display: flex; display: flex;
align-items: center;
} }
.many2many_flex>div { .item-content {
margin-right: 15px; display: flex;
display: flex; flex-direction: column;
flex-direction: column; align-items: center;
align-items: center; text-align: center;
}
/*控制图片大小*/
.item-icon {
width: 50px;
height: 50px;
margin-bottom: 5px;
} }
.many2many_flex>div>:nth-child(2) { .item-label {
position: relative; font-size: 12px;
word-break: break-word;
} }
.close { @media (max-width: 1200px) {
width: 20px; .processing-capabilities-grid {
height: 20px; grid-template-columns: repeat(4, 1fr);
position: absolute; }
top: -8.8px;
right: -8.8px;
color: #fff;
background-color: #000;
opacity: 0;
text-align: center;
line-height: 20px;
font-size: 18px;
} }
.img_close { @media (max-width: 768px) {
opacity: 1; .processing-capabilities-grid {
transform: scale(0.1); grid-template-columns: repeat(3, 1fr);
cursor: pointer; }
}
@media (max-width: 480px) {
.processing-capabilities-grid {
grid-template-columns: repeat(2, 1fr);
}
}
.image-preview-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.9);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
opacity: 0;
transition: opacity 0.3s ease;
}
.image-preview-container.show {
opacity: 1;
}
.image-preview {
max-width: 90%;
max-height: 90%;
object-fit: contain;
box-shadow: 0 0 20px rgba(255, 255, 255, 0.2);
border-radius: 5px;
transform: scale(0.9);
transition: transform 0.3s ease;
}
.image-preview-container.show .image-preview {
transform: scale(1);
}
.image-preview-close {
position: absolute;
top: 20px;
right: 30px;
color: #fff;
font-size: 40px;
font-weight: bold;
transition: 0.3s;
cursor: pointer;
opacity: 0.7;
}
.image-preview-close:hover,
.image-preview-close:focus {
opacity: 1;
text-decoration: none;
cursor: pointer;
} }

View File

@@ -4,35 +4,57 @@ import {Many2ManyCheckboxesField} from "@web/views/fields/many2many_checkboxes/m
import {registry} from "@web/core/registry"; import {registry} from "@web/core/registry";
export class MyCustomWidget extends Many2ManyCheckboxesField { export class MyCustomWidget extends Many2ManyCheckboxesField {
// 你可以重写或者添加一些方法和属性
// 例如你可以重写setup方法来添加一些事件监听器或者初始化一些变量
setup() { setup() {
super.setup(); // 调用父类的setup方法 super.setup();
// 你自己的代码
} }
onImageClick(event) { onImageClick(event, src) {
// 放大图片逻辑 event.preventDefault();
// 获取图片元素 event.stopPropagation();
const img = event.target;
const close = img.nextSibling;
// 实现放大图片逻辑 // 创建预览框
// 比如使用 CSS 放大 const previewContainer = document.createElement('div');
img.parentElement.classList.add('zoomed'); previewContainer.className = 'image-preview-container';
close.classList.add('img_close');
}
onCloseClick(event) { const previewImg = document.createElement('img');
const close = event.target; previewImg.src = src;
const img = close.previousSibling; previewImg.className = 'image-preview';
img.parentElement.classList.remove('zoomed'); // 设置放大的预览图片大小
close.classList.remove('img_close'); previewImg.style.width = '600px';
previewImg.style.height = 'auto'; // 保持宽高比
const closeButton = document.createElement('span');
closeButton.innerHTML = '×';
closeButton.className = 'image-preview-close';
previewContainer.appendChild(previewImg);
previewContainer.appendChild(closeButton);
document.body.appendChild(previewContainer);
// 添加关闭预览的事件监听器
const closePreview = () => {
previewContainer.classList.remove('show');
setTimeout(() => {
document.body.removeChild(previewContainer);
}, 300);
};
closeButton.addEventListener('click', closePreview);
// 点击预览框外部也可以关闭
previewContainer.addEventListener('click', (e) => {
if (e.target === previewContainer) {
closePreview();
}
});
// 使用 setTimeout 来触发过渡效果
setTimeout(() => {
previewContainer.classList.add('show');
}, 10);
} }
} }
MyCustomWidget.template = "jikimo_frontend.MyCustomWidget"; MyCustomWidget.template = "jikimo_frontend.MyCustomWidget";
// MyCustomWidget.supportedTypes = ['many2many'];
registry.category("fields").add("custom_many2many_checkboxes", MyCustomWidget); registry.category("fields").add("custom_many2many_checkboxes", MyCustomWidget);

View File

@@ -2,25 +2,20 @@
<templates xml:space="preserve"> <templates xml:space="preserve">
<t t-name="jikimo_frontend.MyCustomWidget" owl="1"> <t t-name="jikimo_frontend.MyCustomWidget" owl="1">
<div aria-atomic="true" class="many2many_flex"> <div aria-atomic="true" class="many2many_flex processing-capabilities-grid">
<t t-foreach="items" t-as="item" t-key="item[0]"> <t t-foreach="items" t-as="item" t-key="item[0]">
<div> <div class="grid-item">
<CheckBox <CheckBox
value="isSelected(item)" value="isSelected(item)"
disabled="props.readonly" disabled="props.readonly"
onChange="(ev) => this.onChange(item[0], ev)" onChange="(ev) => this.onChange(item[0], ev)"
> >
<t t-esc="item[1]"/> <div class="item-content">
<img t-att-src="item[2]" class="item-icon" t-on-click="(ev) => this.onImageClick(ev, item[2])"/>
<span class="item-label"><t t-esc="item[1]"/></span>
</div>
</CheckBox> </CheckBox>
<div t-on-dblclick="onImageClick">
<t>
<img t-att-src="item[2]" width="50" height="50"/>
<div class="close" t-on-click="onCloseClick">×</div>
</t>
</div> </div>
</div>
</t> </t>
</div> </div>
</t> </t>

View File

@@ -108,6 +108,10 @@ td.o_required_modifier {
} }
.color_3 { .color_3 {
background-color: #808080;
}
.color_4 {
background-color: rgb(255, 150, 0); background-color: rgb(255, 150, 0);
} }

View File

@@ -16,7 +16,7 @@
<!-- hide 登录页面 powerd by odoo 及管理数据库 --> <!-- hide 登录页面 powerd by odoo 及管理数据库 -->
<template id="login_page_layout" inherit_id="web.login_layout" name="Login Page Layout"> <template id="login_page_layout" inherit_id="web.login_layout" name="Login Page Layout">
<xpath expr="//div[@class='card-body']/div[last()]" position="replace"></xpath> <!-- <xpath expr="//div[@class='card-body']/div[last()]" position="replace"></xpath> -->
</template> </template>
<!-- 隐藏odoo版本信息 --> <!-- 隐藏odoo版本信息 -->

View File

@@ -109,7 +109,7 @@
<field name="name">form.sf.machine_tool.type</field> <field name="name">form.sf.machine_tool.type</field>
<field name="model">sf.machine_tool.type</field> <field name="model">sf.machine_tool.type</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<form string="机床型号" delete="0"> <form string="机床型号" delete="0">
<sheet> <sheet>
<div class="oe_title"> <div class="oe_title">
<h1> <h1>
@@ -129,31 +129,28 @@
<field name="machine_tool_picture" widget="image" nolabel="1"/> <field name="machine_tool_picture" widget="image" nolabel="1"/>
</group> </group>
</group> </group>
<group string="加工能力"> <group string="加工能力">
<div> <div>
<field name='jg_image_id' widget="custom_many2many_checkboxes"> <field name='jg_image_id' widget="custom_many2many_checkboxes">
<tree>
<field name="name"/>
<field name="image" widget="image"/>
<tree> </tree>
<field name="name"/> </field>
<field name="image" widget="image"
options="{'size': [100, 100], 'click enlarge': True}"/>
</tree>
</field>
</div> </div>
</group> </group>
<group string="冷却方式"> <group string="冷却方式">
<div> <div>
<field name='lq_image_id' widget="custom_many2many_checkboxes"> <field name='lq_image_id' widget="custom_many2many_checkboxes">
<tree> <tree>
<field name="name"/> <field name="name"/>
<field name="image" widget="image" <field name="image" widget="image"/>
options="{'size': [100, 100], 'click enlarge': True}"/>
</tree> </tree>
</field> </field>
</div> </div>
</group> </group>
@@ -178,7 +175,7 @@
<field name="workbench_H" class="o_address_zip" required="1" <field name="workbench_H" class="o_address_zip" required="1"
options="{'format': false}"/> options="{'format': false}"/>
</div> </div>
<field name="workpiece_load"/> <field name="workpiece_load"/>
<label for="machine_tool_L" string="机床尺寸(mm)"/> <label for="machine_tool_L" string="机床尺寸(mm)"/>
<div class="test_model"> <div class="test_model">
<label for="machine_tool_L" string="长"/> <label for="machine_tool_L" string="长"/>
@@ -192,7 +189,7 @@
<field name="machine_tool_H" class="o_address_zip" <field name="machine_tool_H" class="o_address_zip"
options="{'format': false}"/> options="{'format': false}"/>
</div> </div>
<label for="T_trough_num" string="T型槽尺寸:"/> <label for="T_trough_num" string="T型槽尺寸:"/>
<div class="test_model"> <div class="test_model">
<label for="T_trough_num" string="槽数"/> <label for="T_trough_num" string="槽数"/>
<field name="T_trough_num" class="o_address_zip" <field name="T_trough_num" class="o_address_zip"
@@ -205,20 +202,20 @@
<field name="T_trough_distance" class="o_address_zip" <field name="T_trough_distance" class="o_address_zip"
options="{'format': false}"/> options="{'format': false}"/>
</div> </div>
<!-- <field name="feed_speed" required="1"/>--> <!-- <field name="feed_speed" required="1"/>-->
<!-- <label for="precision_min" string="X轴定位精度(mm)"/>--> <!-- <label for="precision_min" string="X轴定位精度(mm)"/>-->
<!-- <div class="test_model">--> <!-- <div class="test_model">-->
<!-- <label for="precision_min" string="最小(min)"/>--> <!-- <label for="precision_min" string="最小(min)"/>-->
<!-- <field name="precision_min" class="o_address_zip" required="1"--> <!-- <field name="precision_min" class="o_address_zip" required="1"-->
<!-- options="{'format': false}"/>--> <!-- options="{'format': false}"/>-->
<!-- <span>&amp;nbsp;</span>--> <!-- <span>&amp;nbsp;</span>-->
<!-- <label for="precision_max" string="最大(max)"/>--> <!-- <label for="precision_max" string="最大(max)"/>-->
<!-- <field name="precision_max" class="o_address_zip" required="1"--> <!-- <field name="precision_max" class="o_address_zip" required="1"-->
<!-- options="{'format': false}"/>--> <!-- options="{'format': false}"/>-->
<!-- </div>--> <!-- </div>-->
<!-- <field name="lead_screw" required="1"/>--> <!-- <field name="lead_screw" required="1"/>-->
<!-- <field name="guide_rail" required="1"/>--> <!-- <field name="guide_rail" required="1"/>-->
<field name="number_of_axles" required="1" widget="radio" <field name="number_of_axles" required="1" widget="radio"
options="{'horizontal': true}"/> options="{'horizontal': true}"/>
<label for="x_axis" string="加工行程(mm)" <label for="x_axis" string="加工行程(mm)"
@@ -258,7 +255,7 @@
</group> </group>
<group string="主轴"> <group string="主轴">
<field name="taper_type_id" required="1"/> <field name="taper_type_id" required="1"/>
<label for="distance_min" string="主轴端面-工作台距离(mm)"/> <label for="distance_min" string="主轴端面-工作台距离(mm)"/>
<div class="test_model"> <div class="test_model">
<label for="distance_min" string="最小(min)"/> <label for="distance_min" string="最小(min)"/>
<field name="distance_min" class="o_address_zip" <field name="distance_min" class="o_address_zip"
@@ -268,7 +265,7 @@
<field name="distance_max" class="o_address_zip" <field name="distance_max" class="o_address_zip"
options="{'format': false}"/> options="{'format': false}"/>
</div> </div>
<field name="rotate_speed" string="主轴最高转速(r/min)" <field name="rotate_speed" string="主轴最高转速(r/min)"
options="{'format': false}"/> options="{'format': false}"/>
<field name="spindle_center_distance"/> <field name="spindle_center_distance"/>
<field name="spindle_continuous_power"/> <field name="spindle_continuous_power"/>
@@ -286,50 +283,50 @@
<page string="进给/精度参数"> <page string="进给/精度参数">
<group> <group>
<group string="进给参数"> <group string="进给参数">
<field name="X_axis_rapid_traverse_speed"/> <field name="X_axis_rapid_traverse_speed"/>
<field name="Y_axis_rapid_traverse_speed"/> <field name="Y_axis_rapid_traverse_speed"/>
<field name="Z_axis_rapid_traverse_speed"/> <field name="Z_axis_rapid_traverse_speed"/>
<field name="a_axis_rapid_traverse_speed"/> <field name="a_axis_rapid_traverse_speed"/>
<field name="b_axis_rapid_traverse_speed"/> <field name="b_axis_rapid_traverse_speed"/>
<field name="c_axis_rapid_traverse_speed"/> <field name="c_axis_rapid_traverse_speed"/>
<field name="straight_cutting_feed_rate"/> <field name="straight_cutting_feed_rate"/>
<field name="rotary_cutting_feed_rate"/> <field name="rotary_cutting_feed_rate"/>
</group> </group>
<group string="精度参数"> <group string="精度参数">
<field name="X_precision"/> <field name="X_precision"/>
<field name="X_precision_repeat"/> <field name="X_precision_repeat"/>
<field name="Y_precision"/> <field name="Y_precision"/>
<field name="Y_precision_repeat"/> <field name="Y_precision_repeat"/>
<field name="Z_precision"/> <field name="Z_precision"/>
<field name="Z_precision_repeat"/> <field name="Z_precision_repeat"/>
<field name="a_precision"/> <field name="a_precision"/>
<field name="a_precision_repeat"/> <field name="a_precision_repeat"/>
<field name="b_precision"/> <field name="b_precision"/>
<field name="b_precision_repeat"/> <field name="b_precision_repeat"/>
<field name="c_precision"/> <field name="c_precision"/>
<field name="c_precision_repeat"/> <field name="c_precision_repeat"/>
</group> </group>
</group> </group>
</page> </page>
<page string="刀库参数"> <page string="刀库参数">
<group> <group>
<group string="刀具"> <group string="刀具">
<!-- <field name="knife_type" required="1"/>--> <!-- <field name="knife_type" required="1"/>-->
<field name="number_of_knife_library" required="1" options="{'format': false}"/> <field name="number_of_knife_library" required="1" options="{'format': false}"/>
<!-- <field name="tool_speed" required="1"/>--> <!-- <field name="tool_speed" required="1"/>-->
<field name="tool_full_diameter_max"/> <field name="tool_full_diameter_max"/>
<field name="tool_perimeter_diameter_max"/> <field name="tool_perimeter_diameter_max"/>
<field name="tool_long_max"/> <field name="tool_long_max"/>
<!-- <label for="tool_diameter_min" string="刀具刀径(mm)"/>--> <!-- <label for="tool_diameter_min" string="刀具刀径(mm)"/>-->
<!-- <div class="test_model">--> <!-- <div class="test_model">-->
<!-- <label for="tool_diameter_min" string="最小(min)"/>--> <!-- <label for="tool_diameter_min" string="最小(min)"/>-->
<!-- <field name="tool_diameter_min" class="o_address_zip" required="1"--> <!-- <field name="tool_diameter_min" class="o_address_zip" required="1"-->
<!-- options="{'format': false}"/>--> <!-- options="{'format': false}"/>-->
<!-- <span>&amp;nbsp;</span>--> <!-- <span>&amp;nbsp;</span>-->
<!-- <label for="tool_diameter_max" string="最大(max)"/>--> <!-- <label for="tool_diameter_max" string="最大(max)"/>-->
<!-- <field name="tool_diameter_max" class="o_address_zip" required="1"--> <!-- <field name="tool_diameter_max" class="o_address_zip" required="1"-->
<!-- options="{'format': false}"/>--> <!-- options="{'format': false}"/>-->
<!-- </div>--> <!-- </div>-->
<field name="tool_quality_max"/> <field name="tool_quality_max"/>
<field name="T_tool_time"/> <field name="T_tool_time"/>
<field name="C_tool_time"/> <field name="C_tool_time"/>

View File

@@ -10,7 +10,7 @@
""", """,
'category': 'sf', 'category': 'sf',
'website': 'https://www.sf.jikimo.com', 'website': 'https://www.sf.jikimo.com',
'depends': ['sf_base', 'web_widget_model_viewer', 'mrp_subcontracting', 'purchase_stock', 'uom', ], 'depends': ['sf_base', 'mrp_subcontracting', 'purchase_stock', 'uom'],
'data': [ 'data': [
'data/product_data.xml', 'data/product_data.xml',
'data/uom_data.xml', 'data/uom_data.xml',

View File

@@ -12,7 +12,7 @@
'category': 'sf', 'category': 'sf',
'author': 'jikimo', 'author': 'jikimo',
'website': 'https://sf.cs.jikimo.com', 'website': 'https://sf.cs.jikimo.com',
'depends': ['web', 'mail', 'sf_base', 'sf_manufacturing', 'barcodes', ], 'depends': ['web', 'sf_manufacturing', 'barcodes'],
'data': [ 'data': [
# 定义权限组放在最上面 # 定义权限组放在最上面
# 权限组 # 权限组

View File

@@ -15,7 +15,7 @@ db_config = {
"user": "postgres", "user": "postgres",
"password": "postgres", "password": "postgres",
"port": "5432", "port": "5432",
"host": "172.16.10.113" "host": "172.16.10.131"
} }
@@ -24,6 +24,8 @@ def convert_to_seconds(time_str):
if time_str is None: if time_str is None:
return 0 return 0
if time_str == 0:
return 0
pattern = r"(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?" pattern = r"(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?"
@@ -1306,9 +1308,9 @@ class Sf_Dashboard_Connect(http.Controller):
res['data'][item] = { res['data'][item] = {
'wait_time': last_all_time['run_time'] if last_all_time['run_time'] is not None else 0, 'wait_time': last_all_time['run_time'] if last_all_time['run_time'] is not None else 0,
'cut_time': last_all_time['process_time'] if last_all_time['process_time'] is not None else 0, 'cut_time': last_all_time['process_time'] if last_all_time['process_time'] is not None else 0,
'cut_24_time': last_24_time['process_time'] if last_24_time['process_time'] is not None else 0, 'cut_24_time': last_24_time['process_time'] if last_24_time else 0,
'power_on_time': last_all_time['power_on_time'] if last_all_time['power_on_time'] is not None else 0, 'power_on_time': last_all_time['power_on_time'] if last_all_time['power_on_time'] is not None else 0,
'power_on_24_time': last_24_time['power_on_time'] if last_24_time['power_on_time'] is not None else 0, 'power_on_24_time': last_24_time['power_on_time'] if last_24_time else 0,
'alarm_last_24_time': alarm_last_24_time, 'alarm_last_24_time': alarm_last_24_time,
'alarm_last_24_nums': len(list(set(alarm_last_24_nums))), 'alarm_last_24_nums': len(list(set(alarm_last_24_nums))),
'idle_count': idle_count, 'idle_count': idle_count,

View File

@@ -217,7 +217,7 @@ class Machine_ftp(models.Model):
status = fields.Boolean('机床在线状态', readonly=True) status = fields.Boolean('机床在线状态', readonly=True)
# run_status = fields.Selection([('0', '空闲中'), ('1', '加工中'), ('2', '加工中'), ('3', '加工中')], string='机床运行状态', # run_status = fields.Selection([('0', '空闲中'), ('1', '加工中'), ('2', '加工中'), ('3', '加工中')], string='机床运行状态',
# readonly=True, default='0') # readonly=True, default='0')
run_status = fields.Char('机床运行状态', readonly=True) # run_status = fields.Char('机床运行状态', readonly=True)
run_time = fields.Char('机床累计运行时长', readonly=True) run_time = fields.Char('机床累计运行时长', readonly=True)
# 机床系统日期 # 机床系统日期
system_date = fields.Char('机床系统日期', readonly=True) system_date = fields.Char('机床系统日期', readonly=True)

View File

@@ -38,6 +38,8 @@ class SfMaintenanceEquipment(models.Model):
crea_url = "/api/machine_tool/create" crea_url = "/api/machine_tool/create"
run_status = fields.Char('机床运行状态', readonly=True)
# AGV运行日志 # AGV运行日志
agv_logs = fields.One2many('maintenance.equipment.agv.log', 'equipment_id', string='AGV运行日志') agv_logs = fields.One2many('maintenance.equipment.agv.log', 'equipment_id', string='AGV运行日志')
# 1212修改后的字段 # 1212修改后的字段

View File

@@ -12,6 +12,8 @@ def convert_to_seconds(time_str):
if time_str is None: if time_str is None:
return 0 return 0
if time_str == 0:
return 0
pattern = r"(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?" pattern = r"(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?"
@@ -122,24 +124,37 @@ class SfMaintenanceEquipmentOEE(models.Model):
real_dict = result_time['data'][self.equipment_code] real_dict = result_time['data'][self.equipment_code]
print('=', result_time) print('=', result_time)
if result_time['status'] == 1: if result_time['status'] == 1:
self.online_time = round((convert_to_seconds(real_dict['power_on_time']) - convert_to_seconds( if real_dict['power_on_24_time'] == 0:
real_dict['power_on_24_time'])) / 3600, 2) self.online_time = 0
self.work_time = round( self.idle_time = 0
(convert_to_seconds(real_dict['cut_time']) - convert_to_seconds(real_dict['cut_24_time'])) / 3600, self.idle_rate = 0
2) self.work_rate = 0
self.fault_time = 0
self.fault_rate = 0
self.fault_nums = 0
self.idle_nums = 0
self.work_time = 0
else:
self.online_time = round((convert_to_seconds(real_dict['power_on_time']) - convert_to_seconds(
real_dict['power_on_24_time'])) / 3600, 2)
self.idle_time = float(self.online_time) - float(
self.work_time) if self.online_time and self.work_time else 0
self.idle_rate = round(
float(self.idle_time) / (float(self.online_time) if self.online_time else 1) * 100, 2)
self.work_rate = round(
float(self.work_time) / (float(self.online_time) if self.online_time else 1) * 100, 2)
self.fault_time = (float(real_dict['alarm_last_24_time']) if real_dict[
'alarm_last_24_time'] else 0) / 3600
self.fault_rate = round(
float(self.fault_time) / (float(self.online_time) if self.online_time else 1) * 100, 2)
self.fault_nums = real_dict['alarm_last_24_nums']
self.idle_nums = real_dict['idle_count']
self.work_time = round(
(convert_to_seconds(real_dict['cut_time']) - convert_to_seconds(real_dict['cut_24_time'])) / 3600,
2)
self.offline_time = 24 - (float(self.online_time) if self.online_time else 0) self.offline_time = 24 - (float(self.online_time) if self.online_time else 0)
self.idle_time = float(self.online_time) - float(
self.work_time) if self.online_time and self.work_time else 0
self.idle_rate = round(
float(self.idle_time) / (float(self.online_time) if self.online_time else 1) * 100, 2)
self.work_rate = round(
float(self.work_time) / (float(self.online_time) if self.online_time else 1) * 100, 2)
self.fault_time = (float(real_dict['alarm_last_24_time']) if real_dict[
'alarm_last_24_time'] else 0) / 3600
self.fault_rate = round(
float(self.fault_time) / (float(self.online_time) if self.online_time else 1) * 100, 2)
self.fault_nums = real_dict['alarm_last_24_nums']
self.idle_nums = real_dict['idle_count']
if response.status_code == 200: if response.status_code == 200:
result = response.json() result = response.json()

View File

@@ -1142,14 +1142,16 @@
</div> </div>
<div class="show_state" t-attf-class="oe_kanban_global_click o_kanban_record_has_image_fill o_hr_kanban_record oe_kanban_card oe_kanban_global_click <div class="show_state" t-attf-class="oe_kanban_global_click o_kanban_record_has_image_fill o_hr_kanban_record oe_kanban_card oe_kanban_global_click
"> ">
<div t-attf-class="#{record.state.raw_value == '正常' ? 'color_1' : ''}"></div> <div t-attf-class="#{record.run_status.raw_value == '运行中' ? 'color_1' : ''}"></div>
<div t-attf-class="#{record.state.raw_value == '故障' ? 'color_2' : ''}"></div> <div t-attf-class="#{record.run_status.raw_value == '待机' ? 'color_4' : ''}"></div>
<div t-attf-class="#{record.state.raw_value == '不可用' ? 'color_3' : ''}"></div> <div t-attf-class="#{record.run_status.raw_value == '故障' ? 'color_2' : ''}"></div>
<div t-attf-class="#{record.run_status.raw_value == '离线' ? 'color_3' : ''}"></div>
<p class="o_kanban_record_bottom state_zc" <p class="o_kanban_record_bottom state_zc"
t-attf-class="#{record.state.raw_value == '正常' ? 'font_color_1' : ''} t-attf-class="#{record.run_status.raw_value == '运行中' ? 'font_color_1' : ''}
#{record.state.raw_value == '故障' ? 'font_color_2' : ''} #{record.run_status.raw_value == '待机' ? 'font_color_4' : ''}
#{record.state.raw_value == '不可用' ? 'font_color_3' : ''}"> #{record.run_status.raw_value == '故障' ? 'font_color_2' : ''}
<field name="state"/> #{record.run_status.raw_value == '离线' ? 'font_color_3' : ''}">
<field name="run_status"/>
</p> </p>
</div> </div>
</div> </div>

View File

@@ -296,8 +296,13 @@ class MrpProduction(models.Model):
# 编程单更新 # 编程单更新
def update_programming_state(self): def update_programming_state(self):
try: try:
manufacturing_type = 'rework'
if self.is_scrap:
manufacturing_type = 'scrap'
elif self.tool_state == '2':
manufacturing_type = 'invalid_tool_rework'
res = {'programming_no': self.programming_no, res = {'programming_no': self.programming_no,
'manufacturing_type': 'rework' if self.is_scrap is False else 'scrap'} 'manufacturing_type': manufacturing_type}
logging.info('res=%s:' % res) logging.info('res=%s:' % res)
configsettings = self.env['res.config.settings'].get_values() configsettings = self.env['res.config.settings'].get_values()
config_header = Common.get_headers(self, configsettings['token'], configsettings['sf_secret_key']) config_header = Common.get_headers(self, configsettings['token'], configsettings['sf_secret_key'])

View File

@@ -1,4 +1,5 @@
import datetime import datetime
import logging
from datetime import timedelta, time from datetime import timedelta, time
from collections import defaultdict from collections import defaultdict
from odoo import fields, models, api from odoo import fields, models, api
@@ -6,6 +7,8 @@ from odoo.addons.resource.models.resource import Intervals
from odoo.exceptions import UserError, ValidationError from odoo.exceptions import UserError, ValidationError
import math import math
_logger = logging.getLogger(__name__)
class ResWorkcenter(models.Model): class ResWorkcenter(models.Model):
_name = "mrp.workcenter" _name = "mrp.workcenter"
@@ -225,12 +228,16 @@ class ResWorkcenter(models.Model):
if plan_ids: if plan_ids:
sum_qty = sum([p.product_qty for p in plan_ids]) sum_qty = sum([p.product_qty for p in plan_ids])
date_planned_working_hours = self._compute_effective_working_hours_day1(date_planned) date_planned_working_hours = self._compute_effective_working_hours_day1(date_planned)
if sum_qty >= date_planned_working_hours: default_capacity = round(
self.production_line_hour_capacity * date_planned_working_hours, 2)
_logger.info('排程日期:%s,计划数量:%s,日产能:%s,日工时:%s' % (
date_planned, sum_qty, default_capacity, date_planned_working_hours))
if sum_qty >= default_capacity:
return False return False
return True return True
# 处理排程是否超过小时产能 # 处理排程是否超过小时产能
def deal_available_single_machine_capacity(self, date_planned): def deal_available_single_machine_capacity(self, date_planned, count):
date_planned_start = date_planned.strftime('%Y-%m-%d %H:00:00') 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 + timedelta(hours=1)
@@ -242,7 +249,11 @@ class ResWorkcenter(models.Model):
if plan_ids: if plan_ids:
sum_qty = sum([p.product_qty for p in plan_ids]) sum_qty = sum([p.product_qty for p in plan_ids])
if sum_qty >= self.production_line_hour_capacity: production_line_hour_capacity = self.production_line_hour_capacity
if sum_qty >= production_line_hour_capacity:
message = '当前计划开始时间不能预约排程,超过生产线小时产能(%d件)%d' % (
production_line_hour_capacity, count)
raise UserError(message)
return False return False
return True return True

View File

@@ -1197,8 +1197,8 @@ class ResMrpWorkOrder(models.Model):
if record.is_rework is False: if record.is_rework is False:
if not record.material_center_point: if not record.material_center_point:
raise UserError("坯料中心点为空,请检查") raise UserError("坯料中心点为空,请检查")
if record.X_deviation_angle <= 0: # if record.X_deviation_angle <= 0:
raise UserError("X偏差角度小于等于0请检查本次计算的X偏差角度为%s" % record.X_deviation_angle) # raise UserError("X偏差角度小于等于0请检查本次计算的X偏差角度为%s" % record.X_deviation_angle)
record.process_state = '待加工' record.process_state = '待加工'
# record.write({'process_state': '待加工'}) # record.write({'process_state': '待加工'})
record.production_id.process_state = '待加工' record.production_id.process_state = '待加工'
@@ -1827,6 +1827,11 @@ class WorkPieceDelivery(models.Model):
else: else:
raise UserError("接驳站暂未反馈站点实时状态,请稍后再试") raise UserError("接驳站暂未反馈站点实时状态,请稍后再试")
def delivery_avg(self):
is_agv_task_dispatch = self.env['ir.config_parameter'].sudo().get_param('is_agv_task_dispatch')
if is_agv_task_dispatch:
self._delivery_avg()
# 配送至avg小车 # 配送至avg小车
def _delivery_avg(self): def _delivery_avg(self):
config = self.env['res.config.settings'].get_values() config = self.env['res.config.settings'].get_values()
@@ -1886,7 +1891,7 @@ class WorkPieceDelivery(models.Model):
logging.info('delivery_item-name:%s' % delivery_item.name) logging.info('delivery_item-name:%s' % delivery_item.name)
delivery_item.write({ delivery_item.write({
'task_delivery_time': fields.Datetime.now(), 'task_delivery_time': fields.Datetime.now(),
'status': '待配送' 'status': '已下发'
}) })
if delivery_item.type == "上产线": if delivery_item.type == "上产线":
delivery_item.workorder_id.write({'is_delivery': True}) delivery_item.workorder_id.write({'is_delivery': True})

View File

@@ -562,7 +562,12 @@
</div> </div>
<field name="priority" widget="priority"/> <field name="priority" widget="priority"/>
</div> </div>
<t t-if="record.brand_id.raw_value">
<div class="mt-1">
品牌:
<field name="brand_id"></field>
</div>
</t>
<div name="product_specification_id" class="mt-1"> <div name="product_specification_id" class="mt-1">
规格: 规格:
<field name="specification_id"/> <field name="specification_id"/>

View File

@@ -223,8 +223,27 @@
</page> </page>
</xpath> </xpath>
<xpath expr="//label[1]" position="before"> <xpath expr="//label[1]" position="before">
<field name='routing_type' readonly="1"/> <!-- -->
<field name='process_state' attrs='{"invisible": [("routing_type","!=","装夹预调")]}'/> <field name="production_id" invisible="0"/>
<field name="duration_expected" invisible="1"/>
<field name="date_planned_start" invisible="1"/>
<field name="date_planned_finished" invisible="1"/>
<field name="duration" widget="mrp_timer"
invisible="1" sum="real duration"/>
<field name="glb_file" readonly="1" widget="Viewer3D" string="加工模型"/>
<field name="manual_quotation" readonly="1"
attrs="{'invisible': [('routing_type', '!=', 'CNC加工')]}"/>
<field name="processing_panel" readonly="1"
attrs='{"invisible": [("routing_type","in",("获取CNC加工程序","切割"))]}'/>
<field name="equipment_id" readonly="1"
attrs='{"invisible": [("routing_type","in",("获取CNC加工程序","切割","装夹预调"))]}'/>
<field name="production_line_id"
attrs='{"invisible": [("routing_type","in",("获取CNC加工程序","切割","装夹预调"))]}'/>
<field name="production_line_state" readonly="1"
attrs='{"invisible": [("routing_type","in",("获取CNC加工程序","切割","装夹预调"))]}'/>
<field name='routing_type' invisible="1"/>
<field name='process_state' invisible="1"/>
<field name='tag_type' readonly="1" attrs='{"invisible": [("tag_type","=",False)]}' <field name='tag_type' readonly="1" attrs='{"invisible": [("tag_type","=",False)]}'
decoration-danger="tag_type == '重新加工'"/> decoration-danger="tag_type == '重新加工'"/>
<field name="rfid_code" force_save="1" readonly="0" cache="True" <field name="rfid_code" force_save="1" readonly="0" cache="True"
@@ -232,6 +251,24 @@
<field name="rfid_code_old" readonly="1" attrs="{'invisible': [('rfid_code_old', '=', False)]}"/> <field name="rfid_code_old" readonly="1" attrs="{'invisible': [('rfid_code_old', '=', False)]}"/>
</xpath> </xpath>
<xpath expr="//form//sheet//group//group//div[2]" position="replace">
</xpath>
<xpath expr="//form//sheet//group//group//div[1]" position="after">
<field name="save_name" widget="CopyClipboardChar"
attrs="{'invisible':[('routing_type','!=','装夹预调')]}"/>
<label for="material_length" string="物料尺寸"/>
<div class="o_address_format">
<label for="material_length" string="长"/>
<field name="material_length" class="o_address_zip"/>
<span>&amp;nbsp;</span>
<label for="material_width" string="宽"/>
<field name="material_width" class="o_address_zip"/>
<span>&amp;nbsp;</span>
<label for="material_height" string="高"/>
<field name="material_height" class="o_address_zip"/>
</div>
<field name="part_number" string="成品的零件图号"/>
</xpath>
<xpath expr="//label[1]" position="attributes"> <xpath expr="//label[1]" position="attributes">
<attribute name="string">计划加工时间</attribute> <attribute name="string">计划加工时间</attribute>
</xpath> </xpath>
@@ -248,76 +285,12 @@
<field name='materiel_width' string="宽"/> <field name='materiel_width' string="宽"/>
<field name='materiel_height' string="高"/> <field name='materiel_height' string="高"/>
</xpath> </xpath>
<field name="production_id" position="after" invisible="0">
<group>
<field name="date_planned_start" invisible="1"/>
<field name="date_planned_finished" invisible="1"/>
<!-- <field name="production_id" readonly="1"/>-->
<field name="duration" widget="mrp_timer"
attrs="{'invisible': [('production_state','=', 'draft')], 'readonly': [('is_user_working', '=', True)]}"
sum="real duration"/>
<field name="glb_file" readonly="1" widget="Viewer3D" string="加工模型"/>
<field name="manual_quotation" readonly="1"
attrs="{'invisible': [('routing_type', '!=', 'CNC加工')]}"/>
<field name="processing_panel" readonly="1"
attrs='{"invisible": [("routing_type","in",("获取CNC加工程序","切割"))]}'/>
<field name="equipment_id" readonly="1"
attrs='{"invisible": [("routing_type","in",("获取CNC加工程序","切割","装夹预调"))]}'/>
<field name="production_line_id"
attrs='{"invisible": [("routing_type","in",("获取CNC加工程序","切割"))]}'/>
<field name="production_line_state" readonly="0"
attrs='{"invisible": [("routing_type","in",("获取CNC加工程序","切割","装夹预调"))]}'/>
<!-- <field name="functional_fixture_id" -->
<!-- attrs='{"invisible": [("routing_type","!=","装夹预调")]}'/> -->
<!-- <field name="functional_fixture_code" force_save="1" -->
<!-- attrs='{"invisible": [("routing_type","!=","装夹预调")]}'/> -->
<!-- <field name="functional_fixture_type_id" -->
<!-- attrs='{"invisible": [("routing_type","!=","装夹预调")]}'/> -->
</group>
<!-- <group>-->
<!-- <div>-->
<!-- <label for="glb_file" string="加工模型"/>-->
<!-- <field name="glb_file" readonly="1" widget="Viewer3D"/>-->
<!-- </div>-->
<!-- &lt;!&ndash; <field name="glb_file" string="模型" readonly="1" widget="Viewer3D"/>&ndash;&gt;-->
<!-- </group>-->
<!-- <field name="processing_panel" readonly="1" attrs="{'invisible': [('routing_type', 'in', ('获取CNC加工程序','装夹','解除装夹',-->
<!-- '前置三元定位检测','后置三元质量检测','解除装夹'))]}"/>-->
</field>
<!-- <page string="Components" name="components">-->
<xpath expr="//page[1]" position="before"> <xpath expr="//page[1]" position="before">
<!-- <page string="装夹托盘" attrs='{"invisible": [("routing_type","!=","装夹")]}'>-->
<!-- <group>-->
<!-- <field name="routing_type" invisible="1"/>-->
<!-- <field name="tray_code"/>-->
<!-- <field name="tray_id" readonly="1"/>-->
<!-- </group>-->
<!-- <group>-->
<!-- <field name="pro_code" readonly="1" attrs='{"invisible": [("pro_code_ok","=",False)]}'-->
<!-- style="color:green"/>-->
<!-- <field name="pro_code" readonly="1" attrs='{"invisible": [("pro_code_ok","!=",False)]}'/>-->
<!-- <div>-->
<!-- <field name="pro_code_ok" invisible="1"/>-->
<!-- </div>-->
<!-- </group>-->
<!-- <div class="col-12 col-lg-6 o_setting_box">-->
<!-- <button type="object" class="oe_highlight" name="gettray" string="绑定托盘"-->
<!-- attrs='{"invisible": ["|","|",("tray_id","!=",False),("state","!=","progress"),("production_id","=",False)]}'/>-->
<!-- </div>-->
<!-- </page>-->
<page string="工件装夹" attrs='{"invisible": [("routing_type","!=","装夹预调")]}'> <page string="工件装夹" attrs='{"invisible": [("routing_type","!=","装夹预调")]}'>
<group> <group>
<field name="_barcode_scanned" widget="barcode_handler"/> <field name="_barcode_scanned" widget="barcode_handler"/>
<!-- <group string="卡盘">-->
<!-- <field name="chuck_serial_number"/>-->
<!-- <field name="chuck_name"/>-->
<!-- <field name="chuck_brand_id"/>-->
<!-- <field name="chuck_type_id"/>-->
<!-- <field name="chuck_model_id"/>-->
<!-- </group>-->
<group string="托盘"> <group string="托盘">
<field name="tray_serial_number" readonly="1" string="序列号"/> <field name="tray_serial_number" readonly="1" string="序列号"/>
</group> </group>
@@ -332,10 +305,6 @@
<field name="tray_model_id" readonly="1" string="型号"/> <field name="tray_model_id" readonly="1" string="型号"/>
</group> </group>
</group> </group>
<group string="加工图纸">
<!-- 隐藏加工图纸字段名 -->
<field name="processing_drawing" widget="pdf_viewer" string="" readonly="1"/>
</group>
<group string="预调程序信息"> <group string="预调程序信息">
<field name="preset_program_information" colspan="2" nolabel="1" <field name="preset_program_information" colspan="2" nolabel="1"
placeholder="如有预调程序信息请在此处输入....."/> placeholder="如有预调程序信息请在此处输入....."/>
@@ -574,9 +543,7 @@
<!-- <field name="button_state" invisible="1"/>--> <!-- <field name="button_state" invisible="1"/>-->
</tree> </tree>
</field> </field>
<group>
<field name="cnc_worksheet" string="工作指令" widget="pdf_viewer"/>
</group>
</page> </page>
<page string="CMM程序" attrs='{"invisible": [("routing_type","!=","CNC加工")]}'> <page string="CMM程序" attrs='{"invisible": [("routing_type","!=","CNC加工")]}'>
<field name="cmm_ids" widget="one2many" string="CMM程序" readonly="1"> <field name="cmm_ids" widget="one2many" string="CMM程序" readonly="1">
@@ -605,33 +572,43 @@
</page> </page>
</xpath> </xpath>
<xpath expr="//form//sheet//group//group//div[1]" position="after"> <!-- <xpath expr="//form//sheet//group//group//div[1]" position="after">-->
<label for="date_start" string="实际加工时间"/> <!-- <label for="date_start" string="实际加工时间"/>-->
<div class="oe_inline"> <!-- <div class="oe_inline">-->
<field name="date_start" class="mr8 oe_inline"/> <!-- <field name="date_start" class="mr8 oe_inline"/>-->
<strong class="mr8 oe_inline"></strong> <!-- <strong class="mr8 oe_inline">到</strong>-->
<field name="date_finished" class="oe_inline"/> <!-- <field name="date_finished" class="oe_inline"/>-->
</div> <!-- </div>-->
</xpath> <!-- </xpath>-->
<xpath expr="//form//sheet//group//group//div[3]" position="after"> <xpath expr="//page[@name='time_tracking']//field[@name='time_ids']//tree//field[@name='date_end']"
<field name="save_name" widget="CopyClipboardChar" position="after">
attrs="{'invisible':[('routing_type','!=','装夹预调')]}"/> <field name="duration" string="实际时长"/>
<label for="material_length" string="物料尺寸"/>
<div class="o_address_format">
<label for="material_length" string="长"/>
<field name="material_length" class="o_address_zip"/>
<span>&amp;nbsp;</span>
<label for="material_width" string="宽"/>
<field name="material_width" class="o_address_zip"/>
<span>&amp;nbsp;</span>
<label for="material_height" string="高"/>
<field name="material_height" class="o_address_zip"/>
</div>
<field name="part_number" string="成品的零件图号"/>
</xpath> </xpath>
</field> </field>
</record> </record>
<record id="view_mrp_production_workorder_tray_form_inherit_sf_1" model="ir.ui.view">
<field name="name">mrp.production.workorder.tray.form.inherit.sf.1</field>
<field name="model">mrp.workorder</field>
<field name="inherit_id" ref="sf_manufacturing.view_mrp_production_workorder_tray_form_inherit_sf"/>
<field name="arch" type="xml">
<xpath expr="//form//sheet//group//group[2]" position="replace">
<group string="装夹图纸" attrs="{'invisible': [('routing_type', '!=', '装夹预调')]}">
<!-- 隐藏加工图纸字段名 -->
<field name="processing_drawing" widget="pdf_viewer" string="" readonly="1"/>
<!-- <field name="production_id" invisible="0"/>-->
</group>
<group string="工作指令" attrs="{'invisible': [('routing_type', '!=', 'CNC加工')]}">
<field name="cnc_worksheet" string="" widget="pdf_viewer"/>
</group>
</xpath>
<!-- <xpath expr="//form//sheet//group//group[1]" position="before">-->
<!-- <field name="production_id"/>-->
<!-- </xpath>-->
</field>
</record>
<record id="workcenter_form_workorder_search" model="ir.ui.view"> <record id="workcenter_form_workorder_search" model="ir.ui.view">
<field name="name">custom.workorder.search</field> <field name="name">custom.workorder.search</field>
<field name="model">mrp.workorder</field> <field name="model">mrp.workorder</field>
@@ -806,7 +783,7 @@
<field name="feeder_station_start_id" readonly="1" force_save="1"/> <field name="feeder_station_start_id" readonly="1" force_save="1"/>
<!-- <field name="type" readonly="1"/>--> <!-- <field name="type" readonly="1"/>-->
<field name="feeder_station_destination_id" readonly="1" force_save="1"/> <field name="feeder_station_destination_id" readonly="1" force_save="1"/>
<button name="button_delivery" type="object" string="配送" class="oe_highlight"/> <button name="delivery_avg" type="object" string="配送" class="oe_highlight"/>
<button name="action_delivery_history" type="object" class="btn btn-link text-info" icon="fa-history" <button name="action_delivery_history" type="object" class="btn btn-link text-info" icon="fa-history"
string="历史"/> string="历史"/>
</tree> </tree>

View File

@@ -11,7 +11,7 @@
""", """,
'category': 'sf', 'category': 'sf',
'website': 'https://www.sf.jikimo.com', 'website': 'https://www.sf.jikimo.com',
'depends': ['sale', 'purchase', 'sf_plan', 'jikimo_message_notify', 'stock', 'sf_quality'], 'depends': ['sale', 'purchase', 'sf_plan', 'jikimo_message_notify', 'stock', 'sf_quality','mrp'],
'data': [ 'data': [
'data/bussiness_node.xml', 'data/bussiness_node.xml',
'data/cron_data.xml', 'data/cron_data.xml',

View File

@@ -49,6 +49,23 @@
<field name="name">工单已下发通知</field> <field name="name">工单已下发通知</field>
<field name="model">mrp.workorder</field> <field name="model">mrp.workorder</field>
</record> </record>
<!--发货调拨-->
<record id="production_completed_warehouse_reminder" model="jikimo.message.bussiness.node">
<field name="name">生产完工入库提醒</field>
<field name="model">mrp.production</field>
</record>
<record id="order_delivery_reminder" model="jikimo.message.bussiness.node">
<field name="name">订单发货提醒</field>
<field name="model">stock.picking</field>
</record>
<!-- <record id="bussiness_mrp_workorder_pre_overdue_warning" model="jikimo.message.bussiness.node">-->
<!-- <field name="name">装夹预调工单逾期预警</field>-->
<!-- <field name="model">mrp.workorder</field>-->
<!-- </record>-->
<!-- <record id="bussiness_mrp_workorder_pre_overdue" model="jikimo.message.bussiness.node">-->
<!-- <field name="name">装夹预调工单已逾期</field>-->
<!-- <field name="model">mrp.workorder</field>-->
<!-- </record>-->
<record id="bussiness_mrp_workorder_pre_overdue_warning" model="jikimo.message.bussiness.node"> <record id="bussiness_mrp_workorder_pre_overdue_warning" model="jikimo.message.bussiness.node">

View File

@@ -187,8 +187,8 @@
<record id="template_transfer_inventory_remind" model="jikimo.message.template"> <record id="template_transfer_inventory_remind" model="jikimo.message.template">
<field name="menu_id" ref="stock.menu_stock_root"/> <!-- <field name="menu_id" ref="stock.menu_stock_root"/>-->
<field name="action_id" ref="stock.action_picking_tree_ready"/> <!-- <field name="action_id" ref="stock.action_picking_tree_ready"/>-->
<field name="name">调拨入库</field> <field name="name">调拨入库</field>
<field name="model_id" ref="stock.model_stock_picking"/> <field name="model_id" ref="stock.model_stock_picking"/>
<field name="model">stock.picking</field> <field name="model">stock.picking</field>
@@ -196,13 +196,13 @@
<field name="msgtype">markdown</field> <field name="msgtype">markdown</field>
<field name="urgency">normal</field> <field name="urgency">normal</field>
<field name="content">### 调拨入库通知: <field name="content">### 调拨入库通知:
单号:调拨入库单[{{name}}]({{request_url}}) 单号:调拨入库单[{{name}}]({{transfer_inventory_special_url}})
事项:完成刀具物料上架入库</field> 事项:完成刀具物料上架入库</field>
</record> </record>
<record id="template_tool_expired_remind" model="jikimo.message.template"> <record id="template_tool_expired_remind" model="jikimo.message.template">
<field name="menu_id" ref="mrp.menu_mrp_root"/> <!-- <field name="menu_id" ref="mrp.menu_mrp_root"/>-->
<field name="action_id" ref="sf_tool_management.sf_functional_tool_dismantle_view_act"/> <!-- <field name="action_id" ref="sf_tool_management.sf_functional_tool_dismantle_view_act"/>-->
<field name="name">功能刀具寿命到期</field> <field name="name">功能刀具寿命到期</field>
<field name="model_id" ref="sf_tool_management.model_sf_functional_tool_dismantle"/> <field name="model_id" ref="sf_tool_management.model_sf_functional_tool_dismantle"/>
<field name="model">sf.functional.tool.dismantle</field> <field name="model">sf.functional.tool.dismantle</field>
@@ -210,13 +210,13 @@
<field name="msgtype">markdown</field> <field name="msgtype">markdown</field>
<field name="urgency">normal</field> <field name="urgency">normal</field>
<field name="content">### 功能刀具寿命到期提醒: <field name="content">### 功能刀具寿命到期提醒:
单号:拆解单[{{code}}]({{request_url}}) 单号:拆解单[{{code}}]({{tool_expired_remind_special_url}})
事项:{{functional_tool_id.tool_name_id.name}}寿命已到期,需拆解</field> 事项:{{functional_tool_id.tool_name_id.name}}寿命已到期,需拆解</field>
</record> </record>
<record id="template_tool_assembly_remind" model="jikimo.message.template"> <record id="template_tool_assembly_remind" model="jikimo.message.template">
<field name="menu_id" ref="mrp.menu_mrp_root"/> <!-- <field name="menu_id" ref="mrp.menu_mrp_root"/>-->
<field name="action_id" ref="sf_tool_management.sf_functional_tool_assembly_view_act"/> <!-- <field name="action_id" ref="sf_tool_management.sf_functional_tool_assembly_view_act"/>-->
<field name="name">功能刀具组装</field> <field name="name">功能刀具组装</field>
<field name="model_id" ref="sf_tool_management.model_sf_functional_tool_assembly"/> <field name="model_id" ref="sf_tool_management.model_sf_functional_tool_assembly"/>
<field name="model">sf.functional.tool.assembly</field> <field name="model">sf.functional.tool.assembly</field>
@@ -224,8 +224,34 @@
<field name="msgtype">markdown</field> <field name="msgtype">markdown</field>
<field name="urgency">normal</field> <field name="urgency">normal</field>
<field name="content">### 功能刀具组装通知: <field name="content">### 功能刀具组装通知:
单号:组装任务单[{{name}}]({{request_url}}) 单号:组装任务单[{{name}}]({{tool_assembly_special_url}})
事项:{{use_tool_time}}前完成组装</field> 事项:{{use_tool_time}}前完成组装</field>
</record>
<record id="template_production_completed_remind" model="jikimo.message.template">
<!-- <field name="menu_id" ref="mrp.menu_mrp_root"/>-->
<!-- <field name="action_id" ref="mrp.mrp_production_action"/>-->
<field name="name">生产完工入库提醒</field>
<field name="model_id" ref="mrp.model_mrp_production"/>
<field name="model">mrp.production</field>
<field name="bussiness_node_id" ref="production_completed_warehouse_reminder"/>
<field name="msgtype">markdown</field>
<field name="urgency">normal</field>
<field name="content">### 生产完工入库提醒:
单号:生产入库单[{{name}}]({{request_url}})
事项:销售订单{{sale_order_name}}已全部产出,请入库处理</field>
</record>
<record id="template_order_delivery_remind" model="jikimo.message.template">
<!-- <field name="menu_id" ref="stock.menu_stock_root"/>-->
<!-- <field name="action_id" ref="stock.action_picking_tree_ready"/>-->
<field name="name">订单发货提醒</field>
<field name="model_id" ref="stock.model_stock_picking"/>
<field name="model">stock.picking</field>
<field name="bussiness_node_id" ref="order_delivery_reminder"/>
<field name="msgtype">markdown</field>
<field name="urgency">normal</field>
<field name="content">### 订单发货提醒:
单号:发料出库单[{{name}}]({{request_url}})
事项:销售订单{{sale_order_name}}已全部产出并入库,请及时发货</field>
</record> </record>
<record id="template_quality_cnc_test" model="jikimo.message.template"> <record id="template_quality_cnc_test" model="jikimo.message.template">

View File

@@ -7,4 +7,5 @@ from . import sf_message_functional_tool_assembly
from . import sf_message_purchase from . import sf_message_purchase
from . import sf_message_workorder from . import sf_message_workorder
from . import sf_message_functional_tool_dismantle from . import sf_message_functional_tool_dismantle
from . import sf_message_mrp_production
from . import sf_message_quality_cnc_test from . import sf_message_quality_cnc_test

View File

@@ -13,3 +13,14 @@ class SFMessagefunctionalToolAssembly(models.Model):
if obj.loading_task_source == '0' and obj.assemble_status == '0': if obj.loading_task_source == '0' and obj.assemble_status == '0':
obj.add_queue('功能刀具组装') obj.add_queue('功能刀具组装')
return result return result
def get_special_url(self,id,tmplate_name,special_name,model_id):
menu_id = 0
action_id = 0
if tmplate_name=='调拨入库' and special_name== 'tool_assembly_special_url':
menu_id = self.env.ref('mrp.menu_mrp_root').id
action_id = self.env.ref('sf_tool_management.sf_functional_tool_assembly_view_act').id
return super(SFMessagefunctionalToolAssembly, self).get_url(id, menu_id, action_id,model_id)
else:
return super(SFMessagefunctionalToolAssembly, self).get_special_url(id, tmplate_name, special_name, model_id)

View File

@@ -17,3 +17,13 @@ class SFMessagefunctionalToolDismantle(models.Model):
if obj.dismantle_cause in ['寿命到期报废', '崩刀报废'] and obj.state == '待拆解': if obj.dismantle_cause in ['寿命到期报废', '崩刀报废'] and obj.state == '待拆解':
obj.add_queue('功能刀具寿命到期') obj.add_queue('功能刀具寿命到期')
return result return result
def get_special_url(self,id,tmplate_name,special_name,model_id):
menu_id = 0
action_id = 0
if tmplate_name=='调拨入库' and special_name== 'tool_expired_remind_special_url':
menu_id = self.env.ref('mrp.menu_mrp_root').id
action_id = self.env.ref('sf_tool_management.sf_functional_tool_dismantle_view_act').id
return super(SFMessagefunctionalToolDismantle, self).get_url(id, menu_id, action_id,model_id)
else:
return super(SFMessagefunctionalToolDismantle, self).get_special_url(id, tmplate_name, special_name, model_id)

View File

@@ -0,0 +1,55 @@
import re
from odoo import models, fields, api, _
from urllib.parse import urlencode
class SFMessageMrpProduction(models.Model):
_name = 'mrp.production'
_description = "制造订单"
_inherit = ['mrp.production', 'jikimo.message.dispatch']
@api.depends(
'move_raw_ids.state', 'move_raw_ids.quantity_done', 'move_finished_ids.state',
'workorder_ids.state', 'product_qty', 'qty_producing')
def _compute_state(self):
super(SFMessageMrpProduction, self)._compute_state()
for record in self:
if record.state in ['scrap', 'done']:
# 查询制造订单下的所有未完成的生产订单
mrp_production = record.env['mrp.production'].search(
[('origin', '=', record.origin), ('state', 'not in', ['scrap', 'done'])])
if not mrp_production:
mrp_production_queue = self.env["jikimo.message.queue"].search([('res_id', '=', record.id)])
if not mrp_production_queue:
record.add_queue('生产完工入库提醒')
# 获取发送消息内容
def _get_message(self, message_queue_ids):
contents = []
for message_queue_id in message_queue_ids:
if message_queue_id.message_template_id.name == '生产完工入库提醒':
content = message_queue_id.message_template_id.content
mrp_production = self.env['mrp.production'].search([('id', '=', int(message_queue_id.res_id))])
if mrp_production and len(mrp_production) > 0:
stock_picking_sfp = self.env['stock.picking'].search(
[('origin', '=', mrp_production.origin), ('picking_type_id.sequence_code', '=', 'SFP'),
('state', '=', 'assigned')], limit=1)
if stock_picking_sfp:
url = self.request_url()
content = content.replace('{{name}}', stock_picking_sfp.name).replace(
'{{sale_order_name}}', mrp_production.origin).replace('{{request_url}}', url)
contents.append(content)
return contents
def request_url(self):
url = self.env['ir.config_parameter'].get_param('web.base.url')
action_id = self.env.ref('mrp.mrp_production_action').id
menu_id = self.env['ir.model.data'].search([('name', '=', 'module_theme_treehouse')]).id
# 查询参数
params = {'menu_id': menu_id, 'action': action_id, 'model': 'mrp.production',
'view_type': 'kanban'}
# 拼接查询参数
query_string = urlencode(params)
# 拼接URL
full_url = url + "/web#" + query_string
return full_url

View File

@@ -19,9 +19,7 @@ class SFMessagePurchase(models.Model):
return contents return contents
def request_url(self, id): def request_url(self, id):
we_config_info = self.env['we.config'].sudo().search([], limit=1) url = self.env['ir.config_parameter'].get_param('web.base.url')
redirect_domain = self.env['we.app'].sudo().search([('id', '=', we_config_info.odoo_app_id.id)]).redirect_domain
full_url = 'https://%s/' % redirect_domain
action_id = self.env.ref('purchase.purchase_form_action').id action_id = self.env.ref('purchase.purchase_form_action').id
menu_id = self.env['ir.model.data'].search([('name', '=', 'module_website_payment')]).id menu_id = self.env['ir.model.data'].search([('name', '=', 'module_website_payment')]).id
# 查询参数 # 查询参数
@@ -31,5 +29,5 @@ class SFMessagePurchase(models.Model):
# 拼接查询参数 # 拼接查询参数
query_string = urlencode(params) query_string = urlencode(params)
# 拼接URL # 拼接URL
full_url = full_url + "web#" + query_string full_url = url + "/web#" + query_string
return full_url return full_url

View File

@@ -8,6 +8,7 @@ class SFMessageSale(models.Model):
_name = 'sale.order' _name = 'sale.order'
_inherit = ['sale.order', 'jikimo.message.dispatch'] _inherit = ['sale.order', 'jikimo.message.dispatch']
@api.model_create_multi
def create(self, vals_list): def create(self, vals_list):
res = super(SFMessageSale, self).create(vals_list) res = super(SFMessageSale, self).create(vals_list)
if res: if res:

View File

@@ -23,6 +23,28 @@ class SFMessageStockPicking(models.Model):
if record.state == 'assigned' and record.check_in == 'PC': if record.state == 'assigned' and record.check_in == 'PC':
record.add_queue('坯料发料提醒') record.add_queue('坯料发料提醒')
if record.picking_type_id.sequence_code == 'SFP' and record.state == 'done':
stock_picking_sfp = record.env['stock.picking'].search(
[('origin', '=', record.origin), ('state', '!=', 'done'),
('picking_type_id.sequence_code', '=', 'SFP')])
if not stock_picking_sfp:
stock_picking_send = self.env["jikimo.message.queue"].search([('res_id', '=', record.id)])
if not stock_picking_send:
record.add_queue('订单发货提醒')
def deal_stock_picking_sfp(self, message_queue_id): # 处理订单发货提醒
content = None
stock_picking = self.env['stock.picking'].search([('id', '=', int(message_queue_id.res_id))])
stock_picking_out = self.env['stock.picking'].search(
[('origin', '=', stock_picking.origin), ('state', '=', 'assigned'),
('picking_type_id.sequence_code', '=', 'OUT')])
if stock_picking_out and len(stock_picking_out) > 0:
content = message_queue_id.message_template_id.content
url = self.request_url()
content = content.replace('{{name}}', stock_picking_out.name).replace(
'{{sale_order_name}}', stock_picking_out.origin).replace('{{request_url}}', url)
return content
def _get_message(self, message_queue_ids): def _get_message(self, message_queue_ids):
contents = [] contents = []
product_id = [] product_id = []
@@ -46,15 +68,24 @@ class SFMessageStockPicking(models.Model):
'{{number}}', str(i)).replace('{{request_url}}', url) '{{number}}', str(i)).replace('{{request_url}}', url)
product_id.append(mrp_production_info.product_id.id) product_id.append(mrp_production_info.product_id.id)
contents.append(content) contents.append(content)
return contents elif message_queue_id.message_template_id.name == '订单发货提醒':
else: content = self.deal_stock_picking_sfp(message_queue_id)
res = super(SFMessageStockPicking, self)._get_message(message_queue_id) if content:
return res contents.append(content)
return contents
def get_special_url(self, id, tmplate_name, special_name, model_id):
menu_id = 0
action_id = 0
if tmplate_name == '调拨入库' and special_name == 'transfer_inventory_special_url':
menu_id = self.env.ref('stock.menu_stock_root').id
action_id = self.env.ref('stock.action_picking_tree_ready').id
return super(SFMessageStockPicking, self).get_url(id, menu_id, action_id, model_id)
else:
return super(SFMessageStockPicking, self).get_special_url(id, tmplate_name, special_name, model_id)
def request_url(self): def request_url(self):
we_config_info = self.env['we.config'].sudo().search([], limit=1) url = self.env['ir.config_parameter'].get_param('web.base.url')
redirect_domain = self.env['we.app'].sudo().search([('id', '=', we_config_info.odoo_app_id.id)]).redirect_domain
full_url = 'https://%s/' % redirect_domain
action_id = self.env.ref('stock.stock_picking_type_action').id action_id = self.env.ref('stock.stock_picking_type_action').id
menu_id = self.env['ir.model.data'].search([('name', '=', 'module_theme_treehouse')]).id menu_id = self.env['ir.model.data'].search([('name', '=', 'module_theme_treehouse')]).id
# 查询参数 # 查询参数
@@ -63,5 +94,5 @@ class SFMessageStockPicking(models.Model):
# 拼接查询参数 # 拼接查询参数
query_string = urlencode(params) query_string = urlencode(params)
# 拼接URL # 拼接URL
full_url = full_url + "web#" + query_string full_url = url + "/web#" + query_string
return full_url return full_url

View File

@@ -84,9 +84,7 @@ class SFMessageWork(models.Model):
return contents return contents
def request_url(self): def request_url(self):
we_config_info = self.env['we.config'].sudo().search([], limit=1) url = self.env['ir.config_parameter'].get_param('web.base.url')
redirect_domain = self.env['we.app'].sudo().search([('id', '=', we_config_info.odoo_app_id.id)]).redirect_domain
full_url = 'https://%s/' % redirect_domain
action_id = self.env.ref('sf_manufacturing.mrp_workorder_action_tablet').id action_id = self.env.ref('sf_manufacturing.mrp_workorder_action_tablet').id
menu_id = self.env['ir.model.data'].search([('name', '=', 'module_stock_dropshipping')]).id menu_id = self.env['ir.model.data'].search([('name', '=', 'module_stock_dropshipping')]).id
# 查询参数 # 查询参数
@@ -95,7 +93,7 @@ class SFMessageWork(models.Model):
# 拼接查询参数 # 拼接查询参数
query_string = urlencode(params) query_string = urlencode(params)
# 拼接URL # 拼接URL
full_url = full_url + "web#" + query_string full_url = url + "/web#" + query_string
return full_url return full_url
def _overdue_or_warning_func(self): def _overdue_or_warning_func(self):

View File

@@ -25,7 +25,7 @@ class Sf_Mrs_Connect(http.Controller):
ret = json.loads(ret['result']) ret = json.loads(ret['result'])
logging.info('下发编程单:%s' % ret) logging.info('下发编程单:%s' % ret)
domain = [('programming_no', '=', ret['programming_no'])] domain = [('programming_no', '=', ret['programming_no'])]
if ret['manufacturing_type'] == 'scrap': if ret['manufacturing_type'] in ('scrap', 'invalid_tool_rework'):
domain += [('state', 'not in', ['done', 'scrap', 'cancel'])] domain += [('state', 'not in', ['done', 'scrap', 'cancel'])]
productions = request.env['mrp.production'].with_user( productions = request.env['mrp.production'].with_user(
request.env.ref("base.user_admin")).search(domain) request.env.ref("base.user_admin")).search(domain)
@@ -96,6 +96,14 @@ class Sf_Mrs_Connect(http.Controller):
res.update({ res.update({
'production_ids': productions.ids 'production_ids': productions.ids
}) })
# 对制造订单所以面的cnc工单的程序用刀进行校验
try:
productions.production_cnc_tool_checkout()
except Exception as e:
logging.info(f'对cnc工单的程序用刀进行校验报错{e}')
return json.JSONEncoder().encode(res)
return json.JSONEncoder().encode(res) return json.JSONEncoder().encode(res)
else: else:
res = {'status': 0, 'message': '该制造订单暂未开始'} res = {'status': 0, 'message': '该制造订单暂未开始'}

View File

@@ -18,6 +18,7 @@ class OrderPrice(models.Model):
return True return True
except ValueError: except ValueError:
return False return False
@api.depends('sale_order_id.order_line.remark')
def _compute_bfm_amount_total(self): def _compute_bfm_amount_total(self):
for record in self: for record in self:
amount_total = 0 amount_total = 0

View File

@@ -75,7 +75,7 @@ class sf_production_plan(models.Model):
if self.date_planned_start: if self.date_planned_start:
self.date_planned_finished = self.date_planned_start + timedelta(hours=1) self.date_planned_finished = self.date_planned_start + timedelta(hours=1)
#处理计划状态非待排程,计划结束时间为空的数据处理 # 处理计划状态非待排程,计划结束时间为空的数据处理
def deal_no_date_planned_finished(self): def deal_no_date_planned_finished(self):
plans = self.env['sf.production.plan'].search( plans = self.env['sf.production.plan'].search(
[('date_planned_finished', '=', False), ('state', 'in', ['processing', 'done', 'finished'])]) [('date_planned_finished', '=', False), ('state', 'in', ['processing', 'done', 'finished'])])
@@ -90,6 +90,7 @@ class sf_production_plan(models.Model):
for item in plans: for item in plans:
if item.date_planned_start: if item.date_planned_start:
item.order_deadline = item.date_planned_start + timedelta(days=7) item.order_deadline = item.date_planned_start + timedelta(days=7)
@api.model @api.model
def search_read(self, domain=None, fields=None, offset=0, limit=None, order=None): def search_read(self, domain=None, fields=None, offset=0, limit=None, order=None):
@@ -218,7 +219,7 @@ class sf_production_plan(models.Model):
return num return num
def do_production_schedule(self): def do_production_schedule(self, count=1):
""" """
排程方法 排程方法
""" """
@@ -226,7 +227,7 @@ class sf_production_plan(models.Model):
if not record.production_line_id: if not record.production_line_id:
raise ValidationError("未选择生产线") raise ValidationError("未选择生产线")
else: else:
is_schedule = self.deal_processing_schedule(record.date_planned_start) is_schedule = self.deal_processing_schedule(record.date_planned_start, count)
if not is_schedule: if not is_schedule:
raise ValidationError("排程失败") raise ValidationError("排程失败")
workorder_id_list = record.production_id.workorder_ids.ids workorder_id_list = record.production_id.workorder_ids.ids
@@ -281,7 +282,7 @@ class sf_production_plan(models.Model):
} }
# 处理是否可排程 # 处理是否可排程
def deal_processing_schedule(self, date_planned_start): def deal_processing_schedule(self, date_planned_start, count):
for record in self: for record in self:
workcenter_ids = record.production_line_id.mrp_workcenter_ids workcenter_ids = record.production_line_id.mrp_workcenter_ids
if not workcenter_ids: if not workcenter_ids:
@@ -296,7 +297,7 @@ class sf_production_plan(models.Model):
raise UserError('当前计划开始时间不能预约排程,请在工作时间内排程') raise UserError('当前计划开始时间不能预约排程,请在工作时间内排程')
if not production_lines.deal_available_default_capacity(date_planned_start): # 判断生产线是否可排程 if not production_lines.deal_available_default_capacity(date_planned_start): # 判断生产线是否可排程
raise UserError('当前计划开始时间不能预约排程,生产线今日没有可排程的资源') raise UserError('当前计划开始时间不能预约排程,生产线今日没有可排程的资源')
if not production_lines.deal_available_single_machine_capacity(date_planned_start): # 判断生产线是否可排程 if not production_lines.deal_available_single_machine_capacity(date_planned_start, count): # 判断生产线是否可排程
raise UserError('当前计划开始时间不能预约排程,生产线该时间段没有可排程的资源') raise UserError('当前计划开始时间不能预约排程,生产线该时间段没有可排程的资源')
return True return True

View File

@@ -339,7 +339,7 @@
name="空料架配送" name="空料架配送"
sequence="11" sequence="11"
action="sf_manufacturing.sf_workpiece_delivery_empty_racks_act" action="sf_manufacturing.sf_workpiece_delivery_empty_racks_act"
groups="base.group_system" groups="sf_base.group_sf_order_user,sf_base.group_sf_mrp_manager,sf_base.group_sf_equipment_user"
parent="mrp.menu_mrp_manufacturing" parent="mrp.menu_mrp_manufacturing"
/> />
<!-- <menuitem --> <!-- <menuitem -->

View File

@@ -33,7 +33,9 @@ class Action_Plan_All_Wizard(models.TransientModel):
# 使用传递过来的计划ID # 使用传递过来的计划ID
temp_plan_ids = self.plan_ids temp_plan_ids = self.plan_ids
# 在这里添加您的逻辑来处理这些ID # 在这里添加您的逻辑来处理这些ID
count = len(temp_plan_ids) + 1
for plan in temp_plan_ids: for plan in temp_plan_ids:
count = count - 1
# 处理每个计划 # 处理每个计划
# 比如更新计划状态、分配资源等 # 比如更新计划状态、分配资源等
# 示例plan.state = 'scheduled' # 示例plan.state = 'scheduled'
@@ -42,7 +44,7 @@ class Action_Plan_All_Wizard(models.TransientModel):
plan_obj = self.env['sf.production.plan'].browse(plan.id) plan_obj = self.env['sf.production.plan'].browse(plan.id)
plan_obj.production_line_id = self.production_line_id.id plan_obj.production_line_id = self.production_line_id.id
plan.date_planned_start = self.date_planned_start plan.date_planned_start = self.date_planned_start
plan_obj.do_production_schedule() plan_obj.do_production_schedule(count)
# plan_obj.state = 'done' # plan_obj.state = 'done'
print('处理计划:', plan.id, '完成') print('处理计划:', plan.id, '完成')

View File

@@ -17,11 +17,6 @@
<xpath expr="//field[@name='user_id']" position="replace"> <xpath expr="//field[@name='user_id']" position="replace">
<field name="user_id" widget="many2one_avatar_user" context="{'is_sale': True }"/> <field name="user_id" widget="many2one_avatar_user" context="{'is_sale': True }"/>
</xpath> </xpath>
<xpath expr="//form/header/button[@name='action_quotation_send'][1]" position="replace">
<button name="action_quotation_send" string="通过EMAIL发送" type="object"
class="btn-primary" data-hotkey="g" context="{'validate_analytic': True}"
attrs="{'invisible': ['|','&amp;',('check_status', '!=', 'approved'),('state', 'in', ['draft','cancel']),'&amp;',('check_status', '=', 'approved'),('state', 'in', ['sale','cancel'])]}"/>
</xpath>
<xpath expr="//form/header/button[@name='action_confirm']" position="after"> <xpath expr="//form/header/button[@name='action_confirm']" position="after">
<button name="submit" string="提交" type="object" <button name="submit" string="提交" type="object"
context="{'default_order_id':active_id}" context="{'default_order_id':active_id}"
@@ -52,6 +47,39 @@
</attribute> </attribute>
<attribute name="string">拒绝接单</attribute> <attribute name="string">拒绝接单</attribute>
</xpath> </xpath>
<!-- ======================= 销售订单按钮顺序优化 start ======================= -->
<xpath expr="//form/header/button[@name='action_quotation_send'][1]" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//form/header/button[@name='action_quotation_send'][4]" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//form/header/button[@id='create_invoice']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//form/header/button[@id='create_invoice_percentage']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//form/header/button[@name='action_cancel']" position="after">
<button id="create_invoice" name="%(sale.action_view_sale_advance_payment_inv)d"
string="创建结算单"
type="action" class="btn-primary" data-hotkey="q"
attrs="{'invisible': [('invoice_status', '!=', 'to invoice')]}"/>
<button id="create_invoice_percentage" name="%(sale.action_view_sale_advance_payment_inv)d"
string="创建结算单"
type="action" context="{'default_advance_payment_method': 'percentage'}" data-hotkey="q"
attrs="{'invisible': ['|',('invoice_status', '!=', 'no'), ('state', '!=', 'sale')]}"/>
<button name="action_quotation_send" string="通过EMAIL发送" type="object"
class="btn-primary" data-hotkey="g" context="{'validate_analytic': True}"
attrs="{'invisible': ['|','&amp;',('check_status', '!=', 'approved'),('state', 'in', ['draft','cancel']),'&amp;',('check_status', '=', 'approved'),('state', 'in', ['sale','cancel'])]}"/>
<button name="action_quotation_send" string="通过EMAIL发送" type="object" states="sent,sale"
data-hotkey="g" context="{'validate_analytic': True}"/>
</xpath>
<!-- ====================== 销售订单按钮顺序优化 end======================== -->
<xpath expr="//form/header/button[@name='action_draft']" position="attributes"> <xpath expr="//form/header/button[@name='action_draft']" position="attributes">
<attribute name="invisible">1</attribute> <attribute name="invisible">1</attribute>
</xpath> </xpath>

View File

@@ -6,7 +6,7 @@
'summary': '智能工厂刀具管理', 'summary': '智能工厂刀具管理',
'sequence': 1, 'sequence': 1,
'description': """ 'description': """
在本模块,定义了主要的角色、菜单、基础业务对象 在本模块,定义了刀具相关的模型和视图,以及相关的业务逻辑。
""", """,
'category': 'sf', 'category': 'sf',
'website': 'https://www.sf.jikimo.com', 'website': 'https://www.sf.jikimo.com',

View File

@@ -234,7 +234,7 @@ class CAMWorkOrderProgramKnifePlan(models.Model):
sf_functional_tool_assembly_id = fields.Many2one('sf.functional.tool.assembly', '功能刀具组装', readonly=True) sf_functional_tool_assembly_id = fields.Many2one('sf.functional.tool.assembly', '功能刀具组装', readonly=True)
active = fields.Boolean(string='已归档', default=True) active = fields.Boolean(string='已归档', default=True, groups='base.user_root')
@api.depends('functional_tool_name') @api.depends('functional_tool_name')
def _compute_tool_number(self): def _compute_tool_number(self):
@@ -314,37 +314,41 @@ class CAMWorkOrderProgramKnifePlan(models.Model):
'applicant': None, 'applicant': None,
'sf_functional_tool_assembly_id': None}) 'sf_functional_tool_assembly_id': None})
def create_cam_work_plan(self, cnc_processing): def create_cam_work_plan(self, cnc_ids):
""" """
根据传入的工单信息查询是否有需要的功能刀具如果没有则生成CAM工单程序用刀计划 根据传入的工单信息查询是否有需要的功能刀具如果没有则生成CAM工单程序用刀计划
""" """
# 获取编程单号 # 获取编程单号
programming_no = cnc_processing.workorder_id.production_id.programming_no programming_no = cnc_ids[0].workorder_id.production_id.programming_no
logging.info(f'编程单号:{programming_no}') logging.info(f'编程单号:{programming_no}')
cam_id = self.env['sf.cam.work.order.program.knife.plan'].sudo().search( for cnc_processing in cnc_ids:
[('programming_no', '=', programming_no), tool_name = cnc_processing.cutting_tool_name
('functional_tool_name', '=', cnc_processing.cutting_tool_name)]) cam_id = self.env['sf.cam.work.order.program.knife.plan'].sudo().search(
logging.info(f'CAM装刀计划{cam_id}') [('programming_no', '=', programming_no),
if not cam_id: ('functional_tool_name', '=', tool_name)])
knife_plan = self.env['sf.cam.work.order.program.knife.plan'].sudo().create({ if cam_id:
'name': cnc_processing.workorder_id.production_id.name, logging.info(f'编程单号{programming_no}功能刀具名称{tool_name}已存在CAM装刀计划{cam_id}')
'programming_no': programming_no, else:
'cam_procedure_code': cnc_processing.program_name, knife_plan = self.env['sf.cam.work.order.program.knife.plan'].sudo().create({
'filename': cnc_processing.cnc_id.name, 'name': cnc_processing.workorder_id.production_id.name,
'functional_tool_name': cnc_processing.cutting_tool_name, 'programming_no': programming_no,
'cam_cutter_spacing_code': cnc_processing.cutting_tool_no, 'cam_procedure_code': cnc_processing.program_name,
'process_type': cnc_processing.processing_type, 'filename': cnc_processing.cnc_id.name,
'margin_x_y': float(cnc_processing.margin_x_y), 'functional_tool_name': tool_name,
'margin_z': float(cnc_processing.margin_z), 'cam_cutter_spacing_code': cnc_processing.cutting_tool_no,
'finish_depth': float(cnc_processing.depth_of_processing_z), 'process_type': cnc_processing.processing_type,
'extension_length': float(cnc_processing.cutting_tool_extension_length), 'margin_x_y': float(cnc_processing.margin_x_y),
'shank_model': cnc_processing.cutting_tool_handle_type, 'margin_z': float(cnc_processing.margin_z),
'estimated_processing_time': cnc_processing.estimated_processing_time, 'finish_depth': float(cnc_processing.depth_of_processing_z),
}) 'extension_length': float(cnc_processing.cutting_tool_extension_length),
logging.info('CAM工单程序用刀计划创建成功') 'shank_model': cnc_processing.cutting_tool_handle_type,
# 创建装刀请求 'estimated_processing_time': cnc_processing.estimated_processing_time,
knife_plan.apply_for_tooling() })
logging.info(f'创建CAM工单程序用刀计划{knife_plan}')
# 创建装刀请求
knife_plan.apply_for_tooling()
logging.info('CAM工单程序用刀计划创建已完成')
def unlink_cam_plan(self, production): def unlink_cam_plan(self, production):
for item in production: for item in production:
@@ -683,7 +687,7 @@ class FunctionalToolAssembly(models.Model):
sf_cam_work_order_program_knife_plan_id = fields.Many2one('sf.cam.work.order.program.knife.plan', sf_cam_work_order_program_knife_plan_id = fields.Many2one('sf.cam.work.order.program.knife.plan',
'CAM工单程序用刀计划', readonly=True) 'CAM工单程序用刀计划', readonly=True)
active = fields.Boolean(string='已归档', default=True) active = fields.Boolean(string='已归档', default=True, groups='base.user_root')
code = fields.Char('功能刀具编码', compute='_compute_code') code = fields.Char('功能刀具编码', compute='_compute_code')
@@ -1275,7 +1279,7 @@ class FunctionalToolDismantle(models.Model):
item.picking_num = 0 item.picking_num = 0
state = fields.Selection([('待拆解', '待拆解'), ('已拆解', '已拆解')], default='待拆解', tracking=True) state = fields.Selection([('待拆解', '待拆解'), ('已拆解', '已拆解')], default='待拆解', tracking=True)
active = fields.Boolean('有效', default=True) active = fields.Boolean('有效', default=True, groups='base.user_root')
# 刀柄 # 刀柄
handle_product_id = fields.Many2one('product.product', string='刀柄', compute='_compute_functional_tool_num', handle_product_id = fields.Many2one('product.product', string='刀柄', compute='_compute_functional_tool_num',

View File

@@ -402,7 +402,7 @@ class FunctionalToolWarning(models.Model):
dispose_time = fields.Char('处理时间', readonly=True) dispose_time = fields.Char('处理时间', readonly=True)
dispose_func = fields.Char('处理方法/措施', readonly=True) dispose_func = fields.Char('处理方法/措施', readonly=True)
active = fields.Boolean(string='已归档', default=True) active = fields.Boolean(string='已归档', default=True, groups='base.user_root')
functional_tool_name_id = fields.Many2one('sf.functional.tool.assembly', string='功能刀具名称') functional_tool_name_id = fields.Many2one('sf.functional.tool.assembly', string='功能刀具名称')
@@ -554,7 +554,7 @@ class RealTimeDistributionOfFunctionalTools(models.Model):
sf_functional_tool_entity_ids = fields.One2many('sf.functional.cutting.tool.entity', 'safe_inventory_id', sf_functional_tool_entity_ids = fields.One2many('sf.functional.cutting.tool.entity', 'safe_inventory_id',
string='功能刀具信息') string='功能刀具信息')
active = fields.Boolean(string='已归档', default=True) active = fields.Boolean(string='已归档', default=True, groups='base.user_root')
@api.depends('functional_name_id', 'functional_name_id.diameter', 'functional_name_id.angle', @api.depends('functional_name_id', 'functional_name_id.diameter', 'functional_name_id.angle',
'functional_name_id.functional_cutting_tool_model_id') 'functional_name_id.functional_cutting_tool_model_id')

View File

@@ -29,7 +29,7 @@ class CNCprocessing(models.Model):
# else: # else:
# raise ValidationError("MES装刀指令发送失败") # raise ValidationError("MES装刀指令发送失败")
def cnc_tool_checkout(self, cnc_processing_ids): def cnc_tool_checkout_1(self, cnc_processing_ids):
""" """
根据传入的工单信息查询是否有需要的功能刀具如果没有则生成CAM工单程序用刀计划 根据传入的工单信息查询是否有需要的功能刀具如果没有则生成CAM工单程序用刀计划
""" """
@@ -128,13 +128,6 @@ class CNCprocessing(models.Model):
}) })
logging.info('工单cnc程序用刀校验已完成') logging.info('工单cnc程序用刀校验已完成')
@api.model_create_multi
def create(self, vals):
obj = super(CNCprocessing, self).create(vals)
# 调用CAM工单程序用刀计划创建方法
self.cnc_tool_checkout(obj)
return obj
class MrpWorkCenter(models.Model): class MrpWorkCenter(models.Model):
_inherit = 'mrp.workcenter' _inherit = 'mrp.workcenter'
@@ -143,3 +136,78 @@ class MrpWorkCenter(models.Model):
action = self.env.ref('sf_tool_management.sf_functional_tool_assembly_view_act') action = self.env.ref('sf_tool_management.sf_functional_tool_assembly_view_act')
result = action.read()[0] result = action.read()[0]
return result return result
class MrpProduction(models.Model):
_inherit = 'mrp.production'
def production_cnc_tool_checkout(self):
logging.info('开始进行工单cnc程序用刀校验')
invalid_tool = [] # 无效刀
invalid_tool_processing_panel = [] # 无效刀加工面
missing_tool_1 = [] # 缺刀(机内、线边)
missing_tool_2 = [] # 缺刀(库存)
for item in self:
workorder_ids = item.workorder_ids.filtered(lambda a: a.state not in ['rework', 'cancel'])
for workorder_id in workorder_ids:
if workorder_id.cnc_ids:
for cnc_id in workorder_id.cnc_ids:
tool_name = cnc_id.cutting_tool_name
# 查询功能刀具在清单中是否存在
tool_inventory_id = self.env['sf.tool.inventory'].sudo().search([('name', '=', tool_name)])
if not tool_inventory_id:
invalid_tool.append(tool_name)
invalid_tool_processing_panel.append(workorder_id.processing_panel)
continue
# 查询功能刀具是否存在库存
functional_tools = self.env['sf.functional.cutting.tool.entity'].sudo().search(
[('tool_name_id', '=', tool_inventory_id.id), ('functional_tool_status', '=', '正常')])
if not functional_tools.filtered(lambda p: p.current_location in ('线边刀库', '机内刀库')):
missing_tool_1.append(tool_name) # 判断为 ('线边刀库', '机内刀库') 缺刀
if not functional_tools:
missing_tool_2.append(tool_name) # 判断为 库存缺刀
break
# 修改cnc程序的刀具状态
workorder_ids = self.env['mrp.workorder'].sudo().search([('production_id', 'in', self.ids)])
if invalid_tool:
# 修改cnc程序的刀具状态为 ‘无效刀’
cnc_ids = self.env['sf.cnc.processing'].sudo().search(
[('workorder_id', 'in', workorder_ids.ids), ('cutting_tool_name', 'in', invalid_tool)])
if cnc_ids:
cnc_ids.write({'tool_state': '2'})
# 创建制造订单无效刀检测结果记录
for production_id in self:
for processing_panel in list(set(invalid_tool_processing_panel)):
if not production_id.detection_result_ids.filtered(
lambda a: (a.processing_panel == processing_panel and a.detailed_reason == '无效功能刀具'
and a.handle_result == '待处理' and a.routing_type == 'CNC加工'
and a.rework_reason == 'programming' and a.test_results == '返工')):
production_id.detection_result_ids.create({
'production_id': production_id.id,
'processing_panel': processing_panel,
'routing_type': 'CNC加工',
'rework_reason': 'programming', # 原因:编程(programming)
'detailed_reason': '无效功能刀具',
'test_results': '返工',
'handle_result': '待处理'
})
# 自动调用重新获取编程的方法
logging.info('cnc用刀校验到无效刀自动调用重新编程方法update_programming_state()')
self[0].update_programming_state()
# 修改制造订单 编程状态变为“编程中”
self.write({'programming_state': '编程中', 'work_state': '编程中'})
if missing_tool_1:
# 修改 修改cnc程序的刀具状态 为 ‘缺刀’
cnc_ids = self.env['sf.cnc.processing'].sudo().search(
[('workorder_id', 'in', workorder_ids.ids), ('cutting_tool_name', 'in', missing_tool_1)])
if cnc_ids:
cnc_ids.write({'tool_state': '1'})
if missing_tool_2 and not invalid_tool:
# 调用CAM工单程序用刀计划创建方法
cnc_ids = self.env['sf.cnc.processing'].sudo().search(
[('workorder_id', 'in', workorder_ids.filtered(lambda a: a.production_id == self[0].id).ids),
('cutting_tool_name', 'in', missing_tool_2)])
if cnc_ids:
logging.info('调用CAM工单程序用刀计划创建方法')
self.env['sf.cam.work.order.program.knife.plan'].sudo().create_cam_work_plan(cnc_ids)
logging.info('工单cnc程序用刀校验完成')

View File

@@ -10,7 +10,6 @@
<field name="barcode_id" invisible="1"/> <field name="barcode_id" invisible="1"/>
<field name="rfid"/> <field name="rfid"/>
<field name="tool_name_id"/> <field name="tool_name_id"/>
<field name="image" widget='image'/>
<field name="tool_groups_id"/> <field name="tool_groups_id"/>
<field name="functional_tool_diameter"/> <field name="functional_tool_diameter"/>
<field name="knife_tip_r_angle"/> <field name="knife_tip_r_angle"/>

View File

@@ -40,7 +40,7 @@
<field name="cutting_tool_model_id"/> <field name="cutting_tool_model_id"/>
<field name="specification_id"/> <field name="specification_id"/>
<field name="brand_id"/> <field name="brand_id"/>
<field name="qty_available"/> <field name="qty_available" string="库存数量"/>
</tree> </tree>
</field> </field>
</page> </page>

View File

@@ -338,7 +338,7 @@ class ShelfLocation(models.Model):
_name = 'sf.shelf.location' _name = 'sf.shelf.location'
_inherit = ['printing.utils'] _inherit = ['printing.utils']
_description = '货位' _description = '货位'
_rec_name = 'barcode' # _rec_name = 'barcode'
_order = 'id asc, create_date asc' _order = 'id asc, create_date asc'
# current_location_id = fields.Many2one('sf.shelf.location', string='当前位置') # current_location_id = fields.Many2one('sf.shelf.location', string='当前位置')

View File

@@ -0,0 +1,160 @@
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import os
from ftplib import FTP
import win32gui
import win32con
import logging
import time
# 配置日志记录
logging.basicConfig(filename='service.log', level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s')
app = FastAPI()
class FileUploadRequest(BaseModel):
filename: str
# FTP 服务器配置信息
ftp_host = '110.52.114.162'
ftp_port = 10021
ftp_user = 'ftpuser'
ftp_password = '123456'
ftp_directory = '/home/ftp/ftp_root/ThreeTest/XT/Before/'
def find_child_window(parent_hwnd, class_name):
def enum_child_windows(hwnd, lparam):
class_name = win32gui.GetClassName(hwnd)
if class_name == lparam:
child_hwnds.append(hwnd)
return True
child_hwnds = []
win32gui.EnumChildWindows(parent_hwnd, enum_child_windows, class_name)
return child_hwnds
def find_child_window_by_partial_title(parent_hwnd, partial_title):
def enum_child_windows(hwnd, lparam):
# 获取窗口的标题
title = win32gui.GetWindowText(hwnd)
# 检查标题是否包含指定的部分标题
if partial_title in title:
child_hwnds.append(hwnd)
return True
child_hwnds = []
win32gui.EnumChildWindows(parent_hwnd, enum_child_windows, None)
return child_hwnds
def find_child_window_by_title(parent_hwnd, title):
def enum_child_windows(hwnd, lparam):
if win32gui.GetWindowText(hwnd) == lparam:
child_hwnds.append(hwnd)
return True
child_hwnds = []
win32gui.EnumChildWindows(parent_hwnd, enum_child_windows, title)
return child_hwnds
def set_path_and_save(filename):
parent_hwnd = win32gui.FindWindow(None, '另存为')
if parent_hwnd == 0:
raise HTTPException(status_code=404, detail="没有找到保存报告的窗口,请检查!")
# 这里假设“地址:”是你需要的部分标题
address_hwnds = find_child_window_by_partial_title(parent_hwnd, "地址:")
# 确保找到的窗口句柄有效
if not address_hwnds:
raise HTTPException(status_code=404, detail="未找到地址框,请联系管理员!")
# 假设找到的第一个窗口是目标组件
address_hwnd = address_hwnds[0]
logging.info(f"找到地址框地址: {win32gui.GetWindowText(address_hwnd)}")
# 设置路径
local_file_path = os.path.join(win32gui.GetWindowText(address_hwnd).split(' ')[1], filename)
logging.info(f"设置路径: {local_file_path}")
path_hwnds = find_child_window(parent_hwnd, 'Edit')
if not path_hwnds:
raise HTTPException(status_code=404, detail="未找到路径框")
path_hwnd = path_hwnds[0]
win32gui.SendMessage(path_hwnd, win32con.WM_SETTEXT, 0, filename)
button_hwnds = find_child_window_by_title(parent_hwnd, '保存(&S)')
if not button_hwnds:
raise HTTPException(status_code=404, detail="未找到保存按钮")
save_button_hwnd = button_hwnds[0]
win32gui.PostMessage(save_button_hwnd, win32con.BM_CLICK, 0, 0)
return local_file_path
def wait_for_file_to_save(filepath, timeout=30):
start_time = time.time()
while time.time() - start_time < timeout:
if os.path.isfile(filepath):
return True
time.sleep(0.1)
return False
def upload_file_to_ftp(local_file):
if not os.path.isfile(local_file):
raise HTTPException(status_code=204, detail="文件未找到")
ftp = FTP()
try:
ftp.connect(ftp_host, ftp_port)
ftp.login(ftp_user, ftp_password)
ftp.cwd(ftp_directory)
with open(local_file, 'rb') as file:
ftp.storbinary(f'STOR {os.path.basename(local_file)}', file)
return True
except Exception as e:
print(f"文件上传失败: {e}")
return False
finally:
ftp.quit()
@app.post("/get/check/report")
async def upload_file(request: FileUploadRequest):
# 设置路径框并点击保存
local_file_path = set_path_and_save(request.filename)
logging.info(f"文件上传请求: {request.filename}")
logging.info(f"文件保存路径: {local_file_path}")
# 等待文件保存完成
if not wait_for_file_to_save(local_file_path):
raise HTTPException(status_code=500, detail="文件保存超时")
# 上传文件到 FTP
success = upload_file_to_ftp(local_file_path)
if success:
ftp_file_path = os.path.join(ftp_directory, request.filename)
return {"ftp_file_path": ftp_file_path}
else:
raise HTTPException(status_code=500, detail="文件上传失败")
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)

View File

@@ -0,0 +1,5 @@
app.py是主程序文件主要功能是监听装夹自动保存文件并将其上传到FTP服务器。
需要讲app.py做成一个exe文件可以用pyinstaller工具。
然后在windows的计划任务中执行此exe文件即可。