Compare commits
389 Commits
feature/ta
...
feature/au
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f557cb8d10 | ||
|
|
d64a814251 | ||
|
|
6054ce39f3 | ||
|
|
451e70b7c7 | ||
|
|
6c1c2ca0fc | ||
|
|
e7420365cf | ||
|
|
736a18518c | ||
|
|
41b287d559 | ||
|
|
5d8e0bda08 | ||
|
|
e9dbbaedf4 | ||
|
|
92ae6be3bd | ||
|
|
b00038aaed | ||
|
|
86c1880cdb | ||
|
|
cfd2dda4e6 | ||
|
|
c01e9a4d39 | ||
|
|
88ff4bbaa9 | ||
|
|
f62c70d354 | ||
|
|
22c9de86f6 | ||
|
|
b362f74336 | ||
|
|
81cfbfb540 | ||
|
|
668db4475f | ||
|
|
2af21bc4ee | ||
|
|
e6614a624f | ||
|
|
124bc8478e | ||
|
|
1a3a178ec6 | ||
|
|
cae5149fba | ||
|
|
5c4c036948 | ||
|
|
4003d6a652 | ||
|
|
0e8e81e4cb | ||
|
|
8cff3d8377 | ||
|
|
b132771c92 | ||
|
|
cbaf0d79c1 | ||
|
|
953d675dcf | ||
|
|
182eb83090 | ||
|
|
67a607b053 | ||
|
|
33d23c28b1 | ||
|
|
571fb29482 | ||
|
|
1dae20e055 | ||
|
|
6be9d44572 | ||
|
|
8a459e7e90 | ||
|
|
4c486e53aa | ||
|
|
19509a3ce2 | ||
|
|
4d424e90d3 | ||
|
|
9bbf613bcb | ||
|
|
f5ea0ec153 | ||
|
|
ea6c0f7d0b | ||
|
|
763009a3c0 | ||
|
|
cf34ac5bbc | ||
|
|
0faedd35d7 | ||
|
|
135b97d4f8 | ||
|
|
61f3aff768 | ||
|
|
9e369f0150 | ||
|
|
16d50f5bf2 | ||
|
|
80f0454996 | ||
|
|
9b9d811594 | ||
|
|
008ae20329 | ||
|
|
80b3b4b0f7 | ||
|
|
d64b480496 | ||
|
|
854ec8da4e | ||
|
|
d25b871854 | ||
|
|
37aafe7b30 | ||
|
|
26910f7b15 | ||
|
|
3ea2acd46e | ||
|
|
303462ba9c | ||
|
|
53fad50af6 | ||
|
|
1245323b9d | ||
|
|
4d8e86cff3 | ||
|
|
7bc7d2059c | ||
|
|
f9c209e85c | ||
|
|
6ae783387c | ||
|
|
7c0202afc6 | ||
|
|
9ad582612b | ||
|
|
d1db917432 | ||
|
|
847293385d | ||
|
|
4e8eccfd0b | ||
|
|
5cb5306471 | ||
|
|
9a5b45b969 | ||
|
|
7c82bbf3da | ||
|
|
9177acf277 | ||
|
|
93187d7f4a | ||
|
|
94b1551de2 | ||
|
|
1c713d3278 | ||
|
|
bdef93fca3 | ||
|
|
e9e55c72b9 | ||
|
|
919450aafb | ||
|
|
78f486ad7c | ||
|
|
ccdfcb5e50 | ||
|
|
9b275536e9 | ||
|
|
bf39ed10b4 | ||
|
|
0e8bda3369 | ||
|
|
87bb22857a | ||
|
|
9cd8d9d823 | ||
|
|
931732f75e | ||
|
|
35ace3b3eb | ||
|
|
4b4601ae85 | ||
|
|
46337855a4 | ||
|
|
43f8aec70a | ||
|
|
a21b4302d4 | ||
|
|
4adc3b700a | ||
|
|
42b5a32708 | ||
|
|
296e30e5fd | ||
|
|
90718cc989 | ||
|
|
857930e169 | ||
|
|
9b1b6e2b72 | ||
|
|
37217b0dd9 | ||
|
|
f138eab47a | ||
|
|
4e4c53927b | ||
|
|
fab1d77d64 | ||
|
|
f4f7087465 | ||
|
|
020da1d510 | ||
|
|
2956f13ee7 | ||
|
|
939f3d58f4 | ||
|
|
3d85b7863c | ||
|
|
54abb7a9f7 | ||
|
|
9ca9b337ef | ||
|
|
92c4bcb049 | ||
|
|
ce5cb17e31 | ||
|
|
6330929c04 | ||
|
|
79a753b609 | ||
|
|
30ebc62697 | ||
|
|
28b218a13b | ||
|
|
cd24430ea9 | ||
|
|
f4daf94f7b | ||
|
|
4acce194d9 | ||
|
|
761e8d4579 | ||
|
|
284c66ad86 | ||
|
|
6c7c10924a | ||
|
|
8f2d5129b1 | ||
|
|
2a067778bc | ||
|
|
299cc2f12f | ||
|
|
ce705abd20 | ||
|
|
e2bd36ca16 | ||
|
|
e2dbd41e8f | ||
|
|
050a61f334 | ||
|
|
0fa7c1b2d7 | ||
|
|
e1af6ba2f5 | ||
|
|
4a928735eb | ||
|
|
fdc2af4a39 | ||
|
|
70b3daad73 | ||
|
|
d8bade64e1 | ||
|
|
f779c492d8 | ||
|
|
a7b98f1e5e | ||
|
|
797eb9b159 | ||
|
|
c3e40fd3aa | ||
|
|
0ed07b024a | ||
|
|
22da51729b | ||
|
|
c3be2894c9 | ||
|
|
e08f968f1a | ||
|
|
351815ad91 | ||
|
|
25a90f3966 | ||
|
|
50bb50f5ab | ||
|
|
d1ff7c196d | ||
|
|
298d2391de | ||
|
|
49cbeee261 | ||
|
|
b104b358ea | ||
|
|
786fca2937 | ||
|
|
ca6d1f51df | ||
|
|
f324453351 | ||
|
|
d0f5aca707 | ||
|
|
f0cb047e8c | ||
|
|
b74bc62804 | ||
|
|
02467d5220 | ||
|
|
f18c0e3eaf | ||
|
|
c55f0b4c39 | ||
|
|
94f57629e5 | ||
|
|
2639a6ac6b | ||
|
|
52c1992814 | ||
|
|
b1fbf4344e | ||
|
|
a1b9f2dd3b | ||
|
|
441cfaffce | ||
|
|
75e1f5387d | ||
|
|
e4c845a9f6 | ||
|
|
d239509299 | ||
|
|
3f0fda7aac | ||
|
|
cc5452d14c | ||
|
|
5aff8c1737 | ||
|
|
a00f4af923 | ||
|
|
62a2aed7d9 | ||
|
|
e99f01104e | ||
|
|
457f0aa2ac | ||
|
|
1de4704046 | ||
|
|
0d65e67140 | ||
|
|
ccd01c9c75 | ||
|
|
84266364e3 | ||
|
|
0b127ff419 | ||
|
|
d4a01d9b8a | ||
|
|
ea88d8284a | ||
|
|
d2ec420f57 | ||
|
|
97d33adabd | ||
|
|
c11f3cc66c | ||
|
|
6b4a7e35a9 | ||
|
|
11100c9260 | ||
|
|
941c3ca43a | ||
|
|
05f9528ca9 | ||
|
|
b205945f65 | ||
|
|
c1dc28488a | ||
|
|
c6065653f0 | ||
|
|
fd0de5ae09 | ||
|
|
547d6608e6 | ||
|
|
dacb3cc076 | ||
|
|
282657fbca | ||
|
|
7e516c6f0b | ||
|
|
34e2a49bef | ||
|
|
ff4bdd2f2d | ||
|
|
b239fdf847 | ||
|
|
47feb4cf3c | ||
|
|
83ca18d7d5 | ||
|
|
11666b9ae1 | ||
|
|
63b732ff42 | ||
|
|
50d63c28d6 | ||
|
|
73b7ff7d1b | ||
|
|
ccdcd01372 | ||
|
|
198296f0f8 | ||
|
|
f122343e31 | ||
|
|
82fa39f1a3 | ||
|
|
841e1b4ce2 | ||
|
|
1ab62d7724 | ||
|
|
28a46d395b | ||
|
|
64c66f1272 | ||
|
|
f8d957486b | ||
|
|
1866607967 | ||
|
|
40efdf6f3b | ||
|
|
f910df3ce5 | ||
|
|
c99c96a9ea | ||
|
|
95cc557577 | ||
|
|
fa78389f47 | ||
|
|
f7381c43aa | ||
|
|
76c5db61da | ||
|
|
88ffc34a68 | ||
|
|
4f65b34aeb | ||
|
|
4a26d18b46 | ||
|
|
664ac8128a | ||
|
|
ce285818cf | ||
|
|
6f7811a843 | ||
|
|
54ed90e892 | ||
|
|
f8060113d9 | ||
|
|
48ab891991 | ||
|
|
7f384c3f56 | ||
|
|
01a2771dfb | ||
|
|
45b6214ddd | ||
|
|
c96ff3f5b4 | ||
|
|
d84758232e | ||
|
|
479953e082 | ||
|
|
7235f242b2 | ||
|
|
27881589d4 | ||
|
|
79ec3f2c91 | ||
|
|
715b4181be | ||
|
|
0ccda10c65 | ||
|
|
5b4376b5f9 | ||
|
|
1050f7616b | ||
|
|
3a9cc0c09c | ||
|
|
4d63b90373 | ||
|
|
43c4614650 | ||
|
|
eba9ccf083 | ||
|
|
947100a9d4 | ||
|
|
ed4924651a | ||
|
|
3cc2bb94fe | ||
|
|
8ecb3eb50a | ||
|
|
819d89c278 | ||
|
|
66d47c60bc | ||
|
|
0340563749 | ||
|
|
271e23a67f | ||
|
|
de42382f58 | ||
|
|
3728809d10 | ||
|
|
d4e2ace8a6 | ||
|
|
1d4188df7e | ||
|
|
a2323293ca | ||
|
|
29bd1c2968 | ||
|
|
6c35ec13fd | ||
|
|
83107b05e2 | ||
|
|
5fb7165306 | ||
|
|
c569b60d5c | ||
|
|
bd2748659a | ||
|
|
7fef8f4c77 | ||
|
|
3ee233c0bc | ||
|
|
d63081dffa | ||
|
|
0932ec95bf | ||
|
|
7361da0e5d | ||
|
|
2dbddf532c | ||
|
|
eb867e62d8 | ||
|
|
d75504960a | ||
|
|
93efbf742e | ||
|
|
7c3ac138b5 | ||
|
|
63444d3dd1 | ||
|
|
2fcad742b8 | ||
|
|
f341ef5e83 | ||
|
|
d488824e3d | ||
|
|
0c28700f75 | ||
|
|
48d97f3e57 | ||
|
|
ef17c9920c | ||
|
|
ef643a5a72 | ||
|
|
b7ff8d0bd5 | ||
|
|
1e721d68bf | ||
|
|
7454297dcd | ||
|
|
2e3a9a8b80 | ||
|
|
4e0a023c36 | ||
|
|
155c5eb329 | ||
|
|
45718ab925 | ||
|
|
562c8dda8d | ||
|
|
84a37a970f | ||
|
|
37cc02e97a | ||
|
|
7a8753408b | ||
|
|
eb853d0be9 | ||
|
|
52438733d7 | ||
|
|
b986dd8473 | ||
|
|
98fdb581fb | ||
|
|
ea7380288c | ||
|
|
9beecab21d | ||
|
|
2e6932e054 | ||
|
|
5ca9e028d8 | ||
|
|
64e2b71a34 | ||
|
|
8d52c94aed | ||
|
|
9d91a1c99f | ||
|
|
d6fdeafef6 | ||
|
|
371b3a9d65 | ||
|
|
36c1c7b170 | ||
|
|
38d3a7901d | ||
|
|
2223259d13 | ||
|
|
aba270182d | ||
|
|
c9378fc9fe | ||
|
|
378850682d | ||
|
|
02c35803da | ||
|
|
75b60c1ec8 | ||
|
|
2cd4424ba1 | ||
|
|
d52f0aed6e | ||
|
|
d197fc5b9e | ||
|
|
34280fe24b | ||
|
|
fd88a37aec | ||
|
|
0082a308fa | ||
|
|
b5f5826c80 | ||
|
|
5024a9254d | ||
|
|
2a14a630d5 | ||
|
|
ef8ea2599f | ||
|
|
e9ab4270a9 | ||
|
|
a99d651509 | ||
|
|
c54a5b36d4 | ||
|
|
ae844cf203 | ||
|
|
ec66ea76ba | ||
|
|
8643c41193 | ||
|
|
bed0483496 | ||
|
|
a648208656 | ||
|
|
98af6d0530 | ||
|
|
5af1953e04 | ||
|
|
b1a08be57b | ||
|
|
15981aadf1 | ||
|
|
e19cb52e70 | ||
|
|
5269d09d0e | ||
|
|
e7e64720c6 | ||
|
|
a6701a842e | ||
|
|
d697bd13a1 | ||
|
|
6e20a466ce | ||
|
|
dd5c9775fc | ||
|
|
82c274591c | ||
|
|
4f73f57ddf | ||
|
|
269141dfb2 | ||
|
|
cab0e1ce0b | ||
|
|
d4ff7ffaa9 | ||
|
|
d238d09cc3 | ||
|
|
4cee5213bb | ||
|
|
759e4a481d | ||
|
|
1213afe834 | ||
|
|
35fda7106a | ||
|
|
b8baa84270 | ||
|
|
6591e663b6 | ||
|
|
bad5c8d489 | ||
|
|
68f8c94332 | ||
|
|
ddfb233452 | ||
|
|
bbf62d2302 | ||
|
|
17b09c1f6d | ||
|
|
e0fc70ec60 | ||
|
|
4558fc0336 | ||
|
|
4609ec442a | ||
|
|
eb7d9e4168 | ||
|
|
8706e25b0d | ||
|
|
85fea64f49 | ||
|
|
f74215c9f6 | ||
|
|
cc8906980c | ||
|
|
56f1ba0f25 | ||
|
|
0990d73075 | ||
|
|
7d877a0cbb | ||
|
|
e13bad8483 | ||
|
|
22ebb1bbe1 | ||
|
|
bc888d7984 | ||
|
|
c92d4f7868 | ||
|
|
667a2a81fb | ||
|
|
d067c5b8c4 | ||
|
|
f6e371f223 | ||
|
|
0aefe9e656 | ||
|
|
4f8f29e41a |
@@ -3,8 +3,8 @@
|
||||
'name': "jikimo_account_process",
|
||||
|
||||
'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
|
||||
|
||||
@@ -6,6 +6,6 @@ import { patch } from "web.utils";
|
||||
patch(WebClient.prototype, "kolpolok_custom_title_and_favicon.WebClient", {
|
||||
setup() {
|
||||
this._super();
|
||||
this.title.setParts({ zopenerp: "JIKIMO" });
|
||||
// this.title.setParts({ zopenerp: "JIKIMO" });
|
||||
},
|
||||
});
|
||||
@@ -1,41 +1,99 @@
|
||||
.zoomed {
|
||||
position: fixed !important;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%) scale(10);
|
||||
|
||||
.processing-capabilities-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(6, 1fr);
|
||||
gap: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.many2many_flex {
|
||||
display: flex;
|
||||
.grid-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.many2many_flex>div {
|
||||
margin-right: 15px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
.item-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
/*控制图片大小*/
|
||||
.item-icon {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.many2many_flex>div>:nth-child(2) {
|
||||
position: relative;
|
||||
.item-label {
|
||||
font-size: 12px;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.close {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
position: absolute;
|
||||
top: -8.8px;
|
||||
right: -8.8px;
|
||||
color: #fff;
|
||||
background-color: #000;
|
||||
opacity: 0;
|
||||
text-align: center;
|
||||
line-height: 20px;
|
||||
font-size: 18px;
|
||||
@media (max-width: 1200px) {
|
||||
.processing-capabilities-grid {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
.img_close {
|
||||
opacity: 1;
|
||||
transform: scale(0.1);
|
||||
cursor: pointer;
|
||||
@media (max-width: 768px) {
|
||||
.processing-capabilities-grid {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
@@ -4,35 +4,57 @@ import {Many2ManyCheckboxesField} from "@web/views/fields/many2many_checkboxes/m
|
||||
import {registry} from "@web/core/registry";
|
||||
|
||||
export class MyCustomWidget extends Many2ManyCheckboxesField {
|
||||
// 你可以重写或者添加一些方法和属性
|
||||
// 例如,你可以重写setup方法来添加一些事件监听器或者初始化一些变量
|
||||
setup() {
|
||||
super.setup(); // 调用父类的setup方法
|
||||
// 你自己的代码
|
||||
super.setup();
|
||||
}
|
||||
|
||||
onImageClick(event) {
|
||||
// 放大图片逻辑
|
||||
// 获取图片元素
|
||||
const img = event.target;
|
||||
const close = img.nextSibling;
|
||||
onImageClick(event, src) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
// 实现放大图片逻辑
|
||||
// 比如使用 CSS 放大
|
||||
img.parentElement.classList.add('zoomed');
|
||||
close.classList.add('img_close');
|
||||
}
|
||||
// 创建预览框
|
||||
const previewContainer = document.createElement('div');
|
||||
previewContainer.className = 'image-preview-container';
|
||||
|
||||
onCloseClick(event) {
|
||||
const close = event.target;
|
||||
const img = close.previousSibling;
|
||||
img.parentElement.classList.remove('zoomed');
|
||||
close.classList.remove('img_close');
|
||||
const previewImg = document.createElement('img');
|
||||
previewImg.src = src;
|
||||
previewImg.className = 'image-preview';
|
||||
// 设置放大的预览图片大小
|
||||
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.supportedTypes = ['many2many'];
|
||||
|
||||
registry.category("fields").add("custom_many2many_checkboxes", MyCustomWidget);
|
||||
|
||||
registry.category("fields").add("custom_many2many_checkboxes", MyCustomWidget);
|
||||
@@ -2,27 +2,22 @@
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<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]">
|
||||
<div>
|
||||
<div class="grid-item">
|
||||
<CheckBox
|
||||
value="isSelected(item)"
|
||||
disabled="props.readonly"
|
||||
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>
|
||||
<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>
|
||||
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
</templates>
|
||||
@@ -108,6 +108,10 @@ td.o_required_modifier {
|
||||
}
|
||||
|
||||
.color_3 {
|
||||
background-color: #808080;
|
||||
}
|
||||
|
||||
.color_4 {
|
||||
background-color: rgb(255, 150, 0);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
<!-- 修改页面头部图标及文字 -->
|
||||
<template id="favicon_icon" inherit_id="web.layout" name="Web layout">
|
||||
<!-- change the title with reliance partner -->
|
||||
<xpath expr="//head//title" position="before">
|
||||
<!-- <xpath expr="//head//title" position="before">
|
||||
<title t-esc="'JIKIMO'"/>
|
||||
</xpath>
|
||||
</xpath> -->
|
||||
<!-- change the default favicon icon with -->
|
||||
<xpath expr="//head//link[@rel='shortcut icon']" position="replace">
|
||||
<link type="image/x-icon" rel="shortcut icon" href="/jikimo_frontend/static/src/img/jikimo-logo.ico"/>
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
<!-- hide 登录页面 powerd by odoo 及管理数据库 -->
|
||||
<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>
|
||||
|
||||
<!-- 隐藏odoo版本信息 -->
|
||||
|
||||
BIN
jikimo_system_order/static/description/系统工单.png
Normal file
BIN
jikimo_system_order/static/description/系统工单.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 533 B |
@@ -234,7 +234,7 @@
|
||||
</record>
|
||||
|
||||
|
||||
<menuitem name="系统工单" id="work_order_1_list" web_icon="jikimo_system_order,static/description/icon.png"/>
|
||||
<menuitem name="系统工单" id="work_order_1_list" web_icon="jikimo_system_order,static/description/系统工单.png"/>
|
||||
<menuitem name="工单" id="work_order" parent="work_order_1_list" action="system_order"/>
|
||||
<menuitem name="工单模板" id="work_order_template" parent="work_order_1_list" action="work_template" groups="jikimo_system_order.group_operations_permissions_rwc"/>
|
||||
<menuitem name="工单分类" id="work_order_type" parent="work_order_1_list" action="classify" groups="jikimo_system_order.group_operations_permissions_rwc"/>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from . import we_api
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import models
|
||||
from . import controllers
|
||||
21
jikimo_workorder_exception/__manifest__.py
Normal file
21
jikimo_workorder_exception/__manifest__.py
Normal file
@@ -0,0 +1,21 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
{
|
||||
'name': '机企猫 工单异常记录',
|
||||
'version': '1.0',
|
||||
'summary': '记录工单的异常日志',
|
||||
'sequence': 1,
|
||||
'category': 'sf',
|
||||
'website': 'https://www.sf.jikimo.com',
|
||||
'depends': ['sf_manufacturing', 'sf_mrs_connect'],
|
||||
'data': [
|
||||
'views/mrp_workorder_views.xml',
|
||||
'security/ir.model.access.csv',
|
||||
],
|
||||
'demo': [
|
||||
],
|
||||
'license': 'LGPL-3',
|
||||
'installable': True,
|
||||
'application': False,
|
||||
'auto_install': False,
|
||||
}
|
||||
1
jikimo_workorder_exception/controllers/__init__.py
Normal file
1
jikimo_workorder_exception/controllers/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import main
|
||||
89
jikimo_workorder_exception/controllers/main.py
Normal file
89
jikimo_workorder_exception/controllers/main.py
Normal file
@@ -0,0 +1,89 @@
|
||||
from odoo import http, fields
|
||||
from odoo.http import request
|
||||
import json
|
||||
import logging
|
||||
from odoo.addons.sf_mrs_connect.controllers.controllers import Sf_Mrs_Connect
|
||||
from odoo.addons.sf_manufacturing.controllers.controllers import Manufacturing_Connect
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class WorkorderExceptionConroller(http.Controller):
|
||||
|
||||
@http.route('/AutoDeviceApi/BillError', type='json', auth='public', methods=['GET', 'POST'], csrf=False,
|
||||
cors="*")
|
||||
def workder_exception(self, **kw):
|
||||
"""
|
||||
记录工单异常
|
||||
:param kw:
|
||||
:return:
|
||||
"""
|
||||
_logger.info('workder_exception:%s' % kw)
|
||||
try:
|
||||
res = {'Succeed': True, 'ErrorCode': 0, 'Error': ''}
|
||||
datas = request.httprequest.data
|
||||
ret = json.loads(datas)
|
||||
if not ret.get('RfidCode') or not ret.get('coding'):
|
||||
res = {'Succeed': False, 'ErrorCode': 400, 'Error': '参数错误'}
|
||||
return json.JSONEncoder().encode(res)
|
||||
|
||||
# 通过RfidCode获取就绪的CNC工单
|
||||
workorder = request.env['mrp.workorder'].sudo().search([
|
||||
('rfid_code', '=', ret['RfidCode']),
|
||||
('routing_type', '=', 'CNC加工'),
|
||||
])
|
||||
if not workorder:
|
||||
res = {'Succeed': False, 'ErrorCode': 401, 'Error': '无效的工单'}
|
||||
return json.JSONEncoder().encode(res)
|
||||
|
||||
# 创建工单异常记录,关联工单
|
||||
request.env['jikimo.workorder.exception'].sudo().create({
|
||||
'workorder_id': workorder.id,
|
||||
'exception_code': ret.get('coding'),
|
||||
'exception_content': ret.get('Error', '')
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
res = {'Succeed': False, 'ErrorCode': 202, 'Error': e}
|
||||
_logger.info('workder_exception error:%s' % e)
|
||||
return json.JSONEncoder().encode(res)
|
||||
|
||||
|
||||
class SfMrsConnectController(Sf_Mrs_Connect):
|
||||
|
||||
@http.route('/api/cnc_processing/create', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False,
|
||||
cors="*")
|
||||
def get_cnc_processing_create(self, **kw):
|
||||
"""
|
||||
更新工单异常记录【'YC001', 'YC004'】
|
||||
"""
|
||||
res = super(SfMrsConnectController, self).get_cnc_processing_create(**kw)
|
||||
# 如果有未完成的YC0001、YC0004异常记录,则标记为完成
|
||||
res = json.loads(res)
|
||||
_logger.info('已进入工单异常:%s' % res)
|
||||
if res.get('production_ids'):
|
||||
try:
|
||||
productions = request.env['mrp.production'].sudo().search([('id', 'in', res.get('production_ids'))])
|
||||
if productions.workorder_ids:
|
||||
productions.workorder_ids.handle_exception(['YC0001', 'YC0004'])
|
||||
except Exception as e:
|
||||
_logger.info('更新工单异常记录失败:%s' % e)
|
||||
return json.JSONEncoder().encode(res)
|
||||
|
||||
class ManufactruingController(Manufacturing_Connect):
|
||||
|
||||
@http.route('/AutoDeviceApi/FeedBackStart', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
|
||||
cors="*")
|
||||
def button_Work_START(self, **kw):
|
||||
"""
|
||||
更新工单异常记录【'YC0002', 'YC0003'】
|
||||
"""
|
||||
res = super(ManufactruingController, self).button_Work_START(**kw)
|
||||
res = json.loads(res)
|
||||
_logger.info('已进入工单异常:%s' % res)
|
||||
if res.get('workorder_id'):
|
||||
try:
|
||||
workorder = request.env['mrp.workorder'].sudo().browse(int(res.get('workorder_id')))
|
||||
workorder.handle_exception(['YC0002', 'YC0003'])
|
||||
except Exception as e:
|
||||
_logger.info('更新工单异常记录失败:%s' % e)
|
||||
return json.JSONEncoder().encode(res)
|
||||
3
jikimo_workorder_exception/models/__init__.py
Normal file
3
jikimo_workorder_exception/models/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from . import jikimo_workorder_exception
|
||||
from . import mrp_workorder
|
||||
@@ -0,0 +1,14 @@
|
||||
from odoo import models, fields
|
||||
|
||||
|
||||
class JikimoWorkorderException(models.Model):
|
||||
_name = 'jikimo.workorder.exception'
|
||||
_description = '工单异常记录'
|
||||
_order = 'id desc'
|
||||
|
||||
workorder_id = fields.Many2one('mrp.workorder', string='工单')
|
||||
exception_code = fields.Char('异常编码')
|
||||
exception_content = fields.Char('反馈的异常/问题信息')
|
||||
completion_time = fields.Datetime('处理完成时间')
|
||||
state = fields.Selection([('pending', '进行中'), ('done', '已处理')], string='状态', default='pending')
|
||||
|
||||
40
jikimo_workorder_exception/models/mrp_workorder.py
Normal file
40
jikimo_workorder_exception/models/mrp_workorder.py
Normal file
@@ -0,0 +1,40 @@
|
||||
from odoo import models, fields
|
||||
import logging
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class MrpWorkorder(models.Model):
|
||||
_inherit = 'mrp.workorder'
|
||||
|
||||
exception_ids = fields.One2many('jikimo.workorder.exception', 'workorder_id', string='工单异常记录')
|
||||
|
||||
def write(self, values):
|
||||
if values.get('test_results') and self.exception_ids:
|
||||
pending_exception = self.exception_ids.filtered(
|
||||
lambda exc: exc.state == 'pending' and exc.exception_code == 'YC0005'
|
||||
)
|
||||
if pending_exception:
|
||||
pending_exception.write({
|
||||
'completion_time': fields.Datetime.now(),
|
||||
'state': 'done'
|
||||
})
|
||||
return super(MrpWorkorder, self).write(values)
|
||||
|
||||
def handle_exception(self, exception_codes):
|
||||
"""
|
||||
处理异常
|
||||
:param exception_codes: 需要处理的异常编码列表
|
||||
"""
|
||||
if not isinstance(exception_codes, list):
|
||||
exception_codes = [exception_codes]
|
||||
if self.exception_ids:
|
||||
_logger.info('workorder.exception_ids:%s' % self.exception_ids)
|
||||
pending_exception = self.exception_ids.filtered(
|
||||
lambda exc: exc.state == 'pending' and exc.exception_code in exception_codes
|
||||
)
|
||||
_logger.info('pending_exception:%s' % pending_exception)
|
||||
if pending_exception:
|
||||
pending_exception.write({
|
||||
'completion_time': fields.Datetime.now(),
|
||||
'state': 'done'
|
||||
})
|
||||
2
jikimo_workorder_exception/security/ir.model.access.csv
Normal file
2
jikimo_workorder_exception/security/ir.model.access.csv
Normal file
@@ -0,0 +1,2 @@
|
||||
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
|
||||
"access_jikimo_workorder_exception","access.jikimo.workorder.exception","model_jikimo_workorder_exception","mrp.group_mrp_user",1,1,1,0
|
||||
|
2
jikimo_workorder_exception/tests/__init__.py
Normal file
2
jikimo_workorder_exception/tests/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from . import common
|
||||
from . import test_jikimo_workorder_exception
|
||||
48
jikimo_workorder_exception/tests/common.py
Normal file
48
jikimo_workorder_exception/tests/common.py
Normal file
@@ -0,0 +1,48 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
from odoo import fields, Command
|
||||
from odoo.tests.common import TransactionCase, HttpCase, tagged, Form
|
||||
|
||||
import json
|
||||
import time
|
||||
import base64
|
||||
from lxml import etree
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestJikimoWorkorderExceptionCommon(TransactionCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestJikimoWorkorderExceptionCommon, self).setUp()
|
||||
# 获取名字为“1#自动生产线”的制造中心
|
||||
workcenter = self.env['mrp.workcenter'].search([('name', '=', '1#自动生产线')], limit=1)
|
||||
# 创建一个产品
|
||||
product_product = self.env['product.product'].create({
|
||||
'name': '测试产品',
|
||||
'type': 'product',
|
||||
})
|
||||
uom_unit = self.env.ref('uom.product_uom_unit')
|
||||
# 创建一个bom
|
||||
self.bom = self.env['mrp.bom'].create({
|
||||
'product_id': product_product.id,
|
||||
'product_tmpl_id': product_product.product_tmpl_id.id,
|
||||
'product_uom_id': uom_unit.id,
|
||||
'product_qty': 1.0,
|
||||
'type': 'normal',
|
||||
})
|
||||
# 创建一个制造订单
|
||||
self.production = self.env['mrp.production'].create({
|
||||
'name': 'Test Production',
|
||||
'product_id': product_product.id,
|
||||
'bom_id': self.bom.id,
|
||||
'company_id': self.env.ref('base.main_company').id,
|
||||
})
|
||||
# 创建一个测试工单
|
||||
self.workorder = self.env['mrp.workorder'].create({
|
||||
'name': 'Test order',
|
||||
'workcenter_id': workcenter.id,
|
||||
'product_uom_id': self.bom.product_uom_id.id,
|
||||
'production_id': self.production.id,
|
||||
'duration_expected': 1.0,
|
||||
'rfid_code': 'test-123456',
|
||||
'routing_type': 'CNC加工'
|
||||
})
|
||||
@@ -0,0 +1,53 @@
|
||||
import json
|
||||
from datetime import datetime
|
||||
from odoo.addons.jikimo_workorder_exception.tests.common import TestJikimoWorkorderExceptionCommon
|
||||
|
||||
class TestJikimoWorkorderException(TestJikimoWorkorderExceptionCommon):
|
||||
|
||||
def test_create_exception_record(self):
|
||||
exception_record = self.env['jikimo.workorder.exception'].create({
|
||||
'workorder_id': self.workorder.id,
|
||||
'exception_code': 'YC0001',
|
||||
'exception_content': '无CNC编程'
|
||||
})
|
||||
|
||||
self.assertTrue(exception_record)
|
||||
self.assertEqual(exception_record.exception_content, '无CNC编程')
|
||||
self.assertEqual(exception_record.workorder_id.id, self.workorder.id)
|
||||
self.assertEqual(exception_record.exception_code, 'YC0001')
|
||||
|
||||
def test_handle_exception(self):
|
||||
exception_record = self.env['jikimo.workorder.exception'].create({
|
||||
'workorder_id': self.workorder.id,
|
||||
'exception_code': 'YC0001',
|
||||
'exception_content': '无CNC编程'
|
||||
})
|
||||
self.workorder.handle_exception('YC0001')
|
||||
self.assertEqual(exception_record.state, 'done')
|
||||
# 判断完成时间是否为当前分钟
|
||||
self.assertEqual(exception_record.completion_time.minute, datetime.now().minute)
|
||||
|
||||
def test_handle_exception_with_invalid_code(self):
|
||||
exception_record = self.env['jikimo.workorder.exception'].create({
|
||||
'workorder_id': self.workorder.id,
|
||||
'exception_code': 'YC0001',
|
||||
'exception_content': '无CNC编程'
|
||||
})
|
||||
self.workorder.handle_exception(['YC0002', 'YC0004'])
|
||||
self.assertEqual(exception_record.state, 'pending')
|
||||
self.assertEqual(exception_record.completion_time, False)
|
||||
|
||||
|
||||
def test_handle_exception_with_test_results(self):
|
||||
exception_record = self.env['jikimo.workorder.exception'].create({
|
||||
'workorder_id': self.workorder.id,
|
||||
'exception_code': 'YC0005',
|
||||
'exception_content': '工单加工失败'
|
||||
})
|
||||
self.workorder.write({
|
||||
'test_results': '返工',
|
||||
'reason': 'cutter',
|
||||
'detailed_reason': '刀坏了',
|
||||
})
|
||||
self.assertEqual(exception_record.state, 'done')
|
||||
self.assertEqual(exception_record.completion_time.minute, datetime.now().minute)
|
||||
23
jikimo_workorder_exception/views/mrp_workorder_views.xml
Normal file
23
jikimo_workorder_exception/views/mrp_workorder_views.xml
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<odoo>
|
||||
<data>
|
||||
<record id="jikimo_workorder_exception_form_view_inherit" model="ir.ui.view">
|
||||
<field name="name">mrp.workorder.form</field>
|
||||
<field name="model">mrp.workorder</field>
|
||||
<field name="inherit_id" ref="mrp.mrp_production_workorder_form_view_inherit"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//notebook/page[last()]" position="after">
|
||||
<page string="异常记录" name="workorder_exception" attrs="{'invisible': [('routing_type', '!=', 'CNC加工')]}">
|
||||
<field name="exception_ids" nolabel="1" readonly="1">
|
||||
<tree create="false" delete="false" edit="false">
|
||||
<field name="exception_content" string="反馈的异常/问题信息"/>
|
||||
<field name="create_date" string="时间"/>
|
||||
<field name="completion_time"/>
|
||||
</tree>
|
||||
</field>
|
||||
</page>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -1 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import models
|
||||
|
||||
22
jikimo_workorder_exception_notify/__manifest__.py
Normal file
22
jikimo_workorder_exception_notify/__manifest__.py
Normal file
@@ -0,0 +1,22 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
{
|
||||
'name': '机企猫 工单异常消息通知',
|
||||
'version': '1.0',
|
||||
'summary': '当产生工单异常时,发送消息通知',
|
||||
'sequence': 1,
|
||||
'category': 'sf',
|
||||
'website': 'https://www.sf.jikimo.com',
|
||||
'depends': ['jikimo_workorder_exception', 'jikimo_message_notify'],
|
||||
'data': [
|
||||
'data/bussiness_node.xml',
|
||||
'data/template_data.xml',
|
||||
# 'security/ir.model.access.csv',
|
||||
],
|
||||
'demo': [
|
||||
],
|
||||
'license': 'LGPL-3',
|
||||
'installable': True,
|
||||
'application': False,
|
||||
'auto_install': False,
|
||||
}
|
||||
17
jikimo_workorder_exception_notify/data/bussiness_node.xml
Normal file
17
jikimo_workorder_exception_notify/data/bussiness_node.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" ?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<record id="bussiness_no_functional_tool" model="jikimo.message.bussiness.node">
|
||||
<field name="name">无功能刀具</field>
|
||||
<field name="model">jikimo.workorder.exception</field>
|
||||
</record>
|
||||
<record id="bussiness_no_position_data" model="jikimo.message.bussiness.node">
|
||||
<field name="name">无定位数据</field>
|
||||
<field name="model">jikimo.workorder.exception</field>
|
||||
</record>
|
||||
<record id="bussiness_processing_failure" model="jikimo.message.bussiness.node">
|
||||
<field name="name">加工失败</field>
|
||||
<field name="model">jikimo.workorder.exception</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
38
jikimo_workorder_exception_notify/data/template_data.xml
Normal file
38
jikimo_workorder_exception_notify/data/template_data.xml
Normal file
@@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" ?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<record id="template_no_function_tool" model="jikimo.message.template">
|
||||
<field name="name">生产线无功能刀具提醒</field>
|
||||
<field name="model_id" ref="jikimo_workorder_exception_notify.model_jikimo_workorder_exception"/>
|
||||
<field name="model">jikimo.workorder.exception</field>
|
||||
<field name="bussiness_node_id" ref="bussiness_no_functional_tool"/>
|
||||
<field name="msgtype">markdown</field>
|
||||
<field name="urgency">urgent</field>
|
||||
<field name="content">### 生产线无功能刀具提醒
|
||||
单号:工单[{{workorder_id.production_id.name}}]({{url}})
|
||||
原因:生产线无加工程序要用的功能刀具</field>
|
||||
</record>
|
||||
<record id="template_no_position_data" model="jikimo.message.template">
|
||||
<field name="name">工单无定位数据提醒</field>
|
||||
<field name="model_id" ref="jikimo_workorder_exception_notify.model_jikimo_workorder_exception"/>
|
||||
<field name="model">jikimo.workorder.exception</field>
|
||||
<field name="bussiness_node_id" ref="bussiness_no_position_data"/>
|
||||
<field name="msgtype">markdown</field>
|
||||
<field name="urgency">urgent</field>
|
||||
<field name="content">### 工单无定位数据提醒
|
||||
单号:工单[{{workorder_id.production_id.name}}]({{url}})
|
||||
原因:无装夹定位测量数据</field>
|
||||
</record>
|
||||
<record id="template_processing_failure" model="jikimo.message.template">
|
||||
<field name="name">工单加工失败提醒</field>
|
||||
<field name="model_id" ref="jikimo_workorder_exception_notify.model_jikimo_workorder_exception"/>
|
||||
<field name="model">jikimo.workorder.exception</field>
|
||||
<field name="bussiness_node_id" ref="bussiness_processing_failure"/>
|
||||
<field name="msgtype">markdown</field>
|
||||
<field name="urgency">urgent</field>
|
||||
<field name="content">### 工单加工失败提醒
|
||||
单号:工单[{{workorder_id.production_id.name}}]({{url}})
|
||||
原因:加工失败,工件下产线处理</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
3
jikimo_workorder_exception_notify/models/__init__.py
Normal file
3
jikimo_workorder_exception_notify/models/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from . import jikimo_message_template
|
||||
from . import jikimo_workorder_exception
|
||||
@@ -0,0 +1,10 @@
|
||||
from odoo import models
|
||||
|
||||
|
||||
class JikimoMessageTemplate(models.Model):
|
||||
_inherit = "jikimo.message.template"
|
||||
|
||||
def _get_message_model(self):
|
||||
res = super(JikimoMessageTemplate, self)._get_message_model()
|
||||
res.append('jikimo.workorder.exception')
|
||||
return res
|
||||
@@ -0,0 +1,61 @@
|
||||
from odoo import models, api
|
||||
from odoo.addons.sf_base.commons.common import Common
|
||||
import requests, logging
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class JikimoWorkorderException(models.Model):
|
||||
_name = 'jikimo.workorder.exception'
|
||||
_inherit = ['jikimo.workorder.exception', 'jikimo.message.dispatch']
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
res = super(JikimoWorkorderException, self).create(vals_list)
|
||||
# 根据异常编码发送消息提醒
|
||||
try:
|
||||
for rec in res:
|
||||
if rec.exception_code == 'YC0001':
|
||||
# 无CNC程序,调用cloud接口
|
||||
data = {'name': rec.workorder_id.production_id.programming_no, 'exception_code': 'YC0001'}
|
||||
configsettings = self.env['res.config.settings'].sudo().get_values()
|
||||
config_header = Common.get_headers(self, configsettings['token'], configsettings['sf_secret_key'])
|
||||
url = '/api/message/workorder_exception'
|
||||
config_url = configsettings['sf_url'] + url
|
||||
data['token'] = configsettings['token']
|
||||
ret = requests.post(config_url, json=data, headers=config_header)
|
||||
ret = ret.json()
|
||||
_logger.info('无CNC程序异常消息推送接口:%s' % ret)
|
||||
elif rec.exception_code == 'YC0002':
|
||||
# 无功能刀具
|
||||
rec.add_queue('无功能刀具')
|
||||
elif rec.exception_code == 'YC0003':
|
||||
# 无定位数据
|
||||
rec.add_queue('无定位数据')
|
||||
elif rec.exception_code == 'YC0004':
|
||||
# 无FTP文件,调用cloud接口
|
||||
data = {'name': rec.workorder_id.production_id.programming_no, 'exception_code': 'YC0004'}
|
||||
configsettings = self.env['res.config.settings'].sudo().get_values()
|
||||
config_header = Common.get_headers(self, configsettings['token'], configsettings['sf_secret_key'])
|
||||
url = '/api/message/workorder_exception'
|
||||
config_url = configsettings['sf_url'] + url
|
||||
data['token'] = configsettings['token']
|
||||
ret = requests.post(config_url, json=data, headers=config_header)
|
||||
ret = ret.json()
|
||||
_logger.info('无FTP文件异常消息推送接口:%s' % ret)
|
||||
elif rec.exception_code == 'YC0005':
|
||||
# 加工失败
|
||||
rec.add_queue('加工失败')
|
||||
except Exception as e:
|
||||
_logger.error('异常编码发送消息提醒失败:%s' % e)
|
||||
return res
|
||||
|
||||
def _get_message(self, message_queue_ids):
|
||||
contents = super(JikimoWorkorderException, self)._get_message(message_queue_ids)
|
||||
url = self.env['ir.config_parameter'].get_param('web.base.url')
|
||||
action_id = self.env.ref('mrp.mrp_production_action').id
|
||||
for index, content in enumerate(contents):
|
||||
exception_id = self.env['jikimo.workorder.exception'].browse(message_queue_ids[index].res_id)
|
||||
url = url + '/web#id=%s&view_type=form&action=%s' % (exception_id.workorder_id.production_id.id, action_id)
|
||||
contents[index] = content.replace('{{url}}', url)
|
||||
return contents
|
||||
2
jikimo_workorder_exception_notify/tests/__init__.py
Normal file
2
jikimo_workorder_exception_notify/tests/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from . import common
|
||||
from . import test_jikimo_workorder_exception_notify
|
||||
18
jikimo_workorder_exception_notify/tests/common.py
Normal file
18
jikimo_workorder_exception_notify/tests/common.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
from odoo import fields, Command
|
||||
from odoo.tests.common import TransactionCase, HttpCase, tagged, Form
|
||||
|
||||
import json
|
||||
import time
|
||||
import base64
|
||||
from lxml import etree
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestJikimoWorkorderExceptionNotifyCommonNotify(TransactionCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestJikimoWorkorderExceptionNotifyCommonNotify, self).setUp()
|
||||
# 获取最后一个工单
|
||||
self.workorder = self.env['mrp.workorder'].search([], order='id desc', limit=1)
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
import json
|
||||
from datetime import datetime
|
||||
from odoo.addons.jikimo_workorder_exception_notify.tests.common import TestJikimoWorkorderExceptionNotifyCommonNotify
|
||||
|
||||
class TestJikimoWorkorderExceptionNotify(TestJikimoWorkorderExceptionNotifyCommonNotify):
|
||||
|
||||
def test_create_message_template(self):
|
||||
self.assertTrue(self.env['jikimo.message.template'].search([
|
||||
('name', '=', '生产线无功能刀具提醒'),
|
||||
('model', '=', 'jikimo.workorder.exception')
|
||||
]))
|
||||
self.assertTrue(self.env['jikimo.message.template'].search([
|
||||
('name', '=', '工单无定位数据提醒'),
|
||||
('model', '=', 'jikimo.workorder.exception')
|
||||
]))
|
||||
self.assertTrue(self.env['jikimo.message.template'].search([
|
||||
('name', '=', '工单加工失败提醒'),
|
||||
('model', '=', 'jikimo.workorder.exception')
|
||||
]))
|
||||
|
||||
def test_create_message_queue_yc0001(self):
|
||||
exception_record = self.env['jikimo.workorder.exception'].create({
|
||||
'workorder_id': self.workorder.id,
|
||||
'exception_code': 'YC0001',
|
||||
'exception_content': '无CNC程序'
|
||||
})
|
||||
|
||||
message_record = self.env['jikimo.message.queue'].search([
|
||||
('res_id', '=', exception_record.id),
|
||||
('model', '=', 'jikimo.workorder.exception'),
|
||||
('message_status', '=', 'pending')
|
||||
])
|
||||
self.assertFalse(message_record)
|
||||
|
||||
def test_create_message_queue_yc0002(self):
|
||||
exception_record = self.env['jikimo.workorder.exception'].create({
|
||||
'workorder_id': self.workorder.id,
|
||||
'exception_code': 'YC0002',
|
||||
'exception_content': '无功能刀具'
|
||||
})
|
||||
|
||||
bussiness_node = self.env['jikimo.message.bussiness.node'].search([
|
||||
('name', '=', '无功能刀具'),
|
||||
('model', '=', 'jikimo.workorder.exception')
|
||||
])
|
||||
|
||||
message_template = self.env['jikimo.message.template'].search([
|
||||
('bussiness_node_id', '=', bussiness_node.id),
|
||||
('model', '=', 'jikimo.workorder.exception')
|
||||
])
|
||||
|
||||
message_record = self.env['jikimo.message.queue'].search([
|
||||
('res_id', '=', exception_record.id),
|
||||
('model', '=', 'jikimo.workorder.exception'),
|
||||
('message_status', '=', 'pending'),
|
||||
('message_template_id', '=', message_template.id)
|
||||
])
|
||||
self.assertTrue(message_record)
|
||||
|
||||
def test_create_message_queue_yc0003(self):
|
||||
exception_record = self.env['jikimo.workorder.exception'].create({
|
||||
'workorder_id': self.workorder.id,
|
||||
'exception_code': 'YC0003',
|
||||
'exception_content': '无定位数据'
|
||||
})
|
||||
|
||||
bussiness_node = self.env['jikimo.message.bussiness.node'].search([
|
||||
('name', '=', '无定位数据'),
|
||||
('model', '=', 'jikimo.workorder.exception')
|
||||
])
|
||||
|
||||
message_template = self.env['jikimo.message.template'].search([
|
||||
('bussiness_node_id', '=', bussiness_node.id),
|
||||
('model', '=', 'jikimo.workorder.exception')
|
||||
])
|
||||
|
||||
message_record = self.env['jikimo.message.queue'].search([
|
||||
('res_id', '=', exception_record.id),
|
||||
('model', '=', 'jikimo.workorder.exception'),
|
||||
('message_status', '=', 'pending'),
|
||||
('message_template_id', '=', message_template.id)
|
||||
])
|
||||
self.assertTrue(message_record)
|
||||
|
||||
def test_create_message_queue_yc0004(self):
|
||||
exception_record = self.env['jikimo.workorder.exception'].create({
|
||||
'workorder_id': self.workorder.id,
|
||||
'exception_code': 'YC0004',
|
||||
'exception_content': '无CNC程序'
|
||||
})
|
||||
|
||||
message_record = self.env['jikimo.message.queue'].search([
|
||||
('res_id', '=', exception_record.id),
|
||||
('model', '=', 'jikimo.workorder.exception'),
|
||||
('message_status', '=', 'pending')
|
||||
])
|
||||
self.assertFalse(message_record)
|
||||
|
||||
def test_get_message(self):
|
||||
exception_record = self.env['jikimo.workorder.exception'].create({
|
||||
'workorder_id': self.workorder.id,
|
||||
'exception_code': 'YC0002',
|
||||
'exception_content': '无功能刀具'
|
||||
})
|
||||
message_queue_ids = self.env['jikimo.message.queue'].search([
|
||||
('res_id', '=', exception_record.id),
|
||||
('model', '=', 'jikimo.workorder.exception'),
|
||||
('message_status', '=', 'pending')
|
||||
])
|
||||
message = self.env['jikimo.workorder.exception']._get_message(message_queue_ids)
|
||||
self.assertTrue(message)
|
||||
|
||||
|
||||
BIN
quality_control/static/description/质量.png
Normal file
BIN
quality_control/static/description/质量.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 702 B |
@@ -1024,7 +1024,7 @@
|
||||
<menuitem
|
||||
id="menu_quality_root"
|
||||
name="Quality"
|
||||
web_icon="quality_control,static/description/icon.svg"
|
||||
web_icon="quality_control,static/description/质量.png"
|
||||
sequence="150"
|
||||
groups="quality.group_quality_user"/>
|
||||
|
||||
@@ -1033,7 +1033,7 @@
|
||||
name="Overview"
|
||||
action="quality_alert_team_action"
|
||||
parent="menu_quality_root"
|
||||
sequence="5"/>
|
||||
sequence="5" active="False"/>
|
||||
|
||||
<menuitem
|
||||
id="menu_quality_control"
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
pystrich
|
||||
cpca
|
||||
pycryptodome==3.20
|
||||
|
||||
@@ -38,3 +38,17 @@ class Manufacturing_Connect(http.Controller):
|
||||
res = {'Succeed': False, 'ErrorCode': 202, 'Error': e}
|
||||
logging.info('get_maintenance_tool_groups_Info error:%s' % e)
|
||||
return json.JSONEncoder().encode(res)
|
||||
|
||||
|
||||
class MultiInheritController():
|
||||
_sub_classes = []
|
||||
|
||||
def __init_subclass__(cls):
|
||||
"""
|
||||
多继承,解决多个字类时方法调用super的问题
|
||||
"""
|
||||
super().__init_subclass__()
|
||||
if len(cls._sub_classes) > 0 and cls not in cls._sub_classes:
|
||||
cls.__bases__ = (cls._sub_classes[-1],)
|
||||
if cls not in cls._sub_classes:
|
||||
cls._sub_classes.append(cls)
|
||||
|
||||
@@ -321,7 +321,7 @@ class ToolInventory(models.Model):
|
||||
prefix = fields.Char('前缀')
|
||||
postfix = fields.Char('后缀')
|
||||
diameter = fields.Float('直径(mm)')
|
||||
angle = fields.Float('R角(mm)')
|
||||
angle = fields.Float('R角(mm)',default=0)
|
||||
tool_length = fields.Float('刀具总长(mm)')
|
||||
blade_length = fields.Float('避空长/刃长(mm)')
|
||||
knife_head_name = fields.Char('刀头名称')
|
||||
|
||||
@@ -21,8 +21,9 @@ class ToolMaterialsBasicParameters(models.Model):
|
||||
neck_length = fields.Float('颈部长度(mm)')
|
||||
handle_diameter = fields.Float('柄部直径(mm)')
|
||||
handle_length = fields.Float('柄部长度(mm)')
|
||||
blade_tip_diameter = fields.Integer('刀尖直径(mm)')
|
||||
blade_tip_working_size = fields.Char('刀尖处理尺寸(R半径mm/倒角度)', size=20)
|
||||
blade_tip_diameter = fields.Float('刀尖直径(mm)')
|
||||
blade_tip_working_size = fields.Char('刀尖倒角度(°)', size=20)
|
||||
tip_r_size = fields.Float('刀尖R角(mm)',default=0)
|
||||
blade_tip_taper = fields.Integer('刀尖锥度(°)')
|
||||
blade_diameter = fields.Float('刃部直径(mm)')
|
||||
blade_length = fields.Float('刃部长度(mm)')
|
||||
@@ -37,7 +38,7 @@ class ToolMaterialsBasicParameters(models.Model):
|
||||
width = fields.Float('宽度(mm)')
|
||||
cutting_blade_length = fields.Float('切削刃长(mm)')
|
||||
relief_angle = fields.Integer('后角(°)')
|
||||
blade_tip_circular_arc_radius = fields.Char('刀尖圆弧半径(mm)', size=20)
|
||||
blade_tip_circular_arc_radius = fields.Char('刀尖圆弧半径(mm)', size=20,default='0')
|
||||
inscribed_circle_diameter = fields.Float('内接圆直径IC/D(mm)')
|
||||
install_aperture_diameter = fields.Float('安装孔直径(mm)')
|
||||
chip_breaker_groove = fields.Selection([('无', '无'), ('单面', '单面'), ('双面', '双面')],
|
||||
|
||||
@@ -30,6 +30,7 @@ patch(barcodeGenericHandlers, "start", {
|
||||
"O-CMD.PAGER-FIRST": () => updatePager("first"),
|
||||
"O-CMD.PAGER-LAST": () => updatePager("last"),
|
||||
"O-CMD.CONFIRM": () => customClickOnButton(".jikimo_button_confirm"),
|
||||
"O-CMD.FLUSHED": () => customClickOnButton(".jikimo_button_flushed"),
|
||||
};
|
||||
|
||||
barcode.bus.addEventListener("barcode_scanned", (ev) => {
|
||||
|
||||
@@ -159,9 +159,6 @@ td.o_required_modifier {
|
||||
display:inline;
|
||||
}
|
||||
.diameter{
|
||||
display: flex !important;
|
||||
justify-content: flex-start !important;
|
||||
align-items: center !important;
|
||||
}
|
||||
.o_address_format {
|
||||
display: flex !important;
|
||||
|
||||
@@ -109,7 +109,7 @@
|
||||
<field name="name">form.sf.machine_tool.type</field>
|
||||
<field name="model">sf.machine_tool.type</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="机床型号" delete="0">
|
||||
<form string="机床型号" delete="0">
|
||||
<sheet>
|
||||
<div class="oe_title">
|
||||
<h1>
|
||||
@@ -129,31 +129,28 @@
|
||||
<field name="machine_tool_picture" widget="image" nolabel="1"/>
|
||||
</group>
|
||||
</group>
|
||||
<group string="加工能力">
|
||||
<group string="加工能力">
|
||||
<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>
|
||||
<field name="name"/>
|
||||
<field name="image" widget="image"
|
||||
options="{'size': [100, 100], 'click enlarge': True}"/>
|
||||
|
||||
</tree>
|
||||
</field>
|
||||
</tree>
|
||||
</field>
|
||||
</div>
|
||||
|
||||
</group>
|
||||
<group string="冷却方式">
|
||||
<div>
|
||||
<field name='lq_image_id' widget="custom_many2many_checkboxes">
|
||||
<field name='lq_image_id' widget="custom_many2many_checkboxes">
|
||||
|
||||
<tree>
|
||||
<field name="name"/>
|
||||
<field name="image" widget="image"
|
||||
options="{'size': [100, 100], 'click enlarge': True}"/>
|
||||
<tree>
|
||||
<field name="name"/>
|
||||
<field name="image" widget="image"/>
|
||||
|
||||
</tree>
|
||||
</field>
|
||||
</tree>
|
||||
</field>
|
||||
</div>
|
||||
|
||||
</group>
|
||||
@@ -178,7 +175,7 @@
|
||||
<field name="workbench_H" class="o_address_zip" required="1"
|
||||
options="{'format': false}"/>
|
||||
</div>
|
||||
<field name="workpiece_load"/>
|
||||
<field name="workpiece_load"/>
|
||||
<label for="machine_tool_L" string="机床尺寸(mm)"/>
|
||||
<div class="test_model">
|
||||
<label for="machine_tool_L" string="长"/>
|
||||
@@ -192,7 +189,7 @@
|
||||
<field name="machine_tool_H" class="o_address_zip"
|
||||
options="{'format': false}"/>
|
||||
</div>
|
||||
<label for="T_trough_num" string="T型槽尺寸:"/>
|
||||
<label for="T_trough_num" string="T型槽尺寸:"/>
|
||||
<div class="test_model">
|
||||
<label for="T_trough_num" string="槽数"/>
|
||||
<field name="T_trough_num" class="o_address_zip"
|
||||
@@ -205,20 +202,20 @@
|
||||
<field name="T_trough_distance" class="o_address_zip"
|
||||
options="{'format': false}"/>
|
||||
</div>
|
||||
<!-- <field name="feed_speed" required="1"/>-->
|
||||
<!-- <label for="precision_min" string="X轴定位精度(mm)"/>-->
|
||||
<!-- <div class="test_model">-->
|
||||
<!-- <label for="precision_min" string="最小(min)"/>-->
|
||||
<!-- <field name="precision_min" class="o_address_zip" required="1"-->
|
||||
<!-- options="{'format': false}"/>-->
|
||||
<!-- <span>&nbsp;</span>-->
|
||||
<!-- <label for="precision_max" string="最大(max)"/>-->
|
||||
<!-- <field name="precision_max" class="o_address_zip" required="1"-->
|
||||
<!-- options="{'format': false}"/>-->
|
||||
<!-- </div>-->
|
||||
<!-- <field name="feed_speed" required="1"/>-->
|
||||
<!-- <label for="precision_min" string="X轴定位精度(mm)"/>-->
|
||||
<!-- <div class="test_model">-->
|
||||
<!-- <label for="precision_min" string="最小(min)"/>-->
|
||||
<!-- <field name="precision_min" class="o_address_zip" required="1"-->
|
||||
<!-- options="{'format': false}"/>-->
|
||||
<!-- <span>&nbsp;</span>-->
|
||||
<!-- <label for="precision_max" string="最大(max)"/>-->
|
||||
<!-- <field name="precision_max" class="o_address_zip" required="1"-->
|
||||
<!-- options="{'format': false}"/>-->
|
||||
<!-- </div>-->
|
||||
|
||||
<!-- <field name="lead_screw" required="1"/>-->
|
||||
<!-- <field name="guide_rail" required="1"/>-->
|
||||
<!-- <field name="lead_screw" required="1"/>-->
|
||||
<!-- <field name="guide_rail" required="1"/>-->
|
||||
<field name="number_of_axles" required="1" widget="radio"
|
||||
options="{'horizontal': true}"/>
|
||||
<label for="x_axis" string="加工行程(mm)"
|
||||
@@ -258,7 +255,7 @@
|
||||
</group>
|
||||
<group string="主轴">
|
||||
<field name="taper_type_id" required="1"/>
|
||||
<label for="distance_min" string="主轴端面-工作台距离(mm)"/>
|
||||
<label for="distance_min" string="主轴端面-工作台距离(mm)"/>
|
||||
<div class="test_model">
|
||||
<label for="distance_min" string="最小(min)"/>
|
||||
<field name="distance_min" class="o_address_zip"
|
||||
@@ -268,7 +265,7 @@
|
||||
<field name="distance_max" class="o_address_zip"
|
||||
options="{'format': false}"/>
|
||||
</div>
|
||||
<field name="rotate_speed" string="主轴最高转速(r/min)"
|
||||
<field name="rotate_speed" string="主轴最高转速(r/min)"
|
||||
options="{'format': false}"/>
|
||||
<field name="spindle_center_distance"/>
|
||||
<field name="spindle_continuous_power"/>
|
||||
@@ -286,50 +283,50 @@
|
||||
<page string="进给/精度参数">
|
||||
<group>
|
||||
<group string="进给参数">
|
||||
<field name="X_axis_rapid_traverse_speed"/>
|
||||
<field name="Y_axis_rapid_traverse_speed"/>
|
||||
<field name="Z_axis_rapid_traverse_speed"/>
|
||||
<field name="a_axis_rapid_traverse_speed"/>
|
||||
<field name="b_axis_rapid_traverse_speed"/>
|
||||
<field name="c_axis_rapid_traverse_speed"/>
|
||||
<field name="straight_cutting_feed_rate"/>
|
||||
<field name="rotary_cutting_feed_rate"/>
|
||||
<field name="X_axis_rapid_traverse_speed"/>
|
||||
<field name="Y_axis_rapid_traverse_speed"/>
|
||||
<field name="Z_axis_rapid_traverse_speed"/>
|
||||
<field name="a_axis_rapid_traverse_speed"/>
|
||||
<field name="b_axis_rapid_traverse_speed"/>
|
||||
<field name="c_axis_rapid_traverse_speed"/>
|
||||
<field name="straight_cutting_feed_rate"/>
|
||||
<field name="rotary_cutting_feed_rate"/>
|
||||
</group>
|
||||
<group string="精度参数">
|
||||
<field name="X_precision"/>
|
||||
<field name="X_precision_repeat"/>
|
||||
<field name="Y_precision"/>
|
||||
<field name="Y_precision_repeat"/>
|
||||
<field name="Z_precision"/>
|
||||
<field name="Z_precision_repeat"/>
|
||||
<field name="a_precision"/>
|
||||
<field name="a_precision_repeat"/>
|
||||
<field name="b_precision"/>
|
||||
<field name="b_precision_repeat"/>
|
||||
<field name="c_precision"/>
|
||||
<field name="c_precision_repeat"/>
|
||||
<field name="X_precision"/>
|
||||
<field name="X_precision_repeat"/>
|
||||
<field name="Y_precision"/>
|
||||
<field name="Y_precision_repeat"/>
|
||||
<field name="Z_precision"/>
|
||||
<field name="Z_precision_repeat"/>
|
||||
<field name="a_precision"/>
|
||||
<field name="a_precision_repeat"/>
|
||||
<field name="b_precision"/>
|
||||
<field name="b_precision_repeat"/>
|
||||
<field name="c_precision"/>
|
||||
<field name="c_precision_repeat"/>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
<page string="刀库参数">
|
||||
<group>
|
||||
<page string="刀库参数">
|
||||
<group>
|
||||
<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="tool_speed" required="1"/>-->
|
||||
<!-- <field name="tool_speed" required="1"/>-->
|
||||
<field name="tool_full_diameter_max"/>
|
||||
<field name="tool_perimeter_diameter_max"/>
|
||||
<field name="tool_long_max"/>
|
||||
<!-- <label for="tool_diameter_min" string="刀具刀径(mm)"/>-->
|
||||
<!-- <div class="test_model">-->
|
||||
<!-- <label for="tool_diameter_min" string="最小(min)"/>-->
|
||||
<!-- <field name="tool_diameter_min" class="o_address_zip" required="1"-->
|
||||
<!-- options="{'format': false}"/>-->
|
||||
<!-- <span>&nbsp;</span>-->
|
||||
<!-- <label for="tool_diameter_max" string="最大(max)"/>-->
|
||||
<!-- <field name="tool_diameter_max" class="o_address_zip" required="1"-->
|
||||
<!-- options="{'format': false}"/>-->
|
||||
<!-- </div>-->
|
||||
<!-- <label for="tool_diameter_min" string="刀具刀径(mm)"/>-->
|
||||
<!-- <div class="test_model">-->
|
||||
<!-- <label for="tool_diameter_min" string="最小(min)"/>-->
|
||||
<!-- <field name="tool_diameter_min" class="o_address_zip" required="1"-->
|
||||
<!-- options="{'format': false}"/>-->
|
||||
<!-- <span>&nbsp;</span>-->
|
||||
<!-- <label for="tool_diameter_max" string="最大(max)"/>-->
|
||||
<!-- <field name="tool_diameter_max" class="o_address_zip" required="1"-->
|
||||
<!-- options="{'format': false}"/>-->
|
||||
<!-- </div>-->
|
||||
<field name="tool_quality_max"/>
|
||||
<field name="T_tool_time"/>
|
||||
<field name="C_tool_time"/>
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
<field name="handle_length"/>
|
||||
<field name="blade_tip_diameter"/>
|
||||
<field name="blade_tip_working_size"/>
|
||||
<field name="tip_r_size"/>
|
||||
<field name="blade_tip_taper"/>
|
||||
<field name="blade_diameter"/>
|
||||
<field name="blade_length"/>
|
||||
@@ -95,6 +96,7 @@
|
||||
<field name="handle_length"/>
|
||||
<field name="blade_tip_diameter"/>
|
||||
<field name="blade_tip_working_size"/>
|
||||
<field name="tip_r_size"/>
|
||||
<field name="blade_tip_taper"/>
|
||||
<field name="blade_diameter"/>
|
||||
<field name="blade_length"/>
|
||||
@@ -139,6 +141,7 @@
|
||||
</group>
|
||||
<group attrs="{'invisible': [('cutting_tool_type', '!=', '整体式刀具')]}">
|
||||
<field name="blade_tip_working_size"/>
|
||||
<field name="tip_r_size"/>
|
||||
<field name="blade_tip_diameter" class="diameter"/>
|
||||
<field name="blade_tip_taper"/>
|
||||
<field name="blade_helix_angle"/>
|
||||
|
||||
@@ -222,6 +222,7 @@
|
||||
<field name="handle_diameter" class="diameter"/>
|
||||
<field name="handle_length"/>
|
||||
<field name="blade_tip_working_size" class="du"/>
|
||||
<field name="tip_r_size"/>
|
||||
<field name="blade_tip_diameter" class="diameter"/>
|
||||
<field name="blade_tip_taper" class="du"/>
|
||||
<field name="blade_helix_angle" class="du"/>
|
||||
@@ -577,7 +578,7 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_cutting_tool_material_search">
|
||||
<record model="ir.ui.view" id="view_cutting_tool_inventory_search">
|
||||
<field name="name">sf.tool.inventory.search</field>
|
||||
<field name="model">sf.tool.inventory</field>
|
||||
<field name="arch" type="xml">
|
||||
|
||||
@@ -55,8 +55,7 @@ class StatusChange(models.Model):
|
||||
logging.info('接口已经执行=============')
|
||||
else:
|
||||
traceback_error = traceback.format_exc()
|
||||
logging.error("bfm订单状态同步失败:%s request info %s" % traceback_error)
|
||||
logging.error('/api/get/state/get_order 请求失败{}'.format(ret))
|
||||
logging.error("bfm订单状态同步失败:%s" % traceback_error)
|
||||
raise UserError('工厂加工同步订单状态到bfm失败')
|
||||
except UserError as e:
|
||||
traceback_error = traceback.format_exc()
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
""",
|
||||
'category': 'sf',
|
||||
'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/product_data.xml',
|
||||
'data/uom_data.xml',
|
||||
|
||||
@@ -109,6 +109,19 @@
|
||||
'刀具')], 'required': True}
|
||||
</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//sheet//notebook" position="inside">
|
||||
<page string="2D加工图纸">
|
||||
<field name='machining_drawings' attrs="{'invisible': [('categ_type', '!=', '成品')]}"
|
||||
widget="adaptive_viewer"/>
|
||||
</page>
|
||||
</xpath>
|
||||
<xpath expr="//sheet//notebook" position="inside">
|
||||
<page string="质检标准">
|
||||
<field name='quality_standard' attrs="{'invisible': [('categ_type', '!=', '成品')]}"
|
||||
widget="adaptive_viewer"/>
|
||||
|
||||
</page>
|
||||
</xpath>
|
||||
<!-- <xpath expr="//field[@name='default_code']" position="attributes">-->
|
||||
<!-- <attribute name="attrs">{'readonly': [('categ_type', '=', '刀具')], 'invisible':-->
|
||||
<!-- [('product_variant_count', '>' , 1)]}-->
|
||||
@@ -191,6 +204,8 @@
|
||||
attrs="{'invisible': [('cutting_tool_type', '!=', '整体式刀具')],'readonly': [('id', '!=', False)]}"/>
|
||||
<field name="cutting_tool_blade_tip_working_size"
|
||||
attrs="{'invisible': [('cutting_tool_type', '!=', '整体式刀具')],'readonly': [('id', '!=', False)]}"/>
|
||||
<field name="cutting_tool_blade_tip_r_size"
|
||||
attrs="{'invisible': [('cutting_tool_type', '!=', '整体式刀具')],'readonly': [('id', '!=', False)]}"/>
|
||||
<field name="cutting_tool_blade_tip_diameter" string="刀尖直径(mm)" class="diameter"
|
||||
attrs="{'invisible': [('cutting_tool_type', '!=', '整体式刀具')],'readonly': [('id', '!=', False)]}"/>
|
||||
<field name="cutting_tool_blade_tip_taper" string="刀尖锥度(°)"
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
'sequence': 1,
|
||||
'category': 'sf',
|
||||
'website': 'https://www.sf.jikimo.com',
|
||||
'depends': ['hr'],
|
||||
'depends': ['base', 'hr'],
|
||||
'data': [
|
||||
'views/hr_employee.xml',
|
||||
'views/res_config_settings_views.xml',
|
||||
|
||||
@@ -20,7 +20,9 @@ class JkmPracticeEmployee(models.Model):
|
||||
if result['employee_list']:
|
||||
for employee_info in result['employee_list']:
|
||||
if employee_info['work_email']:
|
||||
self.sudo().search([('work_email', '=', employee_info['work_email'])]).write(
|
||||
{'we_id': employee_info['we_id']})
|
||||
hr_employee = self.sudo().search([('work_email', '=', employee_info['work_email'])])
|
||||
hr_employee.write({'we_id': employee_info['we_id']})
|
||||
if hr_employee.user_id:
|
||||
hr_employee.user_id.write({'we_employee_id': employee_info['we_id']})
|
||||
else:
|
||||
logging.info('_employee_info_sync error:%s' % result['message'])
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
'category': 'sf',
|
||||
'author': 'jikimo',
|
||||
'website': 'https://sf.cs.jikimo.com',
|
||||
'depends': ['web', 'mail', 'sf_base', 'sf_manufacturing', 'barcodes', ],
|
||||
'depends': ['web', 'sf_manufacturing', 'barcodes'],
|
||||
'data': [
|
||||
# 定义权限组放在最上面
|
||||
# 权限组
|
||||
@@ -30,7 +30,6 @@
|
||||
'views/machine_info_present.xml',
|
||||
'views/delivery_record.xml',
|
||||
'views/res_config_settings_views.xml',
|
||||
'views/maintenance_views.xml',
|
||||
|
||||
],
|
||||
'assets': {
|
||||
|
||||
@@ -15,7 +15,7 @@ db_config = {
|
||||
"user": "postgres",
|
||||
"password": "postgres",
|
||||
"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:
|
||||
return 0
|
||||
if time_str == 0:
|
||||
return 0
|
||||
|
||||
pattern = r"(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?"
|
||||
|
||||
@@ -162,7 +164,7 @@ class Sf_Dashboard_Connect(http.Controller):
|
||||
# 关机时长:初次上线时间 - 通电时间
|
||||
'power_off_time': power_off_time,
|
||||
# 关机率:关机时长/初次上线时间
|
||||
'power_off_rate': power_off_rate,
|
||||
# 'power_off_rate': power_off_rate,
|
||||
'first_online_duration': first_online_duration,
|
||||
# 停机时间:关机时间 - 运行时间
|
||||
# 停机时长:关机时间 - 初次上线时间
|
||||
@@ -187,11 +189,11 @@ class Sf_Dashboard_Connect(http.Controller):
|
||||
"""
|
||||
res = {'status': 1, 'message': '成功', 'data': {}}
|
||||
logging.info('前端请求日志数据的参数为:%s' % kw)
|
||||
# 连接数据库
|
||||
conn = psycopg2.connect(**db_config)
|
||||
cur = conn.cursor()
|
||||
|
||||
try:
|
||||
# 连接数据库
|
||||
conn = psycopg2.connect(**db_config)
|
||||
cur = conn.cursor()
|
||||
machine_list = ast.literal_eval(kw['machine_list'])
|
||||
begin_time_str = kw['begin_time'].strip('"')
|
||||
end_time_str = kw['end_time'].strip('"')
|
||||
@@ -231,6 +233,100 @@ class Sf_Dashboard_Connect(http.Controller):
|
||||
res['message'] = '前端请求日志数据失败,原因:%s' % e
|
||||
return json.dumps(res)
|
||||
|
||||
finally:
|
||||
if cur:
|
||||
cur.close()
|
||||
if conn:
|
||||
conn.close()
|
||||
|
||||
@http.route('/api/logs/page_data', type='http', auth='public', methods=['GET', 'POST'],
|
||||
csrf=False, cors="*")
|
||||
def logs_page_data(self, **kw):
|
||||
"""
|
||||
拿到日志数据返回给大屏展示(支持时间戳分页)
|
||||
:param kw:
|
||||
:return:
|
||||
"""
|
||||
res = {'status': 1, 'message': '成功', 'data': {}}
|
||||
logging.info('前端请求日志数据的参数为:%s' % kw)
|
||||
|
||||
# 连接数据库
|
||||
conn = psycopg2.connect(**db_config)
|
||||
cur = conn.cursor()
|
||||
try:
|
||||
# 获取并解析传递的参数
|
||||
machine_list = ast.literal_eval(kw.get('machine_list', '[]'))
|
||||
begin_time_str = kw.get('begin_time', '').strip('"')
|
||||
end_time_str = kw.get('end_time', '').strip('"')
|
||||
page = int(kw.get('page', 1)) # 默认页码为1
|
||||
page_size = int(kw.get('page_size', 80)) # 默认每页条数为10
|
||||
|
||||
begin_time = datetime.strptime(begin_time_str, '%Y-%m-%d %H:%M:%S')
|
||||
end_time = datetime.strptime(end_time_str, '%Y-%m-%d %H:%M:%S')
|
||||
|
||||
# 计算分页的 offset
|
||||
offset = (page - 1) * page_size
|
||||
|
||||
# 先查询符合条件的总记录数
|
||||
total_records = 0
|
||||
for item in machine_list:
|
||||
count_sql = '''
|
||||
SELECT COUNT(*)
|
||||
FROM device_data
|
||||
WHERE device_name = %s AND time >= %s AND time <= %s;
|
||||
'''
|
||||
# 执行总记录数查询
|
||||
cur.execute(count_sql, (item, begin_time, end_time))
|
||||
record_count = cur.fetchone()[0] # 获取总记录数
|
||||
total_records += record_count
|
||||
|
||||
# 计算总页数
|
||||
if total_records > 0:
|
||||
total_pages = (total_records + page_size - 1) // page_size # 向上取整
|
||||
else:
|
||||
total_pages = 0
|
||||
|
||||
# 将总页数和总记录数返回到响应中
|
||||
res['total_records'] = total_records
|
||||
res['total_pages'] = total_pages
|
||||
|
||||
for item in machine_list:
|
||||
sql = '''
|
||||
SELECT time, device_state, program_name
|
||||
FROM device_data
|
||||
WHERE device_name = %s AND time >= %s AND time <= %s
|
||||
ORDER BY time DESC
|
||||
LIMIT %s OFFSET %s;
|
||||
'''
|
||||
# 执行SQL命令,使用参数绑定
|
||||
cur.execute(sql, (item, begin_time, end_time, page_size, offset))
|
||||
results = cur.fetchall()
|
||||
|
||||
# 将数据按照 equipment_code 进行分组
|
||||
if item not in res['data']:
|
||||
res['data'][item] = []
|
||||
|
||||
for result in results:
|
||||
res['data'][item].append({
|
||||
'time': result[0].strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'state': result[1],
|
||||
'production_name': result[2],
|
||||
})
|
||||
|
||||
return json.dumps(res) # 返回分页数据
|
||||
|
||||
except Exception as e:
|
||||
logging.info('前端请求日志数据失败,原因:%s' % e)
|
||||
res['status'] = -1
|
||||
res['message'] = '前端请求日志数据失败,原因:%s' % e
|
||||
return json.dumps(res)
|
||||
|
||||
finally:
|
||||
if cur:
|
||||
cur.close()
|
||||
if conn:
|
||||
conn.close()
|
||||
|
||||
# 返回CNC机床列表
|
||||
@http.route('/api/CNCList', type='http', auth='public', methods=['GET', 'POST'], csrf=False,
|
||||
cors="*")
|
||||
@@ -306,20 +402,45 @@ class Sf_Dashboard_Connect(http.Controller):
|
||||
try:
|
||||
plan_obj = request.env['sf.production.plan'].sudo()
|
||||
production_obj = request.env['mrp.production'].sudo()
|
||||
work_order_obj = request.env['mrp.workorder'].sudo()
|
||||
line_list = ast.literal_eval(kw['line_list'])
|
||||
# print('line_list: %s' % line_list)
|
||||
for line in line_list:
|
||||
|
||||
# 工单计划量
|
||||
plan_data_total_counts = production_obj.search_count(
|
||||
[('production_line_id.name', '=', line), ('state', 'not in', ['cancel']),
|
||||
('active', '=', True)])
|
||||
# 工单完成量
|
||||
plan_data_finish_counts = plan_obj.search_count(
|
||||
[('production_line_id.name', '=', line), ('state', 'in', ['finished'])])
|
||||
# # 工单计划量
|
||||
# plan_data_plan_counts = plan_obj.search_count(
|
||||
# [('production_line_id.name', '=', line), ('state', 'not in', ['finished'])])
|
||||
# plan_data_total_counts = production_obj.search_count(
|
||||
# [('production_line_id.name', '=', line), ('state', 'not in', ['cancel']),
|
||||
# ('active', '=', True)])
|
||||
|
||||
# 工单计划量切换为CNC工单
|
||||
plan_data_total_counts = work_order_obj.search_count(
|
||||
[('production_id.production_line_id.name', '=', line),
|
||||
('state', 'in', ['ready', 'progress', 'done']), ('routing_type', '=', 'CNC加工')])
|
||||
|
||||
# # 工单完成量
|
||||
# plan_data_finish_counts = plan_obj.search_count(
|
||||
# [('production_line_id.name', '=', line), ('state', 'in', ['finished'])])
|
||||
|
||||
# 工单完成量切换为CNC工单
|
||||
plan_data_finish_counts = work_order_obj.search_count(
|
||||
[('production_id.production_line_id.name', '=', line),
|
||||
('state', 'in', ['done']), ('routing_type', '=', 'CNC加工')])
|
||||
|
||||
# 超期完成量
|
||||
# 搜索所有已经完成的工单
|
||||
plan_data_overtime = work_order_obj.search([
|
||||
('production_id.production_line_id.name', '=', line),
|
||||
('state', 'in', ['done']),
|
||||
('routing_type', '=', 'CNC加工')
|
||||
])
|
||||
|
||||
# 使用 filtered 进行字段比较
|
||||
plan_data_overtime_counts = plan_data_overtime.filtered(
|
||||
lambda order: order.date_finished > order.date_planned_finished
|
||||
)
|
||||
|
||||
# 获取数量
|
||||
plan_data_overtime_counts = len(plan_data_overtime_counts)
|
||||
|
||||
# 查找符合条件的生产计划记录
|
||||
plan_data = plan_obj.search([
|
||||
@@ -378,7 +499,8 @@ class Sf_Dashboard_Connect(http.Controller):
|
||||
pass_rate = 1
|
||||
if pass_nums:
|
||||
pass_rate = round(
|
||||
(len(pass_nums) / detection_data if len(plan_data_finish_orders) > 0 else 0), 3)
|
||||
# (len(pass_nums) / detection_data if len(plan_data_finish_orders) > 0 else 0), 3)
|
||||
(len(pass_nums) / len(plan_data_finish_orders) if len(plan_data_finish_orders) > 0 else 0), 3)
|
||||
|
||||
# 返工率
|
||||
rework_rate = round(
|
||||
@@ -418,8 +540,12 @@ class Sf_Dashboard_Connect(http.Controller):
|
||||
'plan_data_progress_deviation': plan_data_progress_deviation,
|
||||
'plan_data_rework_counts': plan_data_rework_counts,
|
||||
'on_time_rate': on_time_rate,
|
||||
'detection_data': detection_data,
|
||||
'pass_rate': pass_rate
|
||||
# 'detection_data': detection_data,
|
||||
'detection_data': plan_data_finish_counts,
|
||||
'pass_rate': (plan_data_finish_counts - plan_data_fault_counts) / plan_data_finish_counts,
|
||||
'plan_data_overtime_counts': plan_data_overtime_counts,
|
||||
'overtime_rate': plan_data_overtime_counts / plan_data_finish_counts
|
||||
if plan_data_finish_counts > 0 else 0,
|
||||
}
|
||||
res['data'][line] = data
|
||||
|
||||
@@ -489,11 +615,14 @@ class Sf_Dashboard_Connect(http.Controller):
|
||||
|
||||
for time_interval in time_intervals:
|
||||
start_time, end_time = time_interval
|
||||
# print(start_time, end_time)
|
||||
orders = plan_obj.search([('production_line_id.name', '=', line), ('state', 'in', ['finished']),
|
||||
(date_field_name, '>=', start_time.strftime('%Y-%m-%d 00:00:00')),
|
||||
(date_field_name, '<', end_time.strftime('%Y-%m-%d 00:00:00'))
|
||||
])
|
||||
|
||||
orders = plan_obj.search([
|
||||
('production_line_id.name', '=', line),
|
||||
('state', 'in', ['finished']),
|
||||
(date_field_name, '>=', start_time.strftime('%Y-%m-%d %H:%M:%S')),
|
||||
(date_field_name, '<=', end_time.strftime('%Y-%m-%d %H:%M:%S')) # 包括结束时间
|
||||
])
|
||||
|
||||
# 使用小时和分钟作为键,确保每个小时的数据有独立的键
|
||||
key = start_time.strftime('%H:%M:%S') # 只取小时:分钟:秒作为键
|
||||
time_count_dict[key] = len(orders)
|
||||
@@ -623,13 +752,19 @@ class Sf_Dashboard_Connect(http.Controller):
|
||||
# 未完成订单
|
||||
not_done_orders = plan_obj.search(
|
||||
[('production_line_id.name', '=', line), ('state', 'not in', ['finished']),
|
||||
('production_id.state', 'not in', ['cancel']), ('active', '=', True)
|
||||
('production_id.state', 'not in', ['cancel', 'done']), ('active', '=', True)
|
||||
])
|
||||
# print(not_done_orders)
|
||||
|
||||
# 完成订单
|
||||
# 获取当前时间,并计算24小时前的时间
|
||||
current_time = datetime.now()
|
||||
time_24_hours_ago = current_time - timedelta(hours=24)
|
||||
|
||||
finish_orders = plan_obj.search([
|
||||
('production_line_id.name', '=', line), ('state', 'in', ['finished']),
|
||||
('production_id.state', 'not in', ['cancel']), ('active', '=', True)
|
||||
('production_id.state', 'not in', ['cancel']), ('active', '=', True),
|
||||
('actual_end_time', '>=', time_24_hours_ago)
|
||||
])
|
||||
# print(finish_orders)
|
||||
|
||||
@@ -687,6 +822,8 @@ class Sf_Dashboard_Connect(http.Controller):
|
||||
not_done_data.append(line_dict)
|
||||
|
||||
for finish_order in finish_orders:
|
||||
if not finish_order.actual_end_time:
|
||||
continue
|
||||
blank_name = ''
|
||||
try:
|
||||
blank_name = finish_order.production_id.move_raw_ids[0].product_id.name
|
||||
@@ -707,7 +844,8 @@ class Sf_Dashboard_Connect(http.Controller):
|
||||
'material': material,
|
||||
'dimensions': dimensions,
|
||||
'order_qty': finish_order.product_qty,
|
||||
'finish_time': finish_order.actual_end_time.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'finish_time': finish_order.actual_end_time.strftime(
|
||||
'%Y-%m-%d %H:%M:%S') if finish_order.actual_end_time else ' '
|
||||
|
||||
}
|
||||
done_data.append(line_dict)
|
||||
@@ -745,10 +883,10 @@ class Sf_Dashboard_Connect(http.Controller):
|
||||
'''
|
||||
|
||||
sql2 = '''
|
||||
SELECT DISTINCT ON (alarm_time) alarm_time, alarm_repair_time
|
||||
SELECT DISTINCT ON (alarm_start_time) alarm_time, alarm_start_time
|
||||
FROM device_data
|
||||
WHERE device_name = %s AND alarm_time IS NOT NULL
|
||||
ORDER BY alarm_time, time;
|
||||
WHERE device_name = %s AND alarm_start_time IS NOT NULL
|
||||
ORDER BY alarm_start_time, time;
|
||||
|
||||
'''
|
||||
# 执行SQL命令
|
||||
@@ -764,8 +902,11 @@ class Sf_Dashboard_Connect(http.Controller):
|
||||
res['data'][item] = {'idle_count': row[0]}
|
||||
alarm_count = []
|
||||
for row in result2:
|
||||
alarm_count.append(row[0])
|
||||
total_alarm_time += abs(float(row[0]))
|
||||
alarm_count.append(row[1])
|
||||
if row[0]:
|
||||
total_alarm_time += abs(float(row[0]))
|
||||
else:
|
||||
total_alarm_time += 0.0
|
||||
if len(list(set(alarm_count))) == 1:
|
||||
if list(set(alarm_count))[0] is None:
|
||||
alarm_count_num = 0
|
||||
@@ -791,54 +932,27 @@ class Sf_Dashboard_Connect(http.Controller):
|
||||
"""
|
||||
查询设备的异常情况
|
||||
"""
|
||||
res = {'status': 1, 'message': '成功', 'data': {}}
|
||||
res = {'status': 1, 'message': '成功', 'data': []}
|
||||
logging.info('前端请求机床数据的参数为:%s' % kw)
|
||||
|
||||
# 连接数据库
|
||||
conn = psycopg2.connect(**db_config)
|
||||
cur = conn.cursor()
|
||||
try:
|
||||
# 获取请求的机床数据
|
||||
maintenance_logs_obj = request.env['sf.maintenance.logs'].sudo()
|
||||
# # 获取请求的机床数据
|
||||
# machine_list = ast.literal_eval(kw['machine_list'])
|
||||
# idle_times = []
|
||||
# idle_dict = {}
|
||||
|
||||
# for item in machine_list:
|
||||
sql = '''
|
||||
SELECT DISTINCT ON (alarm_time) alarm_time, alarm_message, system_date, system_time, alarm_repair_time
|
||||
FROM device_data
|
||||
WHERE alarm_time IS NOT NULL
|
||||
ORDER BY alarm_time, time;
|
||||
# machine_data = equipment_obj.search([('code', '=', item)])
|
||||
for log in maintenance_logs_obj.search([]):
|
||||
res['data'].append({
|
||||
'name': log.name,
|
||||
'alarm_time': log.alarm_time.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'fault_alarm_info': log.fault_alarm_info if log.fault_alarm_info else ' ',
|
||||
'fault_process': log.fault_process if log.fault_process else ' ',
|
||||
})
|
||||
|
||||
'''
|
||||
# 执行SQL命令
|
||||
cur.execute(sql)
|
||||
result = cur.fetchall()
|
||||
# print('result', result)
|
||||
|
||||
# 将查询结果转换为字典列表
|
||||
data = []
|
||||
for row in result:
|
||||
record = {
|
||||
'alarm_time': row[0],
|
||||
'alarm_message': row[1],
|
||||
'system_date': row[2],
|
||||
'system_time': row[3],
|
||||
'alarm_repair_time': row[4]
|
||||
}
|
||||
data.append(record)
|
||||
|
||||
# 将数据填充到返回结果中
|
||||
res['data'] = data
|
||||
|
||||
# 返回统计结果
|
||||
return json.dumps(res, ensure_ascii=False)
|
||||
except Exception as e:
|
||||
print(f"An error occurred: {e}")
|
||||
return json.dumps(res)
|
||||
finally:
|
||||
cur.close()
|
||||
conn.close()
|
||||
logging.error(f"An error occurred: {e}")
|
||||
|
||||
return json.dumps(res)
|
||||
|
||||
# 设备oee
|
||||
@http.route('/api/OEE', type='http', auth='public', methods=['GET', 'POST'], csrf=False, cors="*")
|
||||
@@ -1155,27 +1269,113 @@ class Sf_Dashboard_Connect(http.Controller):
|
||||
conn = psycopg2.connect(**db_config)
|
||||
# 获取请求的机床数据
|
||||
machine_list = ast.literal_eval(kw['machine_list'])
|
||||
time_threshold = datetime.now() - timedelta(days=1)
|
||||
|
||||
alarm_last_24_time = 0.0
|
||||
alarm_all_time = 0.0
|
||||
|
||||
def fetch_result_as_dict(cursor):
|
||||
"""辅助函数:将查询结果转为字典"""
|
||||
columns = [desc[0] for desc in cursor.description]
|
||||
return dict(zip(columns, cursor.fetchone())) if cursor.rowcount != 0 else None
|
||||
|
||||
# 获取当前时间的时间戳
|
||||
current_timestamp = datetime.now().timestamp()
|
||||
for item in machine_list:
|
||||
euipment_obj = request.env['maintenance.equipment'].sudo().search([('code', '=', item)])
|
||||
# 机床上线时间段
|
||||
first_online_duration = current_timestamp - euipment_obj.first_online_time.timestamp()
|
||||
with conn.cursor() as cur:
|
||||
cur.execute("""
|
||||
SELECT * FROM device_data
|
||||
WHERE device_name = %s
|
||||
AND device_state != '离线'
|
||||
ORDER BY time DESC
|
||||
LIMIT 1;
|
||||
""", (item,))
|
||||
SELECT * FROM device_data
|
||||
WHERE device_name = %s
|
||||
AND device_state != '离线' AND process_time IS NOT NULL
|
||||
ORDER BY time DESC
|
||||
LIMIT 1;
|
||||
""", (item,))
|
||||
last_all_time = fetch_result_as_dict(cur)
|
||||
with conn.cursor() as cur:
|
||||
cur.execute("""
|
||||
SELECT * FROM device_data
|
||||
WHERE device_name = %s
|
||||
AND device_state != '离线' AND time >= %s AND process_time IS NOT NULL
|
||||
ORDER BY time ASC
|
||||
LIMIT 1;
|
||||
""", (item, time_threshold))
|
||||
last_24_time = fetch_result_as_dict(cur)
|
||||
|
||||
with conn.cursor() as cur:
|
||||
cur.execute("""
|
||||
SELECT COUNT(*)
|
||||
FROM (
|
||||
SELECT DISTINCT ON (idle_start_time) idle_start_time
|
||||
FROM device_data
|
||||
WHERE device_name = %s AND idle_start_time IS NOT NULL AND time >= %s
|
||||
ORDER BY idle_start_time, time
|
||||
) subquery;
|
||||
""", (item, time_threshold))
|
||||
idle_count = cur.fetchone()[0]
|
||||
|
||||
alarm_last_24_nums = []
|
||||
with conn.cursor() as cur:
|
||||
cur.execute("""
|
||||
SELECT DISTINCT ON (alarm_start_time) alarm_time, alarm_start_time
|
||||
FROM device_data
|
||||
WHERE device_name = %s
|
||||
AND alarm_start_time IS NOT NULL AND time >= %s;
|
||||
""", (item, time_threshold))
|
||||
results = cur.fetchall()
|
||||
for result in results:
|
||||
alarm_last_24_nums.append(result[1])
|
||||
if result[0]:
|
||||
if float(result[0]) >= 1000:
|
||||
continue
|
||||
alarm_last_24_time += float(result[0])
|
||||
else:
|
||||
alarm_last_24_time += 0.0
|
||||
|
||||
alarm_all_nums = []
|
||||
with conn.cursor() as cur:
|
||||
cur.execute("""
|
||||
SELECT DISTINCT ON (alarm_start_time) alarm_time, alarm_start_time
|
||||
FROM device_data
|
||||
WHERE device_name = %s
|
||||
AND alarm_start_time IS NOT NULL;
|
||||
""", (item,))
|
||||
results = cur.fetchall()
|
||||
for result in results:
|
||||
alarm_all_nums.append(result[1])
|
||||
if result[0]:
|
||||
if float(result[0]) >= 1000:
|
||||
continue
|
||||
alarm_all_time += float(result[0])
|
||||
else:
|
||||
alarm_all_time += 0.0
|
||||
|
||||
# with conn.cursor() as cur:
|
||||
# cur.execute("""
|
||||
# SELECT * FROM device_data
|
||||
# WHERE device_name = %s
|
||||
# AND total_count IS NOT NULL
|
||||
# ORDER BY time ASC
|
||||
# LIMIT 1;
|
||||
# """, (item, ))
|
||||
# total_count = fetch_result_as_dict(cur)
|
||||
|
||||
# 返回数据
|
||||
res['data'][item] = {
|
||||
'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,
|
||||
'power_on_time': last_all_time['power_on_time'] if last_all_time['power_on_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_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_nums': len(list(set(alarm_last_24_nums))),
|
||||
'idle_count': idle_count,
|
||||
'first_online_time': first_online_duration,
|
||||
'alarm_all_time': alarm_all_time,
|
||||
'alarm_all_nums': len(list(set(alarm_all_nums)))
|
||||
# 'total_count': total_count['total_count'] if total_count else 0
|
||||
}
|
||||
|
||||
conn.close()
|
||||
|
||||
@@ -217,7 +217,7 @@ class Machine_ftp(models.Model):
|
||||
status = fields.Boolean('机床在线状态', readonly=True)
|
||||
# run_status = fields.Selection([('0', '空闲中'), ('1', '加工中'), ('2', '加工中'), ('3', '加工中')], string='机床运行状态',
|
||||
# readonly=True, default='0')
|
||||
run_status = fields.Char('机床运行状态', readonly=True)
|
||||
# run_status = fields.Char('机床运行状态', readonly=True)
|
||||
run_time = fields.Char('机床累计运行时长', readonly=True)
|
||||
# 机床系统日期
|
||||
system_date = fields.Char('机床系统日期', readonly=True)
|
||||
@@ -285,26 +285,6 @@ class Machine_ftp(models.Model):
|
||||
# # 开动率
|
||||
run_rate = fields.Char('开动率', readonly=True)
|
||||
|
||||
# 同步CNC设备到oee
|
||||
def sync_oee(self):
|
||||
"""
|
||||
同步CNC设备到oee
|
||||
:return:
|
||||
"""
|
||||
for record in self:
|
||||
record.ensure_one()
|
||||
cnc_oee_dict = {
|
||||
'equipment_id': record.id,
|
||||
'type_id': record.type_id.id,
|
||||
'machine_tool_picture': record.machine_tool_picture,
|
||||
'equipment_code': record.code,
|
||||
'function_type': record.function_type,
|
||||
}
|
||||
if self.env['maintenance.equipment.oee.logs'].search([('equipment_id', '=', record.id)]):
|
||||
self.env['maintenance.equipment.oee.logs'].write(cnc_oee_dict)
|
||||
else:
|
||||
self.env['maintenance.equipment.oee.logs'].create(cnc_oee_dict)
|
||||
|
||||
|
||||
class WorkCenterBarcode(models.Model):
|
||||
"""
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<group string='状态监控'>
|
||||
<group>
|
||||
<!-- <field name="timestamp"/> -->
|
||||
<field name="status"/>
|
||||
<!-- <field name="status"/> -->
|
||||
<field name="run_status"/>
|
||||
<field name="run_time"/>
|
||||
<field name="system_date"/>
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<odoo>
|
||||
<!-- 修改设备列表视图-->
|
||||
<record id="sf_machine_hr_equipment_view_tree_inherit" model="ir.ui.view">
|
||||
<field name="name">sf.machine.hr.equipment.view.tree.inherit</field>
|
||||
<field name="model">maintenance.equipment</field>
|
||||
<field name="inherit_id" ref="maintenance.hr_equipment_view_tree"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//tree" position="inside">
|
||||
<header>
|
||||
<button name="sync_oee" type="object" string="同步设备至OEE"/>
|
||||
</header>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
@@ -11,6 +11,7 @@
|
||||
'security/group_security.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'security/ir_rule_data.xml',
|
||||
'data/scheduled_actions.xml',
|
||||
'views/maintenance_logs_views.xml',
|
||||
'views/maintenance_equipment_oee_views.xml',
|
||||
'views/maintenance_views.xml',
|
||||
|
||||
14
sf_maintenance/data/scheduled_actions.xml
Normal file
14
sf_maintenance/data/scheduled_actions.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<record id="ir_cron_oee_get_running_datas" model="ir.cron">
|
||||
<field name="name">设备运行数据</field>
|
||||
<field name="model_id" ref="model_maintenance_equipment_oee"/>
|
||||
<field name="state">code</field>
|
||||
<field name="code">model.get_running_datas()</field>
|
||||
<field name="interval_number">15</field>
|
||||
<field name="interval_type">minutes</field>
|
||||
<field name="numbercall">-1</field>
|
||||
<field name="active" eval="True"/>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
3583
sf_maintenance/i18n/zh_CN.po
Normal file
3583
sf_maintenance/i18n/zh_CN.po
Normal file
File diff suppressed because it is too large
Load Diff
@@ -38,6 +38,8 @@ class SfMaintenanceEquipment(models.Model):
|
||||
|
||||
crea_url = "/api/machine_tool/create"
|
||||
|
||||
run_status = fields.Char('机床运行状态', readonly=True)
|
||||
|
||||
# AGV运行日志
|
||||
agv_logs = fields.One2many('maintenance.equipment.agv.log', 'equipment_id', string='AGV运行日志')
|
||||
# 1212修改后的字段
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
# -*-coding:utf-8-*-
|
||||
from odoo import fields, models
|
||||
from odoo import fields, models, api
|
||||
|
||||
|
||||
class SfMaintenanceLogs(models.Model):
|
||||
_name = 'sf.maintenance.logs'
|
||||
_description = '设备故障日志'
|
||||
_order = 'alarm_time desc'
|
||||
|
||||
code = fields.Char(string='编码')
|
||||
name = fields.Char(string='名称')
|
||||
code = fields.Char(string='编码', readonly=True)
|
||||
name = fields.Char(string='名称', compute='_compute_name')
|
||||
type = fields.Selection([('type1', '类型1'), ('type2', '类型2')], string='类型')
|
||||
brand = fields.Many2one('sf.machine.brand', related='maintenance_equipment_id.brand_id', string='品牌')
|
||||
maintenance_equipment_id = fields.Many2one('maintenance.equipment', string='机台号')
|
||||
@@ -28,3 +29,13 @@ class SfMaintenanceLogs(models.Model):
|
||||
fault_duration = fields.Float(string='故障时长')
|
||||
note = fields.Text(string='备注')
|
||||
active = fields.Boolean('Active', default=True)
|
||||
|
||||
@api.depends('code')
|
||||
def _compute_name(self):
|
||||
for record in self:
|
||||
if record.code:
|
||||
record.name = self.env['maintenance.equipment'].sudo().search([('code', '=', record.code), ('active', '=', True)]).name
|
||||
record.maintenance_equipment_id = self.env['maintenance.equipment'].sudo().search([('code', '=', record.code), ('active', '=', True)]).id
|
||||
else:
|
||||
record.name = ''
|
||||
|
||||
|
||||
@@ -1,5 +1,48 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import json
|
||||
import datetime
|
||||
import requests
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
def convert_to_seconds(time_str):
|
||||
# 修改正则表达式,使 H、M、S 部分可选
|
||||
|
||||
if time_str is None:
|
||||
return 0
|
||||
if time_str == 0:
|
||||
return 0
|
||||
|
||||
pattern = r"(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?"
|
||||
|
||||
match = re.match(pattern, time_str)
|
||||
|
||||
if match:
|
||||
# 提取各时间单位,如果某个单位缺失则默认设为0
|
||||
hours = int(match.group(1)) if match.group(1) else 0
|
||||
minutes = int(match.group(2)) if match.group(2) else 0
|
||||
seconds = int(match.group(3)) if match.group(3) else 0
|
||||
|
||||
# 计算总秒数
|
||||
total_seconds = hours * 3600 + minutes * 60 + seconds
|
||||
if total_seconds == 0:
|
||||
# return None
|
||||
pattern = r"(?:(\d+)小时)?(?:(\d+)分钟)?(?:(\d+)秒)?"
|
||||
match = re.match(pattern, time_str)
|
||||
if match:
|
||||
# 提取各时间单位,如果某个单位缺失则默认设为0
|
||||
hours = int(match.group(1)) if match.group(1) else 0
|
||||
minutes = int(match.group(2)) if match.group(2) else 0
|
||||
seconds = int(match.group(3)) if match.group(3) else 0
|
||||
|
||||
# 计算总秒数
|
||||
total_seconds = hours * 3600 + minutes * 60 + seconds
|
||||
return total_seconds
|
||||
else:
|
||||
return None
|
||||
return total_seconds
|
||||
|
||||
|
||||
class SfMaintenanceEquipmentOEE(models.Model):
|
||||
@@ -9,24 +52,300 @@ class SfMaintenanceEquipmentOEE(models.Model):
|
||||
name = fields.Char('设备oee')
|
||||
equipment_id = fields.Many2one('maintenance.equipment', '机台号',
|
||||
domain="[('category_id.equipment_type', '=', '机床'),('state_zc', '=', '已注册')]")
|
||||
|
||||
equipment_code = fields.Char('设备编码', related='equipment_id.code', store=True)
|
||||
|
||||
type_id = fields.Many2one('sf.machine_tool.type', '型号', related='equipment_id.type_id')
|
||||
machine_tool_picture = fields.Binary('设备图片', related='equipment_id.machine_tool_picture')
|
||||
state = fields.Selection(
|
||||
[("正常", "正常"), ("故障停机", "故障停机"), ("计划维保", "计划维保"), ("空闲", "空闲"),
|
||||
("封存(报废)", "封存(报废)")],
|
||||
default='正常', string="机床状态", related='equipment_id.state')
|
||||
run_time = fields.Float('加工时长(h)')
|
||||
equipment_time = fields.Float('开机时长(h)')
|
||||
done_nums = fields.Integer('加工件数')
|
||||
utilization_rate = fields.Char('可用率')
|
||||
fault_time = fields.Float('故障时长')
|
||||
fault_nums = fields.Integer('故障次数')
|
||||
# 故障率
|
||||
fault_rate = fields.Char('故障率')
|
||||
|
||||
online_time = fields.Char('开机时长(小时)', reaonly='True')
|
||||
|
||||
offline_time = fields.Char('关机时长(小时)', reaonly='True')
|
||||
idle_nums = fields.Integer('待机次数', reaonly='True')
|
||||
# 待机时长
|
||||
|
||||
idle_time = fields.Char('待机时长(小时)', reaonly='True')
|
||||
|
||||
# 待机率
|
||||
idle_rate = fields.Char('待机率(%)', reaonly='True')
|
||||
|
||||
work_time = fields.Char('加工时长(小时)', reaonly='True')
|
||||
work_rate = fields.Char('可用率(%)', reaonly='True')
|
||||
fault_time = fields.Char('故障时长(小时)', reaonly='True')
|
||||
fault_rate = fields.Char('故障率(%)', reaonly='True')
|
||||
fault_nums = fields.Integer('故障次数', reaonly='True')
|
||||
|
||||
# 设备故障日志
|
||||
sf_maintenance_logs_ids = fields.One2many('sf.maintenance.logs', 'maintenance_equipment_oee_id', '设备故障日志',
|
||||
related='equipment_id.sf_maintenance_logs_ids')
|
||||
oee_logs = fields.One2many('maintenance.equipment.oee.logs', 'equipment_oee_id', string='运行日志')
|
||||
|
||||
day_logs_detail = fields.Html('日运行日志详情')
|
||||
history_logs_detail = fields.Html('历史运行日志详情')
|
||||
begin_time = fields.Date('开始时间')
|
||||
end_time = fields.Date('结束时间')
|
||||
|
||||
def get_running_datas(self):
|
||||
base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
|
||||
url_time = base_url + '/api/RunningTimeDetail'
|
||||
cnc_list_obj = self.env['maintenance.equipment'].sudo().search(
|
||||
[('function_type', '!=', False), ('active', '=', True)])
|
||||
machine_list = list(map(lambda x: x.code, cnc_list_obj))
|
||||
# print('machine_list: %s' % machine_list)
|
||||
|
||||
data_time = {
|
||||
"machine_list": str(machine_list)
|
||||
}
|
||||
# 发送POST请求
|
||||
response_time = requests.post(url_time, json={}, data=data_time)
|
||||
# print(response_time.json())
|
||||
if response_time.status_code == 200:
|
||||
result_time = response_time.json()
|
||||
if result_time['status'] == 1:
|
||||
real_dict = result_time['data']
|
||||
for key in real_dict:
|
||||
# print(key)
|
||||
equipment_obj = self.env['maintenance.equipment.oee'].sudo().search([('equipment_code', '=', key)])
|
||||
if real_dict[key]['power_on_time'] == 0:
|
||||
equipment_obj.online_time = 0
|
||||
equipment_obj.idle_time = 0
|
||||
equipment_obj.idle_rate = 0
|
||||
equipment_obj.work_rate = 0
|
||||
equipment_obj.fault_time = 0
|
||||
equipment_obj.fault_rate = 0
|
||||
equipment_obj.fault_nums = 0
|
||||
equipment_obj.idle_nums = 0
|
||||
equipment_obj.work_time = 0
|
||||
else:
|
||||
equipment_obj.online_time = round(convert_to_seconds(real_dict[key]['power_on_time']) / 3600, 2)
|
||||
equipment_obj.work_time = round(convert_to_seconds(real_dict[key]['cut_time']) / 3600, 2)
|
||||
equipment_obj.fault_nums = real_dict[key]['alarm_all_nums']
|
||||
equipment_obj.idle_nums = real_dict[key]['idle_count']
|
||||
equipment_obj.fault_time = round((float(real_dict[key]['alarm_all_time']) if real_dict[key][
|
||||
'alarm_all_time'] else 0) / 3600, 2)
|
||||
equipment_obj.idle_time = float(equipment_obj.online_time) - float(
|
||||
equipment_obj.work_time) if equipment_obj.online_time and equipment_obj.work_time else 0
|
||||
equipment_obj.idle_rate = round(
|
||||
float(equipment_obj.idle_time) / (
|
||||
float(equipment_obj.online_time) if equipment_obj.online_time else 1) * 100, 2)
|
||||
equipment_obj.work_rate = round(
|
||||
float(equipment_obj.work_time) / (
|
||||
float(equipment_obj.online_time) if equipment_obj.online_time else 1) * 100, 2)
|
||||
|
||||
equipment_obj.fault_rate = round(
|
||||
float(equipment_obj.fault_time) / (
|
||||
float(equipment_obj.online_time) if equipment_obj.online_time else 1) * 100, 2)
|
||||
|
||||
# 获取当前时间的时间戳
|
||||
current_timestamp = datetime.datetime.now().timestamp()
|
||||
# 机床上线时间段
|
||||
first_online_duration = current_timestamp - int(equipment_obj.equipment_id.first_online_time.timestamp())
|
||||
|
||||
if equipment_obj.online_time:
|
||||
equipment_obj.offline_time = round((first_online_duration - float(equipment_obj.online_time)) / 3600, 2)
|
||||
else:
|
||||
equipment_obj.offline_time = False
|
||||
# equipment_obj.offline_time = equipment_obj.equipment_id.first_online_time - (
|
||||
# float(equipment_obj.online_time) if equipment_obj.online_time else 0)
|
||||
|
||||
# 获取日志详情
|
||||
def get_day_logs(self):
|
||||
base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
|
||||
print(base_url)
|
||||
config = self.env['ir.config_parameter'].sudo()
|
||||
# url = 'http://172.16.10.112:8069/api/logs/list'
|
||||
# url_time = 'http://localhost:9069/api/RunningTimeDetail'
|
||||
url = base_url + '/api/logs/list'
|
||||
url_time = base_url + '/api/RunningTimeDetail'
|
||||
machine_list = [self.equipment_code]
|
||||
begin_time = datetime.datetime.now().strftime('%Y-%m-%d') + ' 00:00:00'
|
||||
end_time = datetime.datetime.now().strftime('%Y-%m-%d') + ' 23:59:59'
|
||||
|
||||
# 请求的数据
|
||||
data = {
|
||||
"machine_list": str(machine_list),
|
||||
"begin_time": begin_time,
|
||||
"end_time": end_time
|
||||
}
|
||||
|
||||
data_time = {
|
||||
"machine_list": str(machine_list)
|
||||
}
|
||||
|
||||
print(data)
|
||||
|
||||
# 发送POST请求
|
||||
response = requests.post(url, json={}, data=data)
|
||||
response_time = requests.post(url_time, json={}, data=data_time)
|
||||
# print(response.json()) # 输出服务器返回的响应
|
||||
print(response_time.json())
|
||||
if response_time.status_code == 200:
|
||||
result_time = response_time.json()
|
||||
real_dict = result_time['data'][self.equipment_code]
|
||||
print('=', result_time)
|
||||
if result_time['status'] == 1:
|
||||
if real_dict['power_on_24_time'] == 0:
|
||||
self.online_time = 0
|
||||
self.idle_time = 0
|
||||
self.idle_rate = 0
|
||||
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)
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
print('============', result)
|
||||
if result['status'] == 1:
|
||||
logs_list = result['data'][self.equipment_code]
|
||||
logs_detail = ''
|
||||
log_state = ''
|
||||
for log in logs_list:
|
||||
if log['state'] != log_state:
|
||||
print('loooooooooooooooooooogs', log)
|
||||
production_name = log['production_name'] if log['production_name'] else ' '
|
||||
logs_detail += '<tr><td>' + log['time'] + '</td><td>' + log[
|
||||
'state'] + '</td><td>' + production_name + '</td></tr>'
|
||||
log_state = log['state']
|
||||
# self.day_logs_detail = '<table><tr><th>时间</th><th>事件/状态</th><th>加工工单</th></tr>' + logs_detail + '</table>'
|
||||
self.day_logs_detail = '''
|
||||
<table border="1" style="border-collapse: collapse; width: 100%; text-align: center;">
|
||||
<tr style="background-color: #f2f2f2;">
|
||||
<th style="padding: 8px; border: 1px solid #ddd;">时间</th>
|
||||
<th style="padding: 8px; border: 1px solid #ddd;">事件/状态</th>
|
||||
<th style="padding: 8px; border: 1px solid #ddd;">加工工单</th>
|
||||
</tr>
|
||||
{logs_detail}
|
||||
</table>
|
||||
'''.format(logs_detail=logs_detail)
|
||||
|
||||
else:
|
||||
self.day_logs_detail = '获取日志失败'
|
||||
else:
|
||||
self.day_logs_detail = '获取日志失败'
|
||||
|
||||
# 获取历史日志详情
|
||||
def get_history_logs(self):
|
||||
config = self.env['ir.config_parameter'].sudo()
|
||||
base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
|
||||
# url = 'http://172.16.10.112:8069/api/logs/list'
|
||||
url = base_url + '/api/logs/list'
|
||||
url_time = base_url + '/api/RunningTimeDetail'
|
||||
machine_list = [self.equipment_code]
|
||||
if not self.begin_time:
|
||||
raise UserError('请选择开始时间')
|
||||
if not self.end_time:
|
||||
raise UserError('请选择结束时间')
|
||||
|
||||
begin_time = self.begin_time.strftime('%Y-%m-%d') + ' 00:00:00'
|
||||
end_time = self.end_time.strftime('%Y-%m-%d') + ' 23:59:59'
|
||||
|
||||
# 请求的数据
|
||||
data = {
|
||||
"machine_list": str(machine_list),
|
||||
"begin_time": begin_time,
|
||||
"end_time": end_time
|
||||
}
|
||||
|
||||
print(data)
|
||||
|
||||
# 发送POST请求
|
||||
response = requests.post(url, json={}, data=data)
|
||||
print(response.json()) # 输出服务器返回的响应
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
print('============', result)
|
||||
if result['status'] == 1:
|
||||
logs_list = result['data'][self.equipment_code]
|
||||
logs_detail = ''
|
||||
log_state = ''
|
||||
for log in logs_list:
|
||||
if log['state'] != log_state:
|
||||
production_name = log['production_name'] if log['production_name'] else ' '
|
||||
logs_detail += '<tr><td>' + log['time'] + '</td><td>' + log[
|
||||
'state'] + '</td><td>' + production_name + '</td></tr>'
|
||||
log_state = log['state']
|
||||
# self.day_logs_detail = '<table><tr><th>时间</th><th>事件/状态</th><th>加工工单</th></tr>' + logs_detail + '</table>'
|
||||
self.history_logs_detail = '''
|
||||
<table border="1" style="border-collapse: collapse; width: 100%; text-align: center;">
|
||||
<tr style="background-color: #f2f2f2;">
|
||||
<th style="padding: 8px; border: 1px solid #ddd;">时间</th>
|
||||
<th style="padding: 8px; border: 1px solid #ddd;">事件/状态</th>
|
||||
<th style="padding: 8px; border: 1px solid #ddd;">加工工单</th>
|
||||
</tr>
|
||||
{logs_detail}
|
||||
</table>
|
||||
'''.format(logs_detail=logs_detail)
|
||||
|
||||
else:
|
||||
self.history_logs_detail = '获取日志失败'
|
||||
else:
|
||||
self.history_logs_detail = '获取日志失败'
|
||||
|
||||
# 下载历史日志
|
||||
def download_history_logs(self):
|
||||
config = self.env['ir.config_parameter'].sudo()
|
||||
url = 'http://172.16.10.112:8069/api/logs/list'
|
||||
machine_list = [self.equipment_code]
|
||||
if not self.begin_time:
|
||||
raise UserError('请选择开始时间')
|
||||
if not self.end_time:
|
||||
raise UserError('请选择结束时间')
|
||||
|
||||
begin_time = self.begin_time.strftime('%Y-%m-%d') + ' 00:00:00'
|
||||
end_time = self.end_time.strftime('%Y-%m-%d') + ' 23:59:59'
|
||||
|
||||
# 请求的数据
|
||||
data = {
|
||||
"machine_list": str(machine_list),
|
||||
"begin_time": begin_time,
|
||||
"end_time": end_time
|
||||
}
|
||||
|
||||
print(data)
|
||||
|
||||
# 发送POST请求
|
||||
response = requests.post(url, json={}, data=data)
|
||||
print(response.json()) # 输出服务器返回的响应
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
print('============', result)
|
||||
if result['status'] == 1:
|
||||
logs_list = result['data'][self.equipment_code]
|
||||
logs_detail = ''
|
||||
for log in logs_list:
|
||||
production_name = log['production_name'] if log['production_name'] else ' '
|
||||
# todo 下载日志
|
||||
|
||||
else:
|
||||
self.history_logs_detail = '下载日志失败'
|
||||
else:
|
||||
self.history_logs_detail = '下载日志失败'
|
||||
|
||||
def name_get(self):
|
||||
result = []
|
||||
@@ -95,4 +414,3 @@ class SfMaintenanceEquipmentOEELogDetail(models.Model):
|
||||
log_id = fields.Many2one('maintenance.equipment.oee.logs', '日志')
|
||||
# equipment_code = fields.Char('设备编码', related='log_id.equipment_code')
|
||||
equipment_code = fields.Char('设备编码', readonly='True')
|
||||
|
||||
|
||||
@@ -67,6 +67,3 @@ access_sf_cutting_tool_type_admin_sf_group_equipment_user,sf_cutting_tool_type_a
|
||||
access_sf_cutting_tool_type_group_purchase_director_sf_group_equipment_user,sf_cutting_tool_type_group_purchase_director,sf_base.model_sf_cutting_tool_type,sf_maintenance.sf_group_equipment_user,1,0,0,0
|
||||
access_sf_cutting_tool_type_group_sale_director_sf_group_equipment_user,sf_cutting_tool_type_group_sale_director,sf_base.model_sf_cutting_tool_type,sf_maintenance.sf_group_equipment_user,1,0,0,0
|
||||
access_sf_cutting_tool_type_group_plan_director_sf_group_equipment_user,sf_cutting_tool_type_group_plan_director,sf_base.model_sf_cutting_tool_type,sf_maintenance.sf_group_equipment_user,1,0,0,0
|
||||
|
||||
access_maintenance_equipment_oee_logs,maintenance_equipment_oee_logs,model_maintenance_equipment_oee_logs,sf_maintenance.sf_group_equipment_manager,1,1,1,1
|
||||
access_maintenance_equipment_oee_log_detail,maintenance_equipment_oee_log_detail,model_maintenance_equipment_oee_log_detail,sf_maintenance.sf_group_equipment_manager,1,1,1,1
|
||||
|
BIN
sf_maintenance/static/description/维护.png
Normal file
BIN
sf_maintenance/static/description/维护.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 858 B |
@@ -8,13 +8,13 @@
|
||||
<field name="arch" type="xml">
|
||||
<tree>
|
||||
<field name="equipment_id"/>
|
||||
<field name="equipment_time"/>
|
||||
<field name="run_time"/>
|
||||
<field name="done_nums"/>
|
||||
<field name="utilization_rate"/>
|
||||
<field name="online_time"/>
|
||||
<field name="work_time"/>
|
||||
<field name="work_rate"/>
|
||||
<field name="fault_time"/>
|
||||
<field name="fault_nums"/>
|
||||
<field name="fault_rate"/>
|
||||
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
@@ -37,32 +37,102 @@
|
||||
|
||||
<group>
|
||||
<group>
|
||||
<field name="type_id" readonly="1"/>
|
||||
<field name="equipment_time"/>
|
||||
<field name="run_time"/>
|
||||
<field name="done_nums"/>
|
||||
<field name="utilization_rate"/>
|
||||
<field name="fault_time"/>
|
||||
<field name="fault_nums"/>
|
||||
<group>
|
||||
<field name="equipment_id" domain="[('name','ilike','加工中心')]"/>
|
||||
<field name="type_id"/>
|
||||
<field name="state"/>
|
||||
<field name="equipment_code"/>
|
||||
</group>
|
||||
</group>
|
||||
<group>
|
||||
<field name="machine_tool_picture" widget="image" readonly="1"/>
|
||||
<group>
|
||||
<!-- <field name="state" nolabel="1"/> -->
|
||||
<!-- <field name="state" string=""/> -->
|
||||
</group>
|
||||
<group>
|
||||
<field name="machine_tool_picture" widget="image" nolabel="1"/>
|
||||
</group>
|
||||
</group>
|
||||
</group>
|
||||
<group>
|
||||
<group>
|
||||
<group>
|
||||
<field name="online_time" readonly="1"/>
|
||||
<field name="offline_time" readonly="1"/>
|
||||
<field name="fault_rate" readonly="1"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="idle_nums" readonly="1"/>
|
||||
<field name="fault_time" readonly="1"/>
|
||||
<field name="fault_nums" readonly="1"/>
|
||||
</group>
|
||||
</group>
|
||||
<group>
|
||||
<group>
|
||||
<field name="idle_time"/>
|
||||
<field name="idle_rate"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="work_time"/>
|
||||
<field name="work_rate"/>
|
||||
</group>
|
||||
</group>
|
||||
|
||||
</group>
|
||||
<!-- <notebook> -->
|
||||
<!-- <page string="运行日志"> -->
|
||||
<!-- <field name="oee_logs"> -->
|
||||
<!-- <tree create="1" edit="1" delete="1" editable="bottom"> -->
|
||||
<!-- <field name = 'run_time'/> -->
|
||||
<!-- <field name = 'state'/> -->
|
||||
<!-- <field name = 'workorder_id'/> -->
|
||||
<!-- <field name = 'time'/> -->
|
||||
<!-- <field name = 'color' widget="color"/> -->
|
||||
<!-- </tree> -->
|
||||
<!-- </field> -->
|
||||
<!-- <page string="24H日志详情"> -->
|
||||
<!-- <group> -->
|
||||
<!-- <button name="get_day_logs" type="object" string="查看24H日志" t-attf-style="white-space:nowrap;"/> -->
|
||||
<!-- </group> -->
|
||||
<!-- <field name="day_logs_detail" readonly="1" widget="html"/> -->
|
||||
<!-- <field name="page_num"/> -->
|
||||
<!-- <group> -->
|
||||
<!-- <group> -->
|
||||
<!-- <button name="previous_day_logs" type="object" string="上一页" t-attf-style="white-space:nowrap;"/> -->
|
||||
<!-- </group> -->
|
||||
<!-- <group> -->
|
||||
<!-- <button name="next_day_logs" type="object" string="下一页" t-attf-style="white-space:nowrap;"/> -->
|
||||
<!-- </group> -->
|
||||
<!-- </group> -->
|
||||
|
||||
|
||||
<!-- <field name="day_logs_detail"/> -->
|
||||
<!-- <field name="day_logs_detail" widget="html" nolabel="1"/> -->
|
||||
<!-- <field name="day_logs_detail" widget="html" nolabel="1" class="oe_form_field oe_form_field_html"/> -->
|
||||
<!-- <field name="day_logs_detail" widget="html" nolabel="1" class="oe_form_field oe_form_field_html" options='{"class": "o_form_readonly"}'/> -->
|
||||
<!-- <field name="day_logs_detail" widget="html" nolabel="1" class="oe_form_field oe_form_field_html" options='{"class": "o_form_readonly", "readonly": true, "style": "width: 100%; height: 400px;"}'/> -->
|
||||
<!-- <group> -->
|
||||
<!-- <div class="oe_html_field"> -->
|
||||
<!-- <div id="pagination_day_logs"> -->
|
||||
<!-- <button id="prev_page_day_logs" disabled="true">Previous</button> -->
|
||||
<!-- <button id="next_page_day_logs">Next</button> -->
|
||||
<!-- </div> -->
|
||||
<!-- </div> -->
|
||||
<!-- </group> -->
|
||||
<!-- </page> -->
|
||||
<!-- </notebook> -->
|
||||
<!-- <page string="历史日志详情"> -->
|
||||
<!-- <group> -->
|
||||
<!-- <group> -->
|
||||
<!-- <group> -->
|
||||
<!-- <field name="begin_time"/> -->
|
||||
<!-- </group> -->
|
||||
<!-- <group> -->
|
||||
<!-- <field name="end_time"/> -->
|
||||
<!-- </group> -->
|
||||
<!-- </group> -->
|
||||
<!-- <group> -->
|
||||
<!-- <group> -->
|
||||
<!-- <button name="get_history_logs" type="object" string="查看历史日志" t-attf-style="white-space:nowrap;"/> -->
|
||||
<!-- </group> -->
|
||||
<!-- <group> -->
|
||||
<!-- <button name="download_history_logs" type="object" string="下载历史日志" t-attf-style="white-space:nowrap;"/> -->
|
||||
<!-- </group> -->
|
||||
<!-- </group> -->
|
||||
|
||||
<!-- </group> -->
|
||||
<!-- <field name="history_logs_detail"/> -->
|
||||
<!-- </page> -->
|
||||
<!-- </notebook> -->
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
<field name="arch" type="xml">
|
||||
<tree>
|
||||
<field name="type" optional="hide"/>
|
||||
<field name="brand"/>
|
||||
<field name="maintenance_equipment_id"/>
|
||||
<!-- <field name="brand"/> -->
|
||||
<field name="name"/>
|
||||
<field name="code_location" optional="hide"/>
|
||||
<field name="fault_code" optional="hide"/>
|
||||
<field name="alarm_time"/>
|
||||
@@ -37,13 +37,14 @@
|
||||
<sheet>
|
||||
<div class="oe_title">
|
||||
<h1>
|
||||
<field name="code" readonly="1"/>
|
||||
<field name="name" readonly="1"/>
|
||||
</h1>
|
||||
</div>
|
||||
<group>
|
||||
<group>
|
||||
|
||||
<!-- <field name="name"/> -->
|
||||
|
||||
<field name="code"/>
|
||||
<!-- <field name="type" required="1" widget="radio" options="{'horizontal': true}"/> -->
|
||||
<field name="maintenance_equipment_id"/>
|
||||
<field name="brand"/>
|
||||
@@ -57,7 +58,6 @@
|
||||
</group>
|
||||
<group>
|
||||
<field name="operator"/>
|
||||
|
||||
<field name="fault_process"/>
|
||||
<!-- <field name="alarm_way" required="1" widget="radio" options="{'horizontal': true}"/> -->
|
||||
<field name="recovery_time"/>
|
||||
@@ -105,249 +105,6 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- 设备运行日志 -->
|
||||
<record id="view_maintenance_logs_run_tree" model="ir.ui.view">
|
||||
<field name="name">maintenance.logs.run.tree</field>
|
||||
<field name="model">maintenance.equipment.oee.logs</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree>
|
||||
<field name="equipment_id"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_maintenance_logs_run_form" model="ir.ui.view">
|
||||
<field name="name">maintenance.logs.run.form</field>
|
||||
<field name="model">maintenance.equipment.oee.logs</field>
|
||||
<field name="arch" type="xml">
|
||||
<!-- <form string="设备运行日志"> -->
|
||||
<!-- <header> -->
|
||||
<!-- <field name="equipment_id" readonly="1"/> -->
|
||||
<!-- </header> -->
|
||||
<!-- <sheet> -->
|
||||
<!-- <div class="oe_title"> -->
|
||||
<!-- <h1> -->
|
||||
<!-- <field name="start_time" readonly="1"/> -->
|
||||
<!-- </h1> -->
|
||||
<!-- </div> -->
|
||||
<!-- <group> -->
|
||||
<!-- <group> -->
|
||||
<!-- <field name="stop_time" readonly="1"/> -->
|
||||
<!-- <field name="duration" readonly="1"/> -->
|
||||
<!-- <field name="oee" readonly="1"/> -->
|
||||
<!-- </group> -->
|
||||
<!-- <group> -->
|
||||
<!-- <field name="note"/> -->
|
||||
<!-- </group> -->
|
||||
|
||||
<!-- </group> -->
|
||||
<!-- </sheet> -->
|
||||
<!-- </form> -->
|
||||
<form string="设备运行日志">
|
||||
<!-- <header> -->
|
||||
<!-- <field name="name" readonly="1"/> -->
|
||||
<!-- </header> -->
|
||||
<sheet>
|
||||
<div class="oe_title">
|
||||
<h1>
|
||||
<field name="name"/>
|
||||
</h1>
|
||||
</div>
|
||||
<group>
|
||||
<group>
|
||||
<group>
|
||||
<field name="equipment_id" domain="[('name','ilike','加工中心')]"/>
|
||||
<field name="type_id"/>
|
||||
<field name="state"/>
|
||||
<field name="equipment_code"/>
|
||||
<field name="function_type"/>
|
||||
</group>
|
||||
</group>
|
||||
<group>
|
||||
<group>
|
||||
<!-- <field name="state" nolabel="1"/> -->
|
||||
<field name="state" string=""/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="machine_tool_picture" widget="image" nolabel="1"/>
|
||||
</group>
|
||||
</group>
|
||||
</group>
|
||||
<group>
|
||||
<group>
|
||||
<group>
|
||||
<field name="online_time" readonly="1"/>
|
||||
<field name="offline_time" readonly="1"/>
|
||||
<field name="fault_rate" readonly="1"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="offline_nums" readonly="1"/>
|
||||
<field name="fault_time" readonly="1"/>
|
||||
<field name="fault_nums" readonly="1"/>
|
||||
</group>
|
||||
</group>
|
||||
<group>
|
||||
<group>
|
||||
<field name="idle_time"/>
|
||||
<field name="idle_rate"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="work_time"/>
|
||||
<field name="work_rate"/>
|
||||
</group>
|
||||
</group>
|
||||
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="24H日志详情">
|
||||
<!-- 筛选出24小时内的日志 -->
|
||||
<!-- <field name="detail_ids" domain="[('time','<',(datetime.datetime.now() - datetime.timedelta(days=1)).strftime('%Y-%m-%d %H:%M:%S'))]"> -->
|
||||
<field name="detail_ids" domain="[('state','ilike','加工')]">
|
||||
<tree>
|
||||
<!-- <field name="sequence"/> -->
|
||||
<field name="time"/>
|
||||
<field name="state"/>
|
||||
<field name="production_name"/>
|
||||
</tree>
|
||||
<!-- <form> -->
|
||||
<!-- <field name="sequence"/> -->
|
||||
<!-- <field name="time"/> -->
|
||||
<!-- <field name="state"/> -->
|
||||
<!-- <field name="production_id"/> -->
|
||||
<!-- </form> -->
|
||||
|
||||
</field>
|
||||
</page>
|
||||
<page string="历史日志详情">
|
||||
<field name="detail_ids">
|
||||
<tree>
|
||||
<!-- <field name="sequence"/> -->
|
||||
<field name="time"/>
|
||||
<field name="state"/>
|
||||
<field name="production_name"/>
|
||||
</tree>
|
||||
<!-- <form> -->
|
||||
<!-- <field name="sequence"/> -->
|
||||
<!-- <field name="time"/> -->
|
||||
<!-- <field name="state"/> -->
|
||||
<!-- <field name="production_id"/> -->
|
||||
<!-- </form> -->
|
||||
|
||||
</field>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<!-- <record id="view_maintenance_logs_run_search" model="ir.ui.view"> -->
|
||||
<!-- <field name="name">maintenance.logs.run.search</field> -->
|
||||
<!-- <field name="model">maintenance.equipment.oee.logs</field> -->
|
||||
<!-- <field name="arch" type="xml"> -->
|
||||
<!-- <search> -->
|
||||
<!-- <field name="equipment_id"/> -->
|
||||
<!-- <field name="start_time"/> -->
|
||||
<!-- <field name="stop_time"/> -->
|
||||
<!-- <field name="duration"/> -->
|
||||
<!-- <field name="oee"/> -->
|
||||
<!-- <field name="note"/> -->
|
||||
<!-- </search> -->
|
||||
|
||||
<!-- </field> -->
|
||||
<!-- </record> -->
|
||||
|
||||
<!-- 设备运行日志详情 -->
|
||||
<record id="view_maintenance_logs_run_detail_tree" model="ir.ui.view">
|
||||
<field name="name">maintenance.logs.run.detail.tree</field>
|
||||
<field name="model">maintenance.equipment.oee.log.detail</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree>
|
||||
<!-- <field name="sequence"/> -->
|
||||
<field name="time"/>
|
||||
<field name="state"/>
|
||||
<field name="production_name"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_maintenance_logs_run_detail_form" model="ir.ui.view">
|
||||
<field name="name">maintenance.logs.run.detail.form</field>
|
||||
<field name="model">maintenance.equipment.oee.log.detail</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="设备运行日志详情">
|
||||
<sheet>
|
||||
<group>
|
||||
<group>
|
||||
<field name="state"/>
|
||||
<!-- <field name="production_id"/> -->
|
||||
</group>
|
||||
<group>
|
||||
<!-- <field name="sequence"/> -->
|
||||
<field name="time"/>
|
||||
</group>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- <record id="view_maintenance_logs_run_detail_search" model="ir.ui.view"> -->
|
||||
<!-- <field name="name">maintenance.logs.run.detail.search</field> -->
|
||||
<!-- <field name="model">maintenance.equipment.oee.logs.detail</field> -->
|
||||
<!-- <field name="arch" type="xml"> -->
|
||||
<!-- <search> -->
|
||||
<!-- <field name="equipment_id"/> -->
|
||||
<!-- <field name="start_time"/> -->
|
||||
<!-- <field name="stop_time"/> -->
|
||||
<!-- <field name="duration"/> -->
|
||||
<!-- <field name="oee"/> -->
|
||||
<!-- <field name="note"/> -->
|
||||
<!-- </search> -->
|
||||
<!-- </field> -->
|
||||
<!-- </record> -->
|
||||
|
||||
<!-- 设备运行日志详情action -->
|
||||
<!-- <record id="action_maintenance_logs_run_detail" model="ir.actions.act_window"> -->
|
||||
<!-- <field name="name">设备运行日志详情</field> -->
|
||||
<!-- <field name="type">ir.actions.act_window</field> -->
|
||||
<!-- <field name="res_model">maintenance.equipment.oee.logs.detail</field> -->
|
||||
<!-- <field name="view_mode">tree,form</field> -->
|
||||
<!-- <field name="view_id" ref="view_maintenance_logs_run_detail_tree"/> -->
|
||||
<!-- <field name="help" type="html"> -->
|
||||
<!-- <p class="oe_view_nocontent_create"> -->
|
||||
<!-- 设备运行日志详情 -->
|
||||
<!-- </p> -->
|
||||
<!-- </field> -->
|
||||
<!-- -->
|
||||
<!-- </record> -->
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<record id="action_maintenance_logs_run" model="ir.actions.act_window">
|
||||
<field name="name">设备运行日志</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">maintenance.equipment.oee.logs</field>
|
||||
<!-- <field name="search_view_id" ref="view_maintenance_logs_run_search"/> -->
|
||||
<field name="view_mode">tree,form</field>
|
||||
<!-- <field name="view_mode">form</field> -->
|
||||
<field name="view_id" ref="view_maintenance_logs_run_tree"/>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
设备运行日志
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<menuitem name="设备运行日志" id="menu_maintenance_logs_run" parent="maintenance.menu_m_request"
|
||||
sequence="10" action="action_maintenance_logs_run"/>
|
||||
|
||||
|
||||
<!-- Action -->
|
||||
|
||||
<record id="action_maintenance_logs" model="ir.actions.act_window">
|
||||
|
||||
@@ -1142,14 +1142,16 @@
|
||||
</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 t-attf-class="#{record.state.raw_value == '正常' ? 'color_1' : ''}"></div>
|
||||
<div t-attf-class="#{record.state.raw_value == '故障' ? 'color_2' : ''}"></div>
|
||||
<div t-attf-class="#{record.state.raw_value == '不可用' ? 'color_3' : ''}"></div>
|
||||
<div t-attf-class="#{record.run_status.raw_value == '运行中' ? 'color_1' : ''}"></div>
|
||||
<div t-attf-class="#{record.run_status.raw_value == '待机' ? 'color_4' : ''}"></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"
|
||||
t-attf-class="#{record.state.raw_value == '正常' ? 'font_color_1' : ''}
|
||||
#{record.state.raw_value == '故障' ? 'font_color_2' : ''}
|
||||
#{record.state.raw_value == '不可用' ? 'font_color_3' : ''}">
|
||||
<field name="state"/>
|
||||
t-attf-class="#{record.run_status.raw_value == '运行中' ? 'font_color_1' : ''}
|
||||
#{record.run_status.raw_value == '待机' ? 'font_color_4' : ''}
|
||||
#{record.run_status.raw_value == '故障' ? 'font_color_2' : ''}
|
||||
#{record.run_status.raw_value == '离线' ? 'font_color_3' : ''}">
|
||||
<field name="run_status"/>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1230,5 +1232,9 @@
|
||||
action="hr_equipment_action1"
|
||||
sequence="0"/>
|
||||
|
||||
<menuitem
|
||||
id="maintenance.menu_maintenance_title"
|
||||
web_icon="sf_maintenance,static/description/维护.png"/>
|
||||
|
||||
|
||||
</odoo>
|
||||
@@ -189,6 +189,7 @@ class Manufacturing_Connect(http.Controller):
|
||||
request.env['sf.production.plan'].sudo().search([('production_id', '=', production_id)]).write(
|
||||
{'actual_start_time': workorder.date_start,
|
||||
'state': 'processing'})
|
||||
res.update({'workorder_id': workorder.id})
|
||||
|
||||
except Exception as e:
|
||||
res = {'Succeed': False, 'ErrorCode': 202, 'Error': e}
|
||||
@@ -595,14 +596,6 @@ class Manufacturing_Connect(http.Controller):
|
||||
if panel_workorder:
|
||||
panel_workorder.write({'production_line_state': '已下产线'})
|
||||
workorder.write({'state': 'to be detected'})
|
||||
# workpiece_delivery = request.env['sf.workpiece.delivery'].sudo().search(
|
||||
# [
|
||||
# ('rfid_code', '=', rfid_code), ('type', '=', '下产线'),
|
||||
# ('production_id', '=', order.production_id.id),
|
||||
# ('workorder_id', '=', order.id),
|
||||
# ('workorder_state', '=', 'done')])
|
||||
# if workpiece_delivery:
|
||||
# delivery_Arr.append(workpiece_delivery.id)
|
||||
else:
|
||||
res = {'Succeed': False, 'ErrorCode': 204,
|
||||
'Error': 'DeviceId为%s没有对应的已配送工件数据' % ret['DeviceId']}
|
||||
@@ -695,4 +688,4 @@ class Manufacturing_Connect(http.Controller):
|
||||
except Exception as e:
|
||||
res = {'Succeed': False, 'ErrorCode': 202, 'Error': str(e)}
|
||||
logging.info('AGVDownProduct error:%s' % e)
|
||||
return json.JSONEncoder().encode(res)
|
||||
return json.JSONEncoder().encode(res)
|
||||
|
||||
@@ -156,17 +156,26 @@ class AgvScheduling(models.Model):
|
||||
if agv_site_state == '空闲':
|
||||
# 查询终点接驳站为agv_site_id的AGV路线
|
||||
task_routes = self.env['sf.agv.task.route'].sudo().search([('end_site_id', '=', agv_site_id)])
|
||||
agv_scheduling = self.env['sf.agv.scheduling'].sudo().search(
|
||||
agv_schedulings = self.env['sf.agv.scheduling'].sudo().search(
|
||||
[('state', '=', '待下发'), ('agv_route_type', 'in', task_routes.mapped('route_type'))],
|
||||
order='id asc',
|
||||
limit=1
|
||||
)
|
||||
task_route = task_routes.filtered(
|
||||
lambda r: r.start_site_id == agv_scheduling.start_site_id and r.start_site_id == agv_scheduling.start_site_id
|
||||
)
|
||||
if task_route:
|
||||
# 下发AGV调度任务并修改接驳站状态为占用
|
||||
agv_scheduling.dispatch_scheduling(task_route)
|
||||
for agv_scheduling in agv_schedulings:
|
||||
# 找到所有起点接驳站匹配的路线
|
||||
start_matched_task_routes = task_routes.filtered(
|
||||
lambda r: r.start_site_id == agv_scheduling.start_site_id
|
||||
)
|
||||
# 如果调度任务有终点接驳站,找到终点接驳站匹配的路线
|
||||
if agv_scheduling.end_site_id:
|
||||
matched_task_routes = start_matched_task_routes.filtered(
|
||||
lambda r: r.end_site_id == agv_scheduling.end_site_id
|
||||
)
|
||||
else:
|
||||
matched_task_routes = start_matched_task_routes
|
||||
if matched_task_routes:
|
||||
# 下发AGV调度任务并修改接驳站状态为占用
|
||||
agv_scheduling.dispatch_scheduling(matched_task_routes[0])
|
||||
break;
|
||||
|
||||
def _delivery_avg(self):
|
||||
config = self.env['res.config.settings'].get_values()
|
||||
|
||||
@@ -18,7 +18,7 @@ class MrpProduction(models.Model):
|
||||
_inherit = 'mrp.production'
|
||||
_description = "制造订单"
|
||||
_order = 'create_date desc'
|
||||
|
||||
deadline_of_delivery = fields.Date('订单交期', tracking=True, compute='_compute_deadline_of_delivery')
|
||||
# tray_ids = fields.One2many('sf.tray', 'production_id', string="托盘")
|
||||
maintenance_count = fields.Integer(compute='_compute_maintenance_count', string="Number of maintenance requests")
|
||||
request_ids = fields.One2many('maintenance.request', 'production_id')
|
||||
@@ -34,6 +34,16 @@ class MrpProduction(models.Model):
|
||||
tool_state_remark = fields.Text(string='功能刀具状态备注(缺刀)', compute='_compute_tool_state_remark', store=True)
|
||||
tool_state_remark2 = fields.Text(string='功能刀具状态备注(无效刀)', readonly=True)
|
||||
|
||||
@api.depends('procurement_group_id.mrp_production_ids.move_dest_ids.group_id.sale_id')
|
||||
def _compute_deadline_of_delivery(self):
|
||||
for production in self:
|
||||
sale_order_ids = production.procurement_group_id.mrp_production_ids.move_dest_ids.group_id.sale_id.ids
|
||||
if not sale_order_ids or len(sale_order_ids) < 1:
|
||||
continue
|
||||
sale_id = self.env['sale.order'].sudo().browse(sale_order_ids[0])
|
||||
if sale_id:
|
||||
production.deadline_of_delivery = sale_id.deadline_of_delivery
|
||||
|
||||
@api.depends('workorder_ids.tool_state_remark')
|
||||
def _compute_tool_state_remark(self):
|
||||
for item in self:
|
||||
@@ -118,10 +128,12 @@ class MrpProduction(models.Model):
|
||||
], string='工序状态', default='待装夹')
|
||||
|
||||
# 零件图号
|
||||
part_number = fields.Char('零件图号')
|
||||
part_number = fields.Char('零件图号', readonly=True)
|
||||
|
||||
# 上传零件图纸
|
||||
part_drawing = fields.Binary('零件图纸')
|
||||
part_drawing = fields.Binary('零件图纸', readonly=True)
|
||||
|
||||
quality_standard = fields.Binary('质检标准', readonly=True)
|
||||
|
||||
@api.depends('product_id.manual_quotation')
|
||||
def _compute_manual_quotation(self):
|
||||
@@ -296,8 +308,13 @@ class MrpProduction(models.Model):
|
||||
# 编程单更新
|
||||
def update_programming_state(self):
|
||||
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,
|
||||
'manufacturing_type': 'rework' if self.is_scrap is False else 'scrap'}
|
||||
'manufacturing_type': manufacturing_type}
|
||||
logging.info('res=%s:' % res)
|
||||
configsettings = self.env['res.config.settings'].get_values()
|
||||
config_header = Common.get_headers(self, configsettings['token'], configsettings['sf_secret_key'])
|
||||
@@ -943,6 +960,8 @@ class MrpProduction(models.Model):
|
||||
if production.programming_no in program_to_production_names:
|
||||
productions_not_delivered = self.env['mrp.production'].search(
|
||||
[('programming_no', '=', production.programming_no), ('programming_state', '=', '已编程未下发')])
|
||||
productions = self.env['mrp.production'].search(
|
||||
[('programming_no', '=', production.programming_no), ('state', 'not in', ('cancel', 'done'))])
|
||||
rework_workorder = production.workorder_ids.filtered(lambda m: m.state == 'rework')
|
||||
if rework_workorder:
|
||||
for rework_item in rework_workorder:
|
||||
@@ -955,6 +974,13 @@ class MrpProduction(models.Model):
|
||||
productions_not_delivered.write(
|
||||
{'state': 'progress', 'programming_state': '已编程', 'is_rework': False})
|
||||
|
||||
# 对制造订单所以面的cnc工单的程序用刀进行校验
|
||||
try:
|
||||
logging.info(f'已更新制造订单:{productions_not_delivered}')
|
||||
productions.production_cnc_tool_checkout()
|
||||
except Exception as e:
|
||||
logging.info(f'对cnc工单的程序用刀进行校验报错:{e}')
|
||||
|
||||
# 从cloud获取重新编程过的最新程序
|
||||
def get_new_program(self, processing_panel):
|
||||
try:
|
||||
@@ -990,8 +1016,8 @@ class MrpProduction(models.Model):
|
||||
panel_workorder.cmm_ids.sudo().unlink()
|
||||
if panel_workorder.cnc_ids:
|
||||
panel_workorder.cnc_ids.sudo().unlink()
|
||||
self.env['sf.cam.work.order.program.knife.plan'].sudo().unlink_cam_plan(
|
||||
production)
|
||||
# self.env['sf.cam.work.order.program.knife.plan'].sudo().unlink_cam_plan(
|
||||
# production)
|
||||
# program_path_tmp_panel = os.path.join('C://Users//43484//Desktop//fsdownload//test',
|
||||
# processing_panel)
|
||||
logging.info('program_path_tmp_panel:%s' % program_path_tmp_panel)
|
||||
|
||||
@@ -40,7 +40,7 @@ class ResMrpRoutingWorkcenter(models.Model):
|
||||
def get_company_id(self):
|
||||
self.company_id = self.env.user.company_id.id
|
||||
|
||||
company_id = fields.Many2one('res.company', compute="get_company_id", related=False)
|
||||
company_id = fields.Many2one('res.company', compute="get_company_id", related=False, store=True)
|
||||
|
||||
# 排产的时候, 根据坯料的长宽高比对一下机床的最大加工尺寸.不符合就不要分配给这个加工中心(机床).
|
||||
# 工单对应的工作中心,根据工序中的工作中心去匹配,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import datetime
|
||||
import logging
|
||||
from datetime import timedelta, time
|
||||
from collections import defaultdict
|
||||
from odoo import fields, models, api
|
||||
@@ -6,6 +7,8 @@ from odoo.addons.resource.models.resource import Intervals
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
import math
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ResWorkcenter(models.Model):
|
||||
_name = "mrp.workcenter"
|
||||
@@ -163,6 +166,19 @@ class ResWorkcenter(models.Model):
|
||||
else:
|
||||
record.effective_working_hours_day = 0
|
||||
|
||||
# 计算传入时间日有效工作时长
|
||||
def _compute_effective_working_hours_day1(self, date):
|
||||
effective_working_hours_day = 0
|
||||
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(date)]
|
||||
if attendance_ids:
|
||||
for attendance_id in attendance_ids:
|
||||
if attendance_id.hour_from and attendance_id.hour_to:
|
||||
effective_working_hours_day += attendance_id.hour_to - attendance_id.hour_from
|
||||
|
||||
return effective_working_hours_day
|
||||
|
||||
# 获取传入时间是星期几
|
||||
def get_current_day_of_week(self, datetime):
|
||||
day_num = datetime.weekday()
|
||||
@@ -211,12 +227,17 @@ class ResWorkcenter(models.Model):
|
||||
('state', 'not in', ['draft', 'cancel'])])
|
||||
if plan_ids:
|
||||
sum_qty = sum([p.product_qty for p in plan_ids])
|
||||
if sum_qty >= self.default_capacity:
|
||||
date_planned_working_hours = self._compute_effective_working_hours_day1(date_planned)
|
||||
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 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_end = date_planned + timedelta(hours=1)
|
||||
@@ -228,7 +249,11 @@ class ResWorkcenter(models.Model):
|
||||
|
||||
if 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 True
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ class ResMrpWorkOrder(models.Model):
|
||||
compute='_compute_state', store=True,
|
||||
default='pending', copy=False, readonly=True, recursive=True, index=True, tracking=True)
|
||||
|
||||
# state = fields.Selection(selection_add=[('to be detected', "待检测"), ('rework', '返工')], tracking=True)
|
||||
delivery_warning = fields.Selection([('normal', '正常'), ('warning', '告警'), ('overdue', '逾期')], string='时效')
|
||||
|
||||
@api.depends('production_id.manual_quotation')
|
||||
def _compute_manual_quotation(self):
|
||||
@@ -225,6 +225,9 @@ class ResMrpWorkOrder(models.Model):
|
||||
material_height = fields.Float(string='高')
|
||||
# 零件图号
|
||||
part_number = fields.Char(related='production_id.part_number', string='零件图号')
|
||||
machining_drawings = fields.Binary('2D加工图纸', related='production_id.part_drawing', readonly=True)
|
||||
quality_standard = fields.Binary('质检标准', related='production_id.quality_standard', readonly=True)
|
||||
|
||||
# 工序状态
|
||||
process_state = fields.Selection([
|
||||
('待装夹', '待装夹'),
|
||||
@@ -1082,6 +1085,12 @@ class ResMrpWorkOrder(models.Model):
|
||||
|
||||
# 重写工单开始按钮方法
|
||||
def button_start(self):
|
||||
if self.routing_type == 'CNC加工':
|
||||
self.env['sf.production.plan'].sudo().search([('name', '=', self.production_id.name)]).write({
|
||||
'state': 'processing',
|
||||
'actual_start_time': datetime.now()
|
||||
})
|
||||
|
||||
if self.routing_type == '装夹预调':
|
||||
# 判断是否有坯料的序列号信息
|
||||
boolean = False
|
||||
@@ -1191,8 +1200,8 @@ class ResMrpWorkOrder(models.Model):
|
||||
if record.is_rework is False:
|
||||
if not record.material_center_point:
|
||||
raise UserError("坯料中心点为空,请检查")
|
||||
if record.X_deviation_angle <= 0:
|
||||
raise UserError("X偏差角度小于等于0,请检查!本次计算的X偏差角度为:%s" % record.X_deviation_angle)
|
||||
# if record.X_deviation_angle <= 0:
|
||||
# raise UserError("X偏差角度小于等于0,请检查!本次计算的X偏差角度为:%s" % record.X_deviation_angle)
|
||||
record.process_state = '待加工'
|
||||
# record.write({'process_state': '待加工'})
|
||||
record.production_id.process_state = '待加工'
|
||||
@@ -1582,8 +1591,11 @@ class SfWorkOrderBarcodes(models.Model):
|
||||
[('routing_type', '=', '装夹预调'), ('rfid_code', '=', barcode)])
|
||||
if workorder_olds:
|
||||
name = ''
|
||||
tem_list = []
|
||||
for workorder in workorder_olds:
|
||||
name = '%s %s' % (name, workorder.production_id.name)
|
||||
tem_list.append(workorder.production_id.name)
|
||||
for i in list(set(tem_list)):
|
||||
name = '%s %s' % (name, i)
|
||||
raise UserError('该托盘已绑定【%s】制造订单,请先解除绑定!!!' % name)
|
||||
if workorder:
|
||||
if workorder.routing_type == '装夹预调':
|
||||
@@ -1817,6 +1829,11 @@ class WorkPieceDelivery(models.Model):
|
||||
return is_free
|
||||
else:
|
||||
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小车
|
||||
def _delivery_avg(self):
|
||||
@@ -1877,7 +1894,7 @@ class WorkPieceDelivery(models.Model):
|
||||
logging.info('delivery_item-name:%s' % delivery_item.name)
|
||||
delivery_item.write({
|
||||
'task_delivery_time': fields.Datetime.now(),
|
||||
'status': '待配送'
|
||||
'status': '已下发'
|
||||
})
|
||||
if delivery_item.type == "上产线":
|
||||
delivery_item.workorder_id.write({'is_delivery': True})
|
||||
|
||||
@@ -44,6 +44,9 @@ class ResProductMo(models.Model):
|
||||
materials_id = fields.Many2one('sf.production.materials', string='材料')
|
||||
materials_type_id = fields.Many2one('sf.materials.model', string='材料型号',
|
||||
domain="[('materials_id', '=', materials_id)]")
|
||||
# materials_type_id = fields.Many2one(related='cutting_tool_model_id.material_model_id', string='材料型号',
|
||||
# domain="[('materials_id', '=', materials_id)]")
|
||||
# cutting_tool_model_id.material_model_id
|
||||
server_product_process_parameters_id = fields.Many2one('sf.production.process.parameter',
|
||||
string='表面工艺参数(服务产品)')
|
||||
model_process_parameters_ids = fields.Many2many('sf.production.process.parameter', 'process_parameter_rel',
|
||||
@@ -57,40 +60,100 @@ class ResProductMo(models.Model):
|
||||
|
||||
cutting_tool_type_id = fields.Many2one('sf.cutting.tool.type', string='类型',
|
||||
domain="[('cutting_tool_material_id.name', '=', cutting_tool_type)]")
|
||||
|
||||
brand_id = fields.Many2one('sf.machine.brand', '品牌')
|
||||
|
||||
tool_length = fields.Float('长度(mm)')
|
||||
tool_width = fields.Float('宽度(mm)')
|
||||
# cutting_tool_type_id = fields.Many2one(related='cutting_tool_model_id.cutting_tool_type_id', string='类型',
|
||||
# domain="[('cutting_tool_material_id.name', '=', cutting_tool_type)]")
|
||||
# brand_id = fields.Many2one('sf.machine.brand', '品牌')
|
||||
brand_id = fields.Many2one(related='cutting_tool_model_id.brand_id', string='品牌')
|
||||
# cutting_tool_model_id.brand_id
|
||||
# tool_length = fields.Float('长度(mm)')
|
||||
tool_length = fields.Float(related='specification_id.length', string='长度(mm)')
|
||||
# specification_id.length
|
||||
# tool_width = fields.Float('宽度(mm)')
|
||||
tool_width = fields.Float(related='specification_id.width', string='宽度(mm)')
|
||||
# specification_id.width
|
||||
tool_height = fields.Float('高度(mm)')
|
||||
tool_thickness = fields.Float('厚度(mm)')
|
||||
tool_weight = fields.Float('重量(kg)')
|
||||
tool_hardness = fields.Integer('硬度(hrc)')
|
||||
coating_material = fields.Char('涂层材质')
|
||||
# tool_thickness = fields.Float('厚度(mm)')
|
||||
tool_thickness = fields.Float(related='specification_id.thickness', string='厚度(mm)')
|
||||
# specification_id.thickness
|
||||
# tool_weight = fields.Float('重量(kg)')
|
||||
tool_weight = fields.Float(related='specification_id.weight', string='重量(kg)')
|
||||
# specification_id.weight
|
||||
# tool_hardness = fields.Integer('硬度(hrc)')
|
||||
tool_hardness = fields.Integer(related='cutting_tool_model_id.tool_hardness', string='硬度(hrc)')
|
||||
# cutting_tool_model_id.tool_hardness
|
||||
# coating_material = fields.Char('涂层材质')
|
||||
coating_material = fields.Char(related='cutting_tool_model_id.coating_material', string='涂层材质')
|
||||
# cutting_tool_model_id.coating_material
|
||||
# 整体式刀具特有字段
|
||||
cutting_tool_total_length = fields.Float('总长度(mm)', digits=(6, 1))
|
||||
cutting_tool_shank_length = fields.Float('柄部长度(mm)', digits=(6, 1))
|
||||
cutting_tool_blade_length = fields.Float('刃部长度(mm)')
|
||||
cutting_tool_blade_number = fields.Selection(
|
||||
[('0', '0'), ('2', '2'), ('3', '3'), ('4', '4'), ('5', '5'), ('6', '6'), ('7', '7'), ('8', '8')],
|
||||
string='刃数(个)', default='0')
|
||||
# cutting_tool_total_length = fields.Float('总长度(mm)', digits=(6, 1))
|
||||
cutting_tool_total_length = fields.Float(related='specification_id.total_length', string='总长度(mm)', digits=(6, 1))
|
||||
# specification_id.total_length
|
||||
# cutting_tool_shank_length = fields.Float('柄部长度(mm)', digits=(6, 1))
|
||||
cutting_tool_shank_length = fields.Float(related='specification_id.handle_length', string='柄部长度(mm)',
|
||||
digits=(6, 1))
|
||||
# specification_id.handle_length
|
||||
# cutting_tool_blade_length = fields.Float('刃部长度(mm)')
|
||||
cutting_tool_blade_length = fields.Float(related='specification_id.blade_length', string='刃部长度(mm)')
|
||||
# specification_id.blade_length
|
||||
# cutting_tool_blade_number = fields.Selection(
|
||||
# [('0', '0'), ('2', '2'), ('3', '3'), ('4', '4'), ('5', '5'), ('6', '6'), ('7', '7'), ('8', '8')],
|
||||
# string='刃数(个)', default='0')
|
||||
cutting_tool_blade_number = fields.Selection(related='specification_id.blade_number', string='刃数(个)')
|
||||
# specification_id.blade_number
|
||||
# 整体式刀具新增字段
|
||||
cutting_tool_neck_length = fields.Float('颈部长度(mm)', digits=(6, 1))
|
||||
cutting_tool_neck_diameter = fields.Float('颈部直径(mm)', digits=(6, 1))
|
||||
cutting_tool_shank_diameter = fields.Float('柄部直径(mm)', digits=(6, 1))
|
||||
cutting_tool_blade_tip_diameter = fields.Float('刀尖直径(mm)', digits=(6, 1))
|
||||
cutting_tool_blade_tip_taper = fields.Integer('刀尖锥度(°)')
|
||||
cutting_tool_blade_helix_angle = fields.Integer('刃部螺旋角(°)')
|
||||
cutting_tool_blade_type = fields.Char('刃部类型')
|
||||
cutting_tool_pitch = fields.Float('牙距(mm)')
|
||||
cutting_tool_blade_width = fields.Float('刃部宽度(mm)')
|
||||
cutting_tool_blade_depth = fields.Float('刃部深度(mm)')
|
||||
# cutting_tool_neck_length = fields.Float('颈部长度(mm)', digits=(6, 1))
|
||||
cutting_tool_neck_length = fields.Float(related='specification_id.neck_length', string='颈部长度(mm)', digits=(6, 1))
|
||||
# specification_id.neck_length
|
||||
# cutting_tool_neck_diameter = fields.Float('颈部直径(mm)', digits=(6, 1))
|
||||
cutting_tool_neck_diameter = fields.Float(related='specification_id.neck_diameter', string='颈部直径(mm)',
|
||||
digits=(6, 1))
|
||||
# specification_id.neck_diameter
|
||||
# cutting_tool_shank_diameter = fields.Float('柄部直径(mm)', digits=(6, 1))
|
||||
cutting_tool_shank_diameter = fields.Float(related='specification_id.handle_diameter', string='柄部直径(mm)', digits=(6, 1))
|
||||
# specification_id.handle_diameter
|
||||
# cutting_tool_blade_tip_diameter = fields.Float('刀尖直径(mm)', digits=(6, 1))
|
||||
cutting_tool_blade_tip_diameter = fields.Float(related='specification_id.blade_tip_diameter', string='刀尖直径(mm)',
|
||||
digits=(6, 1))
|
||||
# specification_id.blade_tip_diameter
|
||||
# cutting_tool_blade_tip_taper = fields.Integer('刀尖锥度(°)')
|
||||
cutting_tool_blade_tip_taper = fields.Integer(related='specification_id.blade_tip_taper', string='刀尖锥度(°)')
|
||||
# specification_id.blade_tip_taper
|
||||
# cutting_tool_blade_helix_angle = fields.Integer('刃部螺旋角(°)')
|
||||
cutting_tool_blade_helix_angle = fields.Integer(related='specification_id.blade_helix_angle', string='刃部螺旋角(°)')
|
||||
# specification_id.blade_helix_angle
|
||||
# cutting_tool_blade_type = fields.Char('刃部类型')
|
||||
cutting_tool_blade_type = fields.Char(related='cutting_tool_model_id.blade_type', string='刃部类型')
|
||||
# cutting_tool_pitch = fields.Float('牙距(mm)')
|
||||
cutting_tool_pitch = fields.Float(related='specification_id.pitch', string='牙距(mm)')
|
||||
# specification_id.pitch
|
||||
# cutting_tool_blade_width = fields.Float('刃部宽度(mm)')
|
||||
cutting_tool_blade_width = fields.Float(related='specification_id.blade_width', string='刃部宽度(mm)')
|
||||
# specification_id.blade_width
|
||||
# cutting_tool_blade_depth = fields.Float('刃部深度(mm)')
|
||||
cutting_tool_blade_depth = fields.Float(related='specification_id.blade_depth', string='刃部深度(mm)')
|
||||
# specification_id.blade_depth
|
||||
cutting_tool_cut_depth = fields.Float('切削深度(mm)')
|
||||
cutting_tool_cut_depth_max = fields.Float('最大切削深度(mm)')
|
||||
cutting_tool_coarse_medium_fine = fields.Selection([('粗', '粗'), ('中', '中'), ('精', '精')], '粗/中/精')
|
||||
cutting_tool_run_out_accuracy_max = fields.Float('端跳精度max', digits=(6, 1))
|
||||
cutting_tool_run_out_accuracy_min = fields.Float('端跳精度min', digits=(6, 1))
|
||||
cutting_tool_blade_tip_working_size = fields.Char('刀尖处理尺寸(R半径mm/倒角)', size=20)
|
||||
# cutting_tool_cut_depth_max = fields.Float('最大切削深度(mm)')
|
||||
cutting_tool_cut_depth_max = fields.Float(related='specification_id.cut_depth_max', string='最大切削深度(mm)')
|
||||
# specification_id.cut_depth_max
|
||||
# cutting_tool_coarse_medium_fine = fields.Selection([('粗', '粗'), ('中', '中'), ('精', '精')], '粗/中/精')
|
||||
cutting_tool_coarse_medium_fine = fields.Selection(related='cutting_tool_model_id.integral_coarse_medium_fine', string='粗/中/精')
|
||||
# cutting_tool_model_id.integral_coarse_medium_fine
|
||||
# cutting_tool_run_out_accuracy_max = fields.Float('端跳精度max', digits=(6, 1))
|
||||
cutting_tool_run_out_accuracy_max = fields.Char(related='cutting_tool_model_id.integral_run_out_accuracy_max', string='端跳精度max', digits=(6, 1))
|
||||
# cutting_tool_model_id.integral_run_out_accuracy_max
|
||||
# cutting_tool_run_out_accuracy_min = fields.Float('端跳精度min', digits=(6, 1))
|
||||
cutting_tool_run_out_accuracy_min = fields.Char(related='cutting_tool_model_id.integral_run_out_accuracy_min',
|
||||
string='端跳精度min', digits=(6, 1))
|
||||
# cutting_tool_model_id.integral_run_out_accuracy_min
|
||||
# cutting_tool_blade_tip_working_size = fields.Char('刀尖倒角度(°)', size=20)
|
||||
cutting_tool_blade_tip_working_size = fields.Char(related='specification_id.blade_tip_working_size',
|
||||
string='刀尖倒角度(°)', size=20)
|
||||
# specification_id.blade_tip_working_size
|
||||
# cutting_tool_blade_tip_r_size = fields.Float('刀尖R角(mm)')
|
||||
cutting_tool_blade_tip_r_size = fields.Float(related='specification_id.tip_r_size',
|
||||
string='刀尖R角(mm)')
|
||||
# specification_id.tip_r_size
|
||||
fit_blade_shape_id = fields.Many2one('maintenance.equipment.image',
|
||||
'适配刀片形状', domain=[('type', '=', '刀片形状')])
|
||||
suitable_machining_method_ids = fields.Many2many('maintenance.equipment.image',
|
||||
@@ -237,6 +300,7 @@ class ResProductMo(models.Model):
|
||||
self.cutting_tool_blade_tip_taper = self.specification_id.blade_tip_taper
|
||||
self.cutting_tool_blade_helix_angle = self.specification_id.blade_helix_angle
|
||||
self.cutting_tool_blade_tip_working_size = self.specification_id.blade_tip_working_size
|
||||
self.cutting_tool_blade_tip_r_size = self.specification_id.tip_r_size
|
||||
self.cutting_tool_pitch = self.specification_id.pitch
|
||||
self.cutting_tool_blade_width = self.specification_id.blade_width
|
||||
self.cutting_tool_blade_depth = self.specification_id.blade_depth
|
||||
@@ -427,44 +491,97 @@ class ResProductMo(models.Model):
|
||||
# if not self.cutting_direction_ids:
|
||||
# raise ValidationError("请选择走刀方向")
|
||||
|
||||
cutting_speed_ids = fields.One2many('sf.cutting.speed', 'product_template_id', string='切削速度Vc')
|
||||
# cutting_speed_ids = fields.One2many('sf.cutting.speed', 'product_template_id', string='切削速度Vc')
|
||||
cutting_speed_ids = fields.One2many(related='cutting_tool_model_id.cutting_speed_ids',
|
||||
string='切削速度Vc')
|
||||
# cutting_tool_model_id.cutting_speed_ids
|
||||
feed_per_tooth_ids = fields.One2many('sf.feed.per.tooth', 'product_template_id', string='每齿走刀量fz')
|
||||
cutting_tool_diameter = fields.Float('刀具直径(mm)')
|
||||
cutting_tool_rear_angle = fields.Integer('后角(°)')
|
||||
cutting_tool_main_included_angle = fields.Integer('主偏角(°)')
|
||||
# cutting_tool_rear_angle = fields.Integer('后角(°)')
|
||||
cutting_tool_rear_angle = fields.Integer(related='specification_id.relief_angle',
|
||||
string='后角(°)')
|
||||
# specification_id.relief_angle
|
||||
# cutting_tool_main_included_angle = fields.Integer('主偏角(°)')
|
||||
cutting_tool_main_included_angle = fields.Integer(related='specification_id.main_included_angle',
|
||||
string='主偏角(°)')
|
||||
# specification_id.main_included_angle
|
||||
# 适用夹头型号可以多选
|
||||
cutting_tool_chuck_id = fields.Many2one(
|
||||
'sf.cutting_tool.standard.library',
|
||||
domain="[('cutting_tool_type', '=', '夹头')]",
|
||||
string='适用夹头型号')
|
||||
# 刀片参数
|
||||
cutting_tool_cut_blade_length = fields.Float('切削刃长(mm)')
|
||||
cutting_tool_blade_tip_circular_arc_radius = fields.Char('刀尖圆弧半径(mm)', size=20)
|
||||
cutting_tool_top_angle = fields.Integer('顶角(°)')
|
||||
cutting_tool_inscribed_circle_diameter = fields.Float('内接圆直径(mm)')
|
||||
cutting_tool_install_aperture_diameter = fields.Float('安装孔直径(mm)')
|
||||
cutting_tool_chip_breaker_groove = fields.Selection([('无', '无'), ('单面', '单面'), ('双面', '双面')],
|
||||
string='有无断屑槽')
|
||||
cutting_tool_chip_breaker_type_code = fields.Char('断屑槽型代号')
|
||||
cutting_tool_bladed_teeth_model = fields.Selection(
|
||||
[('无', '无'), ('V牙型', 'V牙型'), ('米制全牙型', '米制全牙型'), ('美制全牙型', '美制全牙型'),
|
||||
('惠氏全牙型', '惠氏全牙型'), ('BSPT全牙型', 'BSPT全牙型'), ('NPT全牙型', 'NPT全牙型'),
|
||||
('UNJ全牙型', 'UNJ全牙型'), ('DIN405圆牙型', 'DIN405圆牙型'), ('ACME梯形', 'ACME梯形'),
|
||||
('石油管螺纹刀片', '石油管螺纹刀片'), ('矮牙ACME梯形', '矮牙ACME梯形'),
|
||||
('Trapeze30° 103', 'Trapeze30° 103')], string='刀片牙型', default='无')
|
||||
cutting_tool_blade_blade_number = fields.Selection(
|
||||
[('0', '0'), ('1', '1'), ('2', '2'), ('3', '3'), ('4', '4'), ('5', '5'), ('6', '6'),
|
||||
('7', '7'), ('8', '8'), ('9', '9'), ('10', '10')],
|
||||
string='刀片的刃数(个)')
|
||||
# cutting_tool_cut_blade_length = fields.Float('切削刃长(mm)')
|
||||
cutting_tool_cut_blade_length = fields.Float(related='specification_id.cutting_blade_length',
|
||||
string='切削刃长(mm)')
|
||||
# specification_id.cutting_blade_length
|
||||
# cutting_tool_blade_tip_circular_arc_radius = fields.Char('刀尖圆弧半径(mm)', size=20)
|
||||
cutting_tool_blade_tip_circular_arc_radius = fields.Char(related='specification_id.blade_tip_circular_arc_radius',
|
||||
string='刀尖圆弧半径(mm)')
|
||||
# specification_id.blade_tip_circular_arc_radius
|
||||
# cutting_tool_top_angle = fields.Integer('顶角(°)')
|
||||
cutting_tool_top_angle = fields.Integer(related='specification_id.top_angle',
|
||||
string='顶角(°)')
|
||||
# specification_id.top_angle
|
||||
# cutting_tool_inscribed_circle_diameter = fields.Float('内接圆直径(mm)')
|
||||
cutting_tool_inscribed_circle_diameter = fields.Float(related='specification_id.inscribed_circle_diameter',
|
||||
string='内接圆直径(mm)')
|
||||
# specification_id.inscribed_circle_diameter
|
||||
# cutting_tool_install_aperture_diameter = fields.Float('安装孔直径(mm)')
|
||||
cutting_tool_install_aperture_diameter = fields.Float(related='specification_id.install_aperture_diameter',
|
||||
string='安装孔直径(mm)')
|
||||
# specification_id.install_aperture_diameter
|
||||
# cutting_tool_chip_breaker_groove = fields.Selection([('无', '无'), ('单面', '单面'), ('双面', '双面')],
|
||||
# string='有无断屑槽')
|
||||
cutting_tool_chip_breaker_groove = fields.Selection(related='specification_id.chip_breaker_groove',
|
||||
string='有无断屑槽')
|
||||
# specification_id.chip_breaker_groove
|
||||
# cutting_tool_chip_breaker_type_code = fields.Char('断屑槽型代号')
|
||||
cutting_tool_chip_breaker_type_code = fields.Char(related='specification_id.chip_breaker_type_code',
|
||||
string='断屑槽型代号')
|
||||
# specification_id.chip_breaker_type_code
|
||||
# cutting_tool_bladed_teeth_model = fields.Selection(
|
||||
# [('无', '无'), ('V牙型', 'V牙型'), ('米制全牙型', '米制全牙型'), ('美制全牙型', '美制全牙型'),
|
||||
# ('惠氏全牙型', '惠氏全牙型'), ('BSPT全牙型', 'BSPT全牙型'), ('NPT全牙型', 'NPT全牙型'),
|
||||
# ('UNJ全牙型', 'UNJ全牙型'), ('DIN405圆牙型', 'DIN405圆牙型'), ('ACME梯形', 'ACME梯形'),
|
||||
# ('石油管螺纹刀片', '石油管螺纹刀片'), ('矮牙ACME梯形', '矮牙ACME梯形'),
|
||||
# ('Trapeze30° 103', 'Trapeze30° 103')], string='刀片牙型', default='无')
|
||||
cutting_tool_bladed_teeth_model = fields.Selection(related='specification_id.blade_teeth_model',
|
||||
string='断屑槽型代号')
|
||||
# specification_id.blade_teeth_model
|
||||
# cutting_tool_blade_blade_number = fields.Selection(
|
||||
# [('0', '0'), ('1', '1'), ('2', '2'), ('3', '3'), ('4', '4'), ('5', '5'), ('6', '6'),
|
||||
# ('7', '7'), ('8', '8'), ('9', '9'), ('10', '10')],
|
||||
# string='刀片的刃数(个)')
|
||||
cutting_tool_blade_blade_number = fields.Selection(related='specification_id.blade_blade_number',
|
||||
string='刀片的刃数(个)')
|
||||
# specification_id.blade_blade_number
|
||||
|
||||
cutting_tool_thread_model = fields.Selection([('无', '无'), ('外螺纹', '外螺纹'), ('内螺纹', '内螺纹')],
|
||||
string='螺纹类型')
|
||||
cutting_tool_thread_num = fields.Float('每英寸螺纹数(tpi)')
|
||||
cutting_tool_blade_tip_height_tolerance = fields.Char('刀尖高度公差(mm)', size=20)
|
||||
cutting_tool_inscribed_circle_tolerance = fields.Char('内接圆公差(mm)', size=20)
|
||||
cutting_tool_thickness_tolerance = fields.Char('厚度公差(mm)', size=20)
|
||||
|
||||
cutting_tool_jump_accuracy = fields.Char('径跳精度(mm)')
|
||||
# cutting_tool_thread_model = fields.Selection([('无', '无'), ('外螺纹', '外螺纹'), ('内螺纹', '内螺纹')],
|
||||
# string='螺纹类型')
|
||||
cutting_tool_thread_model = fields.Selection(related='specification_id.thread_model',
|
||||
string='螺纹类型')
|
||||
# specification_id.thread_model
|
||||
# cutting_tool_thread_num = fields.Float('每英寸螺纹数(tpi)')
|
||||
cutting_tool_thread_num = fields.Float(related='specification_id.thread_num',
|
||||
string='每英寸螺纹数(tpi)')
|
||||
# specification_id.thread_num
|
||||
# cutting_tool_blade_tip_height_tolerance = fields.Char('刀尖高度公差(mm)', size=20)
|
||||
cutting_tool_blade_tip_height_tolerance = fields.Char(related='specification_id.blade_tip_height_tolerance',
|
||||
string='刀尖高度公差(mm)')
|
||||
# specification_id.blade_tip_height_tolerance
|
||||
# cutting_tool_inscribed_circle_tolerance = fields.Char('内接圆公差(mm)', size=20)
|
||||
cutting_tool_inscribed_circle_tolerance = fields.Char(related='specification_id.inscribed_circle_tolerance',
|
||||
string='内接圆公差(mm)')
|
||||
# specification_id.inscribed_circle_tolerance
|
||||
# cutting_tool_thickness_tolerance = fields.Char('厚度公差(mm)', size=20)
|
||||
cutting_tool_thickness_tolerance = fields.Char(related='specification_id.thickness_tolerance',
|
||||
string='厚度公差(mm)')
|
||||
# specification_id.thickness_tolerance
|
||||
# cutting_tool_jump_accuracy = fields.Char('径跳精度(mm)')
|
||||
cutting_tool_jump_accuracy = fields.Char(related='specification_id.run_out_accuracy',
|
||||
string='径跳精度(mm)')
|
||||
# specification_id.run_out_accuracy
|
||||
cutting_tool_working_hardness = fields.Integer('加工硬度(hrc)')
|
||||
cutting_tool_cutter_bar_ids = fields.Many2many(
|
||||
'sf.cutting_tool.standard.library',
|
||||
@@ -483,55 +600,153 @@ class ResProductMo(models.Model):
|
||||
string='适用刀盘型号' # 使用空列表作为默认值
|
||||
)
|
||||
# 刀杆/参数
|
||||
cutting_tool_knife_head_height = fields.Float('刀头高度(mm)')
|
||||
cutting_tool_knife_head_width = fields.Float('刀头宽度(mm)')
|
||||
cutting_tool_knife_head_length = fields.Float('刀头长度(mm)')
|
||||
cutting_tool_blade_diameter = fields.Float('刃径/刃部直径(mm)')
|
||||
cutting_tool_cutter_arbor_diameter = fields.Float('刀杆直径(mm)')
|
||||
cutting_tool_min_machining_aperture = fields.Integer('最小加工孔径(mm)')
|
||||
cutting_tool_install_blade_tip_num = fields.Integer('可装刀片数/齿数(个)')
|
||||
cutting_tool_installing_structure = fields.Char('安装结构', size=20)
|
||||
# cutting_tool_knife_head_height = fields.Float('刀头高度(mm)')
|
||||
cutting_tool_knife_head_height = fields.Float(related='specification_id.knife_head_height',
|
||||
string='刀头高度(mm)')
|
||||
# specification_id.knife_head_height
|
||||
# cutting_tool_knife_head_width = fields.Float('刀头宽度(mm)')
|
||||
cutting_tool_knife_head_width = fields.Float(related='specification_id.knife_head_width',
|
||||
string='刀头宽度(mm)')
|
||||
# cutting_tool_knife_head_length = fields.Float('刀头长度(mm)')
|
||||
cutting_tool_knife_head_length = fields.Float(related='specification_id.knife_head_length',
|
||||
string='刀头长度(mm)')
|
||||
# cutting_tool_blade_diameter = fields.Float('刃径/刃部直径(mm)')
|
||||
cutting_tool_blade_diameter = fields.Float(related='specification_id.blade_diameter',
|
||||
string='刃径/刃部直径(mm)')
|
||||
# specification_id.blade_diameter
|
||||
# cutting_tool_cutter_arbor_diameter = fields.Float('刀杆直径(mm)')
|
||||
cutting_tool_cutter_arbor_diameter = fields.Float(related='specification_id.cutter_arbor_diameter',
|
||||
string='刀杆直径(mm)')
|
||||
# specification_id.cutter_arbor_diameter
|
||||
# cutting_tool_min_machining_aperture = fields.Integer('最小加工孔径(mm)')
|
||||
cutting_tool_min_machining_aperture = fields.Integer(related='specification_id.min_machining_aperture',
|
||||
string='最小加工孔径(mm)')
|
||||
# specification_id.min_machining_aperture
|
||||
# cutting_tool_install_blade_tip_num = fields.Integer('可装刀片数/齿数(个)')
|
||||
cutting_tool_install_blade_tip_num = fields.Integer(related='specification_id.install_blade_tip_num',
|
||||
string='可装刀片数/齿数(个)')
|
||||
# specification_id.install_blade_tip_num
|
||||
# cutting_tool_installing_structure = fields.Char('安装结构', size=20)
|
||||
cutting_tool_installing_structure = fields.Char(related='specification_id.installing_structure',
|
||||
string='安装结构')
|
||||
# specification_id.installing_structure
|
||||
cutting_tool_blade_id = fields.Many2one(
|
||||
'sf.cutting_tool.standard.library',
|
||||
domain="[('cutting_tool_type', '=', '刀片')]",
|
||||
string='适用刀片型号' # 使用空列表作为默认值
|
||||
)
|
||||
cutting_tool_tool_shim = fields.Char('适配刀垫型号', size=50)
|
||||
cutting_tool_cotter_pin = fields.Char('适配销钉型号', size=50)
|
||||
cutting_tool_pressing_plate = fields.Char('适配压板型号', size=50)
|
||||
cutting_tool_screw = fields.Char('适配螺钉型号', size=50)
|
||||
cutting_tool_wrench = fields.Char('适配扳手型号')
|
||||
cutting_tool_is_cooling_hole = fields.Boolean('有无冷却孔', default=False)
|
||||
cutting_tool_locating_slot_code = fields.Char('定位槽代号', size=20)
|
||||
# cutting_tool_blade_id = fields.Many2one(related='specification_id.blade_id',
|
||||
# domain="[('cutting_tool_type', '=', '刀片')]",
|
||||
# string='适用刀片型号')
|
||||
# specification_id.blade_id
|
||||
# cutting_tool_tool_shim = fields.Char('适配刀垫型号', size=50)
|
||||
cutting_tool_tool_shim = fields.Char(related='specification_id.tool_shim',
|
||||
string='适配刀垫型号')
|
||||
# specification_id.tool_shim
|
||||
# cutting_tool_cotter_pin = fields.Char('适配销钉型号', size=50)
|
||||
cutting_tool_cotter_pin = fields.Char(related='specification_id.cotter_pin',
|
||||
string='适配销钉型号')
|
||||
# cutting_tool_pressing_plate = fields.Char('适配压板型号', size=50)
|
||||
cutting_tool_pressing_plate = fields.Char(related='specification_id.pressing_plate',
|
||||
string='适配压板型号')
|
||||
# cutting_tool_screw = fields.Char('适配螺钉型号', size=50)
|
||||
cutting_tool_screw = fields.Char(related='specification_id.screw',
|
||||
string='适配螺钉型号')
|
||||
# specification_id.screw
|
||||
# cutting_tool_wrench = fields.Char('适配扳手型号')
|
||||
cutting_tool_wrench = fields.Char(related='specification_id.spanner',
|
||||
string='适配扳手型号')
|
||||
# specification_id.spanner
|
||||
# cutting_tool_is_cooling_hole = fields.Boolean('有无冷却孔', default=False)
|
||||
cutting_tool_is_cooling_hole = fields.Boolean(related='specification_id.is_cooling_hole',
|
||||
string='有无冷却孔')
|
||||
# specification_id.is_cooling_hole
|
||||
# cutting_tool_locating_slot_code = fields.Char('定位槽代号', size=20)
|
||||
cutting_tool_locating_slot_code = fields.Char(related='specification_id.locating_slot_code',
|
||||
string='定位槽代号')
|
||||
# specification_id.locating_slot_code
|
||||
# 刀盘参数
|
||||
cutting_tool_cutter_head_diameter = fields.Float('刀盘直径(mm)')
|
||||
cutting_tool_interface_diameter = fields.Float('接口直径(mm)')
|
||||
|
||||
# cutting_tool_cutter_head_diameter = fields.Float('刀盘直径(mm)')
|
||||
cutting_tool_cutter_head_diameter = fields.Float(related='specification_id.cutter_head_diameter',
|
||||
string='刀盘直径(mm)')
|
||||
# specification_id.cutter_head_diameter
|
||||
# cutting_tool_interface_diameter = fields.Float('接口直径(mm)')
|
||||
cutting_tool_interface_diameter = fields.Float(related='specification_id.interface_diameter',
|
||||
string='接口直径(mm)')
|
||||
# specification_id.interface_diameter
|
||||
# 刀柄参数
|
||||
cutting_tool_clamping_diameter_max = fields.Float('最大夹持直径')
|
||||
cutting_tool_clamping_diameter_min = fields.Float('最小夹持直径')
|
||||
cutting_tool_flange_length = fields.Float('法兰柄长(mm)')
|
||||
cutting_tool_flange_diameter = fields.Float('法兰直径(mm)')
|
||||
cutting_tool_is_safety_lock = fields.Boolean('有无安全锁', default=False)
|
||||
cutting_tool_is_high_speed_cutting = fields.Boolean('可高速切削', default=False)
|
||||
cutting_tool_change_time = fields.Integer('换刀时间(s)')
|
||||
cutting_tool_clamping_way = fields.Char('夹持方式')
|
||||
cutting_tool_fit_chuck_size = fields.Char('适配夹头尺寸')
|
||||
cutting_tool_taper_shank_model = fields.Char('锥柄型号')
|
||||
# cutting_tool_clamping_diameter_max = fields.Float('最大夹持直径')
|
||||
cutting_tool_clamping_diameter_max = fields.Float(related='specification_id.max_clamping_diameter',
|
||||
string='最大夹持直径')
|
||||
# specification_id.max_clamping_diameter
|
||||
# cutting_tool_clamping_diameter_min = fields.Float('最小夹持直径')
|
||||
cutting_tool_clamping_diameter_min = fields.Float(related='specification_id.min_clamping_diameter',
|
||||
string='最小夹持直径')
|
||||
# specification_id.min_clamping_diameter
|
||||
# cutting_tool_flange_length = fields.Float('法兰柄长(mm)')
|
||||
cutting_tool_flange_length = fields.Float(related='specification_id.flange_shank_length',
|
||||
string='法兰柄长(mm)')
|
||||
# cutting_tool_flange_diameter = fields.Float('法兰直径(mm)')
|
||||
cutting_tool_flange_diameter = fields.Float(related='specification_id.flange_diameter',
|
||||
string='法兰直径(mm)')
|
||||
# cutting_tool_is_safety_lock = fields.Boolean('有无安全锁', default=False)
|
||||
cutting_tool_is_safety_lock = fields.Boolean(related='specification_id.is_safe_lock',
|
||||
string='有无安全锁')
|
||||
# cutting_tool_is_high_speed_cutting = fields.Boolean('可高速切削', default=False)
|
||||
cutting_tool_is_high_speed_cutting = fields.Boolean(related='specification_id.is_quick_cutting',
|
||||
string='可高速切削')
|
||||
# cutting_tool_change_time = fields.Integer('换刀时间(s)')
|
||||
cutting_tool_change_time = fields.Integer(related='specification_id.tool_changing_time',
|
||||
string='换刀时间(s)')
|
||||
# cutting_tool_clamping_way = fields.Char('夹持方式')
|
||||
cutting_tool_clamping_way = fields.Char(related='specification_id.clamping_mode',
|
||||
string='夹持方式')
|
||||
# cutting_tool_fit_chuck_size = fields.Char('适配夹头尺寸')
|
||||
cutting_tool_fit_chuck_size = fields.Char(related='specification_id.fit_chuck_size',
|
||||
string='适配夹头尺寸')
|
||||
# cutting_tool_taper_shank_model = fields.Char('锥柄型号')
|
||||
cutting_tool_taper_shank_model = fields.Char(related='specification_id.taper_shank_model',
|
||||
string='锥柄型号')
|
||||
cutting_tool_standard_speed = fields.Integer('标准转速(n/min)')
|
||||
cutting_tool_speed_max = fields.Integer('最大转速(n/min)')
|
||||
# cutting_tool_speed_max = fields.Integer('最大转速(n/min)')
|
||||
cutting_tool_speed_max = fields.Integer(related='specification_id.max_rotate_speed',
|
||||
string='最大转速(n/min)')
|
||||
# specification_id.max_rotate_speed
|
||||
cutting_tool_cooling_type = fields.Char('冷却类型')
|
||||
cutting_tool_dynamic_balance_class = fields.Char('动平衡等级')
|
||||
cutting_tool_fit_nut_model = fields.Char('适用锁紧螺母型号')
|
||||
|
||||
# cutting_tool_fit_nut_model = fields.Char('适用锁紧螺母型号')
|
||||
cutting_tool_fit_nut_model = fields.Char(related='specification_id.nut',
|
||||
string='适用锁紧螺母型号')
|
||||
# specification_id.nut
|
||||
# 夹头参数
|
||||
cutting_tool_taper = fields.Integer('锥度(°)')
|
||||
cutting_tool_top_diameter = fields.Float('顶部直径')
|
||||
cutting_tool_outer_diameter = fields.Float('外径(mm)')
|
||||
cutting_tool_inner_diameter = fields.Float('内径(mm)')
|
||||
cooling_suit_type_ids = fields.Char('适用冷却套型号')
|
||||
cutting_tool_max_load_capacity = fields.Float('最大负载能力(kg)')
|
||||
cutting_tool_er_size_model = fields.Char('尺寸型号')
|
||||
# cutting_tool_taper = fields.Integer('锥度(°)')
|
||||
cutting_tool_taper = fields.Integer(related='specification_id.taper',
|
||||
string='锥度(°)')
|
||||
# specification_id.taper
|
||||
# cutting_tool_top_diameter = fields.Float('顶部直径')
|
||||
cutting_tool_top_diameter = fields.Float(related='specification_id.top_diameter',
|
||||
string='顶部直径')
|
||||
# specification_id.top_diameter
|
||||
# cutting_tool_outer_diameter = fields.Float('外径(mm)')
|
||||
# specification_id.outer_diameter
|
||||
cutting_tool_outer_diameter = fields.Float(related='specification_id.outer_diameter',
|
||||
string='外径(mm)')
|
||||
# cutting_tool_inner_diameter = fields.Float('内径(mm)')
|
||||
cutting_tool_inner_diameter = fields.Float(related='specification_id.inner_diameter',
|
||||
string='内径(mm)')
|
||||
# specification_id.inner_diameter
|
||||
# cooling_suit_type_ids = fields.Char('适用冷却套型号')
|
||||
cooling_suit_type_ids = fields.Char(related='specification_id.cooling_jacket',
|
||||
string='适用冷却套型号')
|
||||
# specification_id.cooling_jacket
|
||||
# cutting_tool_max_load_capacity = fields.Float('最大负载能力(kg)')
|
||||
cutting_tool_max_load_capacity = fields.Float(related='specification_id.max_load_capacity',
|
||||
string='最大负载能力(kg)')
|
||||
# specification_id.max_load_capacity
|
||||
# cutting_tool_er_size_model = fields.Char('尺寸型号')
|
||||
cutting_tool_er_size_model = fields.Char(related='specification_id.er_size_model',
|
||||
string='尺寸型号')
|
||||
# specification_id.er_size_model
|
||||
# cutting_tool_handle_ids = fields.Many2many(
|
||||
# 'sf.cutting_tool.standard.library',
|
||||
# relation='product_cutting_tool_library_chuck_handle_rel',
|
||||
@@ -546,6 +761,10 @@ class ResProductMo(models.Model):
|
||||
domain="[('cutting_tool_type', '=', '刀柄')]",
|
||||
string='适用刀柄型号'
|
||||
)
|
||||
# cutting_tool_handle_id = fields.Many2one(related='cutting_tool_model_id.handle_id',
|
||||
# domain="[('cutting_tool_type', '=', '刀柄')]",
|
||||
# string='适用刀柄型号')
|
||||
# cutting_tool_model_id.handle_id
|
||||
|
||||
# 注册状态
|
||||
register_state = fields.Selection([('未注册', '未注册'), ('已注册', '已注册'), ('注册失败', '注册失败')],
|
||||
@@ -555,6 +774,10 @@ class ResProductMo(models.Model):
|
||||
# bfm下单
|
||||
manual_quotation = fields.Boolean('人工编程', default=False, readonly=True)
|
||||
part_number = fields.Char(string='零件图号', readonly=True)
|
||||
machining_drawings = fields.Binary('2D加工图纸', readonly=True)
|
||||
quality_standard = fields.Binary('质检标准', readonly=True)
|
||||
machining_drawings_name = fields.Char('2D加工图纸名', readonly=True)
|
||||
quality_standard_name = fields.Char('质检标准名', readonly=True)
|
||||
|
||||
@api.constrains('tool_length')
|
||||
def _check_tool_length_size(self):
|
||||
@@ -616,6 +839,11 @@ class ResProductMo(models.Model):
|
||||
else:
|
||||
return self.env.ref('sf_dlm.product_uom_cubic_millimeter')
|
||||
|
||||
def attachment_update(self, name, res_id, res_field):
|
||||
attachment_info = self.env['ir.attachment'].sudo().search(
|
||||
[('res_id', '=', res_id), ('res_field', '=', res_field)], limit=1)
|
||||
attachment_info.write({'name': name})
|
||||
|
||||
# 业务平台分配工厂后在智能工厂先创建销售订单再创建该产品
|
||||
def product_create(self, product_id, item, order_id, order_number, i):
|
||||
copy_product_id = product_id.with_user(self.env.ref("base.user_admin")).copy()
|
||||
@@ -654,6 +882,11 @@ class ResProductMo(models.Model):
|
||||
'manual_quotation': item['manual_quotation'] or False,
|
||||
'part_number': item.get('part_number') or '',
|
||||
'active': True,
|
||||
'machining_drawings_name': item['machining_drawings_name'],
|
||||
'quality_standard_name': item['quality_standard_name'],
|
||||
'machining_drawings': '' if not item['machining_drawings'] else base64.b64decode(
|
||||
item['machining_drawings']),
|
||||
'quality_standard': '' if not item['quality_standard'] else base64.b64decode(item['quality_standard']),
|
||||
}
|
||||
tax_id = self.env['account.tax'].sudo().search(
|
||||
[('type_tax_use', '=', 'sale'), ('amount', '=', item.get('tax')), ('price_include', '=', 'True')])
|
||||
@@ -661,6 +894,12 @@ class ResProductMo(models.Model):
|
||||
vals.update({'taxes_id': [(6, 0, [int(tax_id)])]})
|
||||
copy_product_id.sudo().write(vals)
|
||||
product_id.product_tmpl_id.active = False
|
||||
if item['machining_drawings'] and item['machining_drawings_name']:
|
||||
self.attachment_update(item['machining_drawings_name'], copy_product_id.product_tmpl_id.id,
|
||||
'machining_drawings')
|
||||
if item['quality_standard'] and item['quality_standard_name']:
|
||||
self.attachment_update(item['quality_standard_name'], copy_product_id.product_tmpl_id.id,
|
||||
'quality_standard')
|
||||
return copy_product_id
|
||||
|
||||
def _get_ids(self, param):
|
||||
|
||||
@@ -182,6 +182,11 @@ class StockRule(models.Model):
|
||||
moves._action_confirm()
|
||||
return True
|
||||
|
||||
def attachment_update(self, name, res_id, res_field):
|
||||
attachment_info = self.env['ir.attachment'].sudo().search(
|
||||
[('res_id', '=', res_id), ('res_field', '=', res_field)], limit=1)
|
||||
attachment_info.write({'name': name})
|
||||
|
||||
@api.model
|
||||
def _run_manufacture(self, procurements):
|
||||
productions_values_by_company = defaultdict(list)
|
||||
@@ -272,6 +277,16 @@ class StockRule(models.Model):
|
||||
if quick_easy_order:
|
||||
production.write({'part_number': quick_easy_order.part_drawing_number,
|
||||
'part_drawing': quick_easy_order.machining_drawings})
|
||||
else:
|
||||
production.write({'part_number': production.product_id.part_number,
|
||||
'part_drawing': production.product_id.machining_drawings,
|
||||
'quality_standard': production.product_id.quality_standard})
|
||||
if production.product_id.machining_drawings and production.product_id.machining_drawings_name:
|
||||
self.attachment_update(production.product_id.machining_drawings_name, production.id,
|
||||
'part_drawing')
|
||||
if production.product_id.quality_standard and production.product_id.quality_standard_name:
|
||||
self.attachment_update(production.product_id.quality_standard_name, production.id,
|
||||
'quality_standard')
|
||||
if sale_order:
|
||||
# sale_order.write({'schedule_status': 'to schedule'})
|
||||
self.env['sf.production.plan'].sudo().with_company(company_id).create({
|
||||
|
||||
@@ -103,10 +103,20 @@ access_mrp_production_split_multi_group_sf_mrp_user,access.mrp.production.split.
|
||||
access_mrp_production_split_group_sf_mrp_user,access.mrp.production.split,mrp.model_mrp_production_split,sf_base.group_sf_mrp_user,1,1,1,0
|
||||
access_mrp_production_split_line_group_sf_mrp_user,access.mrp.production.split.line,mrp.model_mrp_production_split_line,sf_base.group_sf_mrp_user,1,1,1,0
|
||||
access_mrp_workcenter_capacity_manager_group_sf_mrp_user,mrp.workcenter.capacity.manager,mrp.model_mrp_workcenter_capacity,sf_base.group_sf_mrp_user,1,1,1,0
|
||||
|
||||
|
||||
access_mrp_workcenter_group_quality,mrp_workcenter_group_quality,model_mrp_workcenter,sf_base.group_quality,1,0,0,0
|
||||
access_mrp_workcenter_group_quality_director,mrp_workcenter_group_quality_director,model_mrp_workcenter,sf_base.group_quality_director,1,0,0,0
|
||||
access_sf_detection_result_group_quality,sf_detection_result_group_quality,model_sf_detection_result,sf_base.group_quality,1,0,1,0
|
||||
access_sf_detection_result_group_quality_director,sf_detection_result_group_quality_director,model_sf_detection_result,sf_base.group_quality_director,1,0,1,0
|
||||
access_mrp_workcenter_productivity_loss_group_quality,mrp_workcenter_productivity_loss_group_quality,mrp.model_mrp_workcenter_productivity_loss,sf_base.group_quality,1,0,0,0
|
||||
access_mrp_workcenter_productivity_loss_group_quality_director,mrp_workcenter_productivity_loss_group_quality_director,mrp.model_mrp_workcenter_productivity_loss,sf_base.group_quality_director,1,0,0,0
|
||||
access_mrp_workcenter_productivity_group_quality,mrp_workcenter_productivity_group_quality,mrp.model_mrp_workcenter_productivity,sf_base.group_quality,1,1,1,0
|
||||
access_mrp_workcenter_productivity_group_quality_director,mrp_workcenter_productivity_group_quality_director,mrp.model_mrp_workcenter_productivity,sf_base.group_quality_director,1,1,1,0
|
||||
access_mrp_production_group_plan_dispatch,mrp_production,model_mrp_production,sf_base.group_plan_dispatch,1,1,0,0
|
||||
access_mrp_workorder,mrp_workorder,model_mrp_workorder,sf_base.group_plan_dispatch,1,1,1,0
|
||||
access_mrp_production_group_quality,mrp_production,model_mrp_production,sf_base.group_quality,1,1,0,0
|
||||
access_mrp_production_group_quality_director,mrp_production,model_mrp_production,sf_base.group_quality_director,1,1,0,0
|
||||
access_mrp_workorder_group_quality,mrp_workorder,model_mrp_workorder,sf_base.group_quality,1,1,0,0
|
||||
access_mrp_workorder_group_quality_director,mrp_workorder,model_mrp_workorder,sf_base.group_quality_director,1,1,0,0
|
||||
access_mrp_workorder,mrp_workorder,model_mrp_workorder,sf_base.group_plan_dispatch,1,1,0,0
|
||||
access_sf_production_line_group_plan_dispatch,sf.production.line,model_sf_production_line,sf_base.group_plan_dispatch,1,0,0,0
|
||||
access_sf_production_line_group_plan_director,sf.production.line,model_sf_production_line,sf_base.group_plan_director,1,1,1,0
|
||||
access_sf_production_line,sf.production.line,model_sf_production_line,sf_maintenance.sf_group_equipment_user,1,1,1,0
|
||||
|
||||
|
@@ -98,9 +98,9 @@
|
||||
<field name="production_line_id" readonly="1"/>
|
||||
<!-- <field name="production_line_state" readonly="1"/>-->
|
||||
<field name="part_number" string="成品的零件图号"/>
|
||||
<field name="part_drawing"/>
|
||||
<field name="tool_state"/>
|
||||
<field name="tool_state_remark" string="备注" attrs="{'invisible': [('tool_state', '!=', '1')]}"/>
|
||||
<field name="deadline_of_delivery" readonly="1"/>
|
||||
<field name="tool_state_remark2" invisible="1"/>
|
||||
</xpath>
|
||||
<xpath expr="//header//button[@name='action_cancel']" position="replace">
|
||||
@@ -296,6 +296,13 @@
|
||||
</attribute>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//sheet//notebook//page[@name='operations']//field[@name='workorder_ids']"
|
||||
position="replace">
|
||||
<field name="workorder_ids"
|
||||
attrs="{'readonly': ['|', ('state', '!=', 'test_value'), '&', ('state', '=', 'done'), ('is_locked', '=', True)]}"
|
||||
context="{'tree_view_ref': 'mrp.mrp_production_workorder_tree_editable_view', 'default_product_uom_id': product_uom_id, 'from_manufacturing_order': True}"/>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//sheet//notebook//page[@name='operations']" position="after">
|
||||
<page string="检测结果" attrs="{'invisible': [('detection_result_ids', '=', [])]}">
|
||||
<field name="detection_result_ids" string="" readonly="0">
|
||||
@@ -321,6 +328,16 @@
|
||||
<xpath expr="//field[@name='components_availability']" position="attributes">
|
||||
<attribute name="string">投料状态</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//sheet//notebook" position="inside">
|
||||
<page string="零件图纸">
|
||||
<field name="part_drawing" widget="adaptive_viewer"/>
|
||||
</page>
|
||||
</xpath>
|
||||
<xpath expr="//sheet//notebook" position="inside">
|
||||
<page string="质检标准">
|
||||
<field name="quality_standard" widget="adaptive_viewer"/>
|
||||
</page>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -344,8 +361,11 @@
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//tree" position="attributes">
|
||||
<attribute name="default_order">sequence</attribute>
|
||||
<attribute name="decoration-warning">delivery_warning == 'warning'</attribute>
|
||||
<attribute name="decoration-danger">delivery_warning == 'overdue'</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='state']" position="replace">
|
||||
<field name="delivery_warning" invisible="True"/>
|
||||
<field name="state" widget="badge" decoration-warning="state == 'progress'"
|
||||
decoration-success="state == 'done'" decoration-danger="state in ('cancel','rework')"
|
||||
decoration-muted="state == 'to be detected'"
|
||||
@@ -552,7 +572,12 @@
|
||||
</div>
|
||||
<field name="priority" widget="priority"/>
|
||||
</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">
|
||||
规格:
|
||||
<field name="specification_id"/>
|
||||
|
||||
@@ -101,7 +101,9 @@
|
||||
<!-- <field name="target">fullscreen</field>-->
|
||||
<field name="target">current</field>
|
||||
<field name="domain">[('state', '!=', 'cancel'),('schedule_state', '=', '已排')]</field>
|
||||
<field name="context">{'search_default_product': 1, 'search_default_workcenter_id': active_id}</field>
|
||||
<field name="context">{'search_default_product': 1, 'search_default_workcenter_id':
|
||||
active_id,'search_default_filter_order_warning':1,'search_default_filter_order_overdue':1}
|
||||
</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_workorder">
|
||||
没有工单要做!
|
||||
@@ -221,15 +223,52 @@
|
||||
</page>
|
||||
</xpath>
|
||||
<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)]}'
|
||||
decoration-danger="tag_type == '重新加工'"/>
|
||||
<field name="rfid_code" force_save="1" readonly="1" cache="True"
|
||||
<field name="rfid_code" force_save="1" readonly="0" cache="True"
|
||||
attrs="{'invisible': [('rfid_code_old', '!=', False)]}"/>
|
||||
<field name="rfid_code_old" readonly="1" attrs="{'invisible': [('rfid_code_old', '=', False)]}"/>
|
||||
|
||||
</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>&nbsp;</span>
|
||||
<label for="material_width" string="宽"/>
|
||||
<field name="material_width" class="o_address_zip"/>
|
||||
<span>&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">
|
||||
<attribute name="string">计划加工时间</attribute>
|
||||
</xpath>
|
||||
@@ -246,76 +285,12 @@
|
||||
<field name='materiel_width' string="宽"/>
|
||||
<field name='materiel_height' string="高"/>
|
||||
</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="1"
|
||||
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>-->
|
||||
<!-- <!– <field name="glb_file" string="模型" readonly="1" widget="Viewer3D"/>–>-->
|
||||
<!-- </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">
|
||||
<!-- <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","!=","装夹预调")]}'>
|
||||
<group>
|
||||
<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="托盘">
|
||||
<field name="tray_serial_number" readonly="1" string="序列号"/>
|
||||
</group>
|
||||
@@ -330,10 +305,6 @@
|
||||
<field name="tray_model_id" readonly="1" string="型号"/>
|
||||
</group>
|
||||
</group>
|
||||
<group string="加工图纸">
|
||||
<!-- 隐藏加工图纸字段名 -->
|
||||
<field name="processing_drawing" widget="pdf_viewer" string="" readonly="1"/>
|
||||
</group>
|
||||
<group string="预调程序信息">
|
||||
<field name="preset_program_information" colspan="2" nolabel="1"
|
||||
placeholder="如有预调程序信息请在此处输入....."/>
|
||||
@@ -520,7 +491,8 @@
|
||||
|
||||
<xpath expr="//form//header" position="inside">
|
||||
<button type="object" class="oe_highlight jikimo_button_confirm" name="get_three_check_datas"
|
||||
string="获取数据" attrs='{"invisible": [("state","!=","progress")]}'/>
|
||||
string="获取数据"
|
||||
attrs='{"invisible": ["|", ("state","!=","progress"), ("routing_type","!=","装夹预调")]}'/>
|
||||
</xpath>
|
||||
|
||||
|
||||
@@ -571,9 +543,7 @@
|
||||
<!-- <field name="button_state" invisible="1"/>-->
|
||||
</tree>
|
||||
</field>
|
||||
<group>
|
||||
<field name="cnc_worksheet" string="工作指令" widget="pdf_viewer"/>
|
||||
</group>
|
||||
|
||||
</page>
|
||||
<page string="CMM程序" attrs='{"invisible": [("routing_type","!=","CNC加工")]}'>
|
||||
<field name="cmm_ids" widget="one2many" string="CMM程序" readonly="1">
|
||||
@@ -602,33 +572,60 @@
|
||||
|
||||
</page>
|
||||
</xpath>
|
||||
<xpath expr="//form//sheet//group//group//div[1]" position="after">
|
||||
<label for="date_start" string="实际加工时间"/>
|
||||
<div class="oe_inline">
|
||||
<field name="date_start" class="mr8 oe_inline"/>
|
||||
<strong class="mr8 oe_inline">到</strong>
|
||||
<field name="date_finished" class="oe_inline"/>
|
||||
</div>
|
||||
<!-- <xpath expr="//form//sheet//group//group//div[1]" position="after">-->
|
||||
<!-- <label for="date_start" string="实际加工时间"/>-->
|
||||
<!-- <div class="oe_inline">-->
|
||||
<!-- <field name="date_start" class="mr8 oe_inline"/>-->
|
||||
<!-- <strong class="mr8 oe_inline">到</strong>-->
|
||||
<!-- <field name="date_finished" class="oe_inline"/>-->
|
||||
<!-- </div>-->
|
||||
<!-- </xpath>-->
|
||||
<xpath expr="//page[@name='time_tracking']//field[@name='time_ids']//tree//field[@name='date_end']"
|
||||
position="after">
|
||||
<field name="duration" string="实际时长"/>
|
||||
</xpath>
|
||||
<xpath expr="//form//sheet//group//group//div[3]" 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>&nbsp;</span>
|
||||
<label for="material_width" string="宽"/>
|
||||
<field name="material_width" class="o_address_zip"/>
|
||||
<span>&nbsp;</span>
|
||||
<label for="material_height" string="高"/>
|
||||
<field name="material_height" class="o_address_zip"/>
|
||||
</div>
|
||||
<field name="part_number" string="成品的零件图号"/>
|
||||
<xpath expr="//page[@name='time_tracking']" position="attributes">
|
||||
<attribute name="groups">
|
||||
mrp.group_mrp_manager,sf_base.group_sf_mrp_manager,sf_base.group_sf_equipment_user,sf_base.group_sf_order_user
|
||||
</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//sheet//notebook" position="inside">
|
||||
<page string="2D加工图纸">
|
||||
<field name="machining_drawings" widget="adaptive_viewer"
|
||||
attrs="{'invisible': [('routing_type', '!=', 'CNC加工')]}"/>
|
||||
</page>
|
||||
</xpath>
|
||||
<xpath expr="//sheet//notebook" position="inside">
|
||||
<page string="质检标准">
|
||||
<field name="quality_standard" widget="adaptive_viewer"
|
||||
attrs="{'invisible': [('routing_type', '!=', 'CNC加工')]}"/>
|
||||
</page>
|
||||
</xpath>
|
||||
</field>
|
||||
</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">
|
||||
<field name="name">custom.workorder.search</field>
|
||||
<field name="model">mrp.workorder</field>
|
||||
@@ -640,6 +637,12 @@
|
||||
<xpath expr="//filter[@name='progress']" position="after">
|
||||
<filter string="待检测" name="state" domain="[('state','=','to be detected')]"/>
|
||||
</xpath>
|
||||
<xpath expr="//filter[@name='date_start_filter']" position="before">
|
||||
<separator/>
|
||||
<filter string="预警" name="filter_order_warning" domain="[('delivery_warning', '=', 'warning')]"/>
|
||||
<filter string="逾期" name="filter_order_overdue" domain="[('delivery_warning', '=', 'overdue')]"/>
|
||||
<separator/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -797,7 +800,7 @@
|
||||
<field name="feeder_station_start_id" readonly="1" force_save="1"/>
|
||||
<!-- <field name="type" readonly="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"
|
||||
string="历史"/>
|
||||
</tree>
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
from . import models
|
||||
from . import models
|
||||
from . import controllers
|
||||
|
||||
@@ -11,10 +11,13 @@
|
||||
""",
|
||||
'category': 'sf',
|
||||
'website': 'https://www.sf.jikimo.com',
|
||||
'depends': ['base', 'sf_plan', 'sf_sale'],
|
||||
'depends': ['sale', 'purchase', 'sf_plan', 'jikimo_message_notify', 'stock', 'sf_quality', 'mrp'],
|
||||
'data': [
|
||||
'data/bussiness_node.xml',
|
||||
'data/cron_data.xml',
|
||||
'data/template_data.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'views/sf_message_template_view.xml',
|
||||
|
||||
],
|
||||
'test': [
|
||||
],
|
||||
|
||||
1
sf_message/controllers/__init__.py
Normal file
1
sf_message/controllers/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import main
|
||||
60
sf_message/controllers/main.py
Normal file
60
sf_message/controllers/main.py
Normal file
@@ -0,0 +1,60 @@
|
||||
import json
|
||||
import requests
|
||||
import logging
|
||||
from odoo import http
|
||||
from odoo.http import request
|
||||
from odoo.addons.sf_mrs_connect.controllers.controllers import Sf_Mrs_Connect
|
||||
from odoo.addons.sf_base.commons.common import Common
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class MessageSfMrsConnect(Sf_Mrs_Connect):
|
||||
|
||||
@http.route('/api/cnc_processing/create', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False,
|
||||
cors="*")
|
||||
def get_cnc_processing_create(self, **kw):
|
||||
res = super(MessageSfMrsConnect, self).get_cnc_processing_create(**kw)
|
||||
res = json.loads(res)
|
||||
_logger.info('已进入消息推送:%s' % res)
|
||||
if res.get('production_ids'):
|
||||
try:
|
||||
_logger.info('已编程的制造订单:%s' % res.get('production_ids'))
|
||||
productions = request.env['mrp.production'].sudo().search([('id', 'in', res.get('production_ids'))])
|
||||
# 过滤programming_state为已编程,tool_state为2的制造订单
|
||||
tool_state_valid_productions = productions.filtered(lambda x: x.tool_state == '2')
|
||||
if tool_state_valid_productions:
|
||||
data = {
|
||||
'name': tool_state_valid_productions[0].programming_no
|
||||
}
|
||||
# 请求cloud接口,发送微信消息推送
|
||||
configsettings = request.env['res.config.settings'].sudo().get_values()
|
||||
config_header = Common.get_headers(self, configsettings['token'], configsettings['sf_secret_key'])
|
||||
url = '/api/message/invalid_tool_state'
|
||||
config_url = configsettings['sf_url'] + url
|
||||
data['token'] = configsettings['token']
|
||||
ret = requests.post(config_url, json=data, headers=config_header)
|
||||
ret = ret.json()
|
||||
_logger.info('无效用刀异常消息推送接口:%s' % ret)
|
||||
except Exception as e:
|
||||
_logger.info('无效用刀异常消息推送接口:%s' % e)
|
||||
return json.JSONEncoder().encode(res)
|
||||
|
||||
@http.route('/api/maintenance_logs/notify', type='json', auth='public', methods=['GET', 'POST'], csrf=False, cors="*")
|
||||
def maintenance_logs_notify(self, **kw):
|
||||
res = {'code': 200, 'message': '设备故障日志信息推送成功'}
|
||||
datas = request.httprequest.data
|
||||
ret = json.loads(datas)
|
||||
log_id = ret.get('log_id')
|
||||
if not log_id:
|
||||
res = {'code': 400, 'message': '设备故障日志id不能为空'}
|
||||
return json.JSONEncoder().encode(res)
|
||||
try:
|
||||
if not isinstance(log_id, list):
|
||||
log_id = [log_id]
|
||||
maintenance_logs = request.env['sf.maintenance.logs'].sudo().search([('id', 'in', [int(id) for id in log_id])])
|
||||
if maintenance_logs:
|
||||
maintenance_logs.add_queue('设备故障')
|
||||
except Exception as e:
|
||||
res = {'code': 400, 'message': '设备故障信息推送失败', 'error': str(e)}
|
||||
return json.JSONEncoder().encode(res)
|
||||
|
||||
110
sf_message/data/bussiness_node.xml
Normal file
110
sf_message/data/bussiness_node.xml
Normal file
@@ -0,0 +1,110 @@
|
||||
<?xml version="1.0" ?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<!--销售订单-->
|
||||
<record id="bussiness_pending_order" model="jikimo.message.bussiness.node">
|
||||
<field name="name">待接单</field>
|
||||
<field name="model">sale.order</field>
|
||||
</record>
|
||||
|
||||
<record id="bussiness_to_be_confirm" model="jikimo.message.bussiness.node">
|
||||
<field name="name">确认接单</field>
|
||||
<field name="model">sale.order</field>
|
||||
</record>
|
||||
|
||||
<record id="bussiness_sale_order_overdue_warning" model="jikimo.message.bussiness.node">
|
||||
<field name="name">销售订单逾期预警</field>
|
||||
<field name="model">sale.order</field>
|
||||
</record>
|
||||
|
||||
<record id="bussiness_sale_order_overdue" model="jikimo.message.bussiness.node">
|
||||
<field name="name">销售订单已逾期</field>
|
||||
<field name="model">sale.order</field>
|
||||
</record>
|
||||
|
||||
|
||||
<record id="transfer_inventory" model="jikimo.message.bussiness.node">
|
||||
<field name="name">调拨入库</field>
|
||||
<field name="model">stock.picking</field>
|
||||
</record>
|
||||
<record id="tool_assembly" model="jikimo.message.bussiness.node">
|
||||
<field name="name">功能刀具组装</field>
|
||||
<field name="model">sf.functional.tool.assembly</field>
|
||||
</record>
|
||||
<record id="tool_dismantle" model="jikimo.message.bussiness.node">
|
||||
<field name="name">功能刀具寿命到期</field>
|
||||
<field name="model">sf.functional.tool.dismantle</field>
|
||||
</record>
|
||||
<record id="bussiness_material_purchase_remind" model="jikimo.message.bussiness.node">
|
||||
<field name="name">坯料采购提醒</field>
|
||||
<field name="model">purchase.order</field>
|
||||
</record>
|
||||
<record id="bussiness_material_picking_remind" model="jikimo.message.bussiness.node">
|
||||
<field name="name">坯料发料提醒</field>
|
||||
<field name="model">stock.picking</field>
|
||||
</record>
|
||||
|
||||
<!--工单-->
|
||||
<record id="bussiness_mrp_workorder_remind" model="jikimo.message.bussiness.node">
|
||||
<field name="name">工单已下发通知</field>
|
||||
<field name="model">mrp.workorder</field>
|
||||
</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_cnc_overdue_warning" model="jikimo.message.bussiness.node">
|
||||
<field name="name">CNC加工工单逾期预警</field>
|
||||
<field name="model">mrp.workorder</field>
|
||||
</record>
|
||||
<record id="bussiness_mrp_workorder_cnc_overdue" model="jikimo.message.bussiness.node">
|
||||
<field name="name">CNC加工工单已逾期</field>
|
||||
<field name="model">mrp.workorder</field>
|
||||
</record>
|
||||
|
||||
<record id="bussiness_mrp_workorder_unclamp_overdue_warning" model="jikimo.message.bussiness.node">
|
||||
<field name="name">解除装夹工单逾期预警</field>
|
||||
<field name="model">mrp.workorder</field>
|
||||
</record>
|
||||
|
||||
<record id="bussiness_mrp_workorder_unclamp_overdue" model="jikimo.message.bussiness.node">
|
||||
<field name="name">解除装夹工单已逾期</field>
|
||||
<field name="model">mrp.workorder</field>
|
||||
</record>
|
||||
|
||||
<record id="bussiness_mrp_workorder_surface_overdue_warning" model="jikimo.message.bussiness.node">
|
||||
<field name="name">表面工艺工单逾期预警</field>
|
||||
<field name="model">mrp.workorder</field>
|
||||
</record>
|
||||
|
||||
<record id="bussiness_mrp_workorder_surface_overdue" model="jikimo.message.bussiness.node">
|
||||
<field name="name">表面工艺工单已逾期</field>
|
||||
<field name="model">mrp.workorder</field>
|
||||
</record>
|
||||
|
||||
<record id="bussiness_quality_cnc_test" model="jikimo.message.bussiness.node">
|
||||
<field name="name">待质量判定</field>
|
||||
<field name="model">quality.cnc.test</field>
|
||||
</record>
|
||||
|
||||
<record id="bussiness_maintenance_logs" model="jikimo.message.bussiness.node">
|
||||
<field name="name">设备故障</field>
|
||||
<field name="model">sf.maintenance.logs</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
42
sf_message/data/cron_data.xml
Normal file
42
sf_message/data/cron_data.xml
Normal file
@@ -0,0 +1,42 @@
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<record model="ir.cron" id="ir_cron_sale_order_overdue_warning">
|
||||
<field name="name">检查销售订单是否已逾期预警和逾期</field>
|
||||
<field name="model_id" ref="model_sale_order"/>
|
||||
<field name="state">code</field>
|
||||
<field name="code">model._overdue_or_warning_func()</field>
|
||||
<field name="interval_number">10</field>
|
||||
<field name="interval_type">minutes</field>
|
||||
<field name="numbercall">-1</field>
|
||||
<field name="doall" eval="False"/>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<field name="active" eval="True"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.cron" id="ir_cron_mrp_workorder_overdue_warning">
|
||||
<field name="name">检查工单是否已逾期预警和逾期</field>
|
||||
<field name="model_id" ref="model_mrp_workorder"/>
|
||||
<field name="state">code</field>
|
||||
<field name="code">model._overdue_or_warning_func()</field>
|
||||
<field name="interval_number">10</field>
|
||||
<field name="interval_type">minutes</field>
|
||||
<field name="numbercall">-1</field>
|
||||
<field name="doall" eval="False"/>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<field name="active" eval="True"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.cron" id="ir_cron_mrp_workorder_recover_time_warning">
|
||||
<field name="name">检查工单是否完成并恢复正常时效</field>
|
||||
<field name="model_id" ref="model_mrp_workorder"/>
|
||||
<field name="state">code</field>
|
||||
<field name="code">model._recover_time_warning_func()</field>
|
||||
<field name="interval_number">10</field>
|
||||
<field name="interval_type">minutes</field>
|
||||
<field name="numbercall">-1</field>
|
||||
<field name="doall" eval="False"/>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<field name="active" eval="True"/>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
279
sf_message/data/template_data.xml
Normal file
279
sf_message/data/template_data.xml
Normal file
@@ -0,0 +1,279 @@
|
||||
<?xml version="1.0" ?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
|
||||
<record id="template_pending_order" model="jikimo.message.template">
|
||||
<field name="name">待接单</field>
|
||||
<field name="model_id" ref="sale.model_sale_order"/>
|
||||
<field name="model">sale.order</field>
|
||||
<field name="bussiness_node_id" ref="bussiness_pending_order"/>
|
||||
<field name="msgtype">markdown</field>
|
||||
<field name="urgency">normal</field>
|
||||
<field name="content">### 待接单提醒:
|
||||
单号:销售订单[{{name}}]({{url}})
|
||||
事项:请确认是否接单。
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="template_to_be_confirm" model="jikimo.message.template">
|
||||
<field name="name">待排程提醒</field>
|
||||
<field name="model_id" ref="sale.model_sale_order"/>
|
||||
<field name="model">sale.order</field>
|
||||
<field name="bussiness_node_id" ref="bussiness_to_be_confirm"/>
|
||||
<field name="msgtype">markdown</field>
|
||||
<field name="urgency">normal</field>
|
||||
<field name="content">### 待排程提醒:
|
||||
单号:产品[{{product_id}}]({{url}})
|
||||
事项:{{mrp_production_count}}个制造订单待计划排程
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="template_sale_order_overdue_warning" model="jikimo.message.template">
|
||||
<field name="name">销售订单逾期预警</field>
|
||||
<field name="model_id" ref="sale.model_sale_order"/>
|
||||
<field name="model">sale.order</field>
|
||||
<field name="bussiness_node_id" ref="bussiness_sale_order_overdue_warning"/>
|
||||
<field name="msgtype">markdown</field>
|
||||
<field name="urgency">normal</field>
|
||||
<field name="content">### 销售订单逾期预警
|
||||
事项:共有[{{warning_num}}]({{url}})个销售订单有逾期风险
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="template_sale_order_overdue" model="jikimo.message.template">
|
||||
<field name="name">销售订单已逾期</field>
|
||||
<field name="model_id" ref="sale.model_sale_order"/>
|
||||
<field name="model">sale.order</field>
|
||||
<field name="bussiness_node_id" ref="bussiness_sale_order_overdue"/>
|
||||
<field name="msgtype">markdown</field>
|
||||
<field name="urgency">urgent</field>
|
||||
<field name="content">### 销售订单已逾期提醒
|
||||
事项:共有[{{overdue_num}}]({{url}})个销售订单已逾期
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="template_material_purchase_remind" model="jikimo.message.template">
|
||||
<field name="name">坯料采购提醒</field>
|
||||
<field name="model_id" ref="purchase.model_purchase_order"/>
|
||||
<field name="model">purchase.order</field>
|
||||
<field name="bussiness_node_id" ref="bussiness_material_purchase_remind"/>
|
||||
<field name="msgtype">markdown</field>
|
||||
<field name="urgency">normal</field>
|
||||
<field name="content">### 坯料采购通知:
|
||||
单号:采购单[{{name}}]({{request_url}})
|
||||
事项:请确认坯料采购单并处理</field>
|
||||
</record>
|
||||
|
||||
<record id="template_material_picking_remind" model="jikimo.message.template">
|
||||
<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="bussiness_material_picking_remind"/>
|
||||
<field name="msgtype">markdown</field>
|
||||
<field name="urgency">normal</field>
|
||||
<field name="content">### 坯料发料提醒:
|
||||
单号:产品[{{product_id}}]({{request_url}})
|
||||
事项:共{{number}}个生产发料单待确认处理</field>
|
||||
</record>
|
||||
|
||||
<record id="template_mrp_workorder_remind" model="jikimo.message.template">
|
||||
<field name="name">工单已下发通知</field>
|
||||
<field name="model_id" ref="mrp_workorder.model_mrp_workorder"/>
|
||||
<field name="model">mrp.workorder</field>
|
||||
<field name="bussiness_node_id" ref="bussiness_mrp_workorder_remind"/>
|
||||
<field name="msgtype">markdown</field>
|
||||
<field name="urgency">normal</field>
|
||||
<field name="content">### 工单已下发通知:
|
||||
单号:产品[{{product_id}}]({{request_url}})
|
||||
事项:共{{number}}个工单已下发,请查收知悉</field>
|
||||
</record>
|
||||
|
||||
<record id="template_mrp_workorder_pre_overdue_warning" model="jikimo.message.template">
|
||||
<field name="name">装夹预调工单逾期预警</field>
|
||||
<field name="model_id" ref="mrp_workorder.model_mrp_workorder"/>
|
||||
<field name="model">mrp.workorder</field>
|
||||
<field name="bussiness_node_id" ref="bussiness_mrp_workorder_pre_overdue_warning"/>
|
||||
<field name="msgtype">markdown</field>
|
||||
<field name="send_type">timing</field>
|
||||
<field name="urgency">normal</field>
|
||||
<field name="content">### 工单逾期预警
|
||||
事项:共有[{{warning_num}}]({{url}})工单有逾期风险</field>
|
||||
</record>
|
||||
|
||||
<record id="template_mrp_workorder_pre_overdue" model="jikimo.message.template">
|
||||
<field name="name">装夹预调工单已逾期</field>
|
||||
<field name="model_id" ref="mrp_workorder.model_mrp_workorder"/>
|
||||
<field name="model">mrp.workorder</field>
|
||||
<field name="bussiness_node_id" ref="bussiness_mrp_workorder_pre_overdue"/>
|
||||
<field name="msgtype">markdown</field>
|
||||
<field name="send_type">timing</field>
|
||||
<field name="urgency">normal</field>
|
||||
<field name="content">### 工单已逾期提醒
|
||||
事项:共有[{{overdue_num}}]({{url}})工单已逾期</field>
|
||||
</record>
|
||||
|
||||
<record id="template_mrp_workorder_cnc_overdue_warning" model="jikimo.message.template">
|
||||
<field name="name">CNC加工工单逾期预警</field>
|
||||
<field name="model_id" ref="mrp_workorder.model_mrp_workorder"/>
|
||||
<field name="model">mrp.workorder</field>
|
||||
<field name="bussiness_node_id" ref="bussiness_mrp_workorder_cnc_overdue_warning"/>
|
||||
<field name="msgtype">markdown</field>
|
||||
<field name="send_type">timing</field>
|
||||
<field name="urgency">normal</field>
|
||||
<field name="content">### 工单逾期预警
|
||||
事项:共有[{{warning_num}}]({{url}})工单有逾期风险</field>
|
||||
</record>
|
||||
|
||||
<record id="template_mrp_workorder_cnc_overdue" model="jikimo.message.template">
|
||||
<field name="name">CNC加工工单已逾期</field>
|
||||
<field name="model_id" ref="mrp_workorder.model_mrp_workorder"/>
|
||||
<field name="model">mrp.workorder</field>
|
||||
<field name="bussiness_node_id" ref="bussiness_mrp_workorder_cnc_overdue"/>
|
||||
<field name="msgtype">markdown</field>
|
||||
<field name="send_type">timing</field>
|
||||
<field name="urgency">normal</field>
|
||||
<field name="content">### 工单已逾期提醒
|
||||
事项:共有[{{overdue_num}}]({{url}})工单已逾期</field>
|
||||
</record>
|
||||
|
||||
<record id="template_mrp_workorder_unclamp_overdue_warning" model="jikimo.message.template">
|
||||
<field name="name">解除装夹工单逾期预警</field>
|
||||
<field name="model_id" ref="mrp_workorder.model_mrp_workorder"/>
|
||||
<field name="model">mrp.workorder</field>
|
||||
<field name="bussiness_node_id" ref="bussiness_mrp_workorder_unclamp_overdue_warning"/>
|
||||
<field name="msgtype">markdown</field>
|
||||
<field name="send_type">timing</field>
|
||||
<field name="urgency">normal</field>
|
||||
<field name="content">### 工单逾期预警
|
||||
事项:共有[{{warning_num}}]({{url}})工单有逾期风险</field>
|
||||
</record>
|
||||
|
||||
<record id="template_mrp_workorder_unclamp_overdue" model="jikimo.message.template">
|
||||
<field name="name">解除装夹工单已逾期</field>
|
||||
<field name="model_id" ref="mrp_workorder.model_mrp_workorder"/>
|
||||
<field name="model">mrp.workorder</field>
|
||||
<field name="bussiness_node_id" ref="bussiness_mrp_workorder_unclamp_overdue"/>
|
||||
<field name="msgtype">markdown</field>
|
||||
<field name="send_type">timing</field>
|
||||
<field name="urgency">normal</field>
|
||||
<field name="content">### 工单已逾期提醒
|
||||
事项:共有[{{overdue_num}}]({{url}})工单已逾期</field>
|
||||
</record>
|
||||
|
||||
<record id="template_mrp_workorder_surface_overdue_warning" model="jikimo.message.template">
|
||||
<field name="name">表面工艺工单逾期预警</field>
|
||||
<field name="model_id" ref="mrp_workorder.model_mrp_workorder"/>
|
||||
<field name="model">mrp.workorder</field>
|
||||
<field name="bussiness_node_id" ref="bussiness_mrp_workorder_surface_overdue_warning"/>
|
||||
<field name="msgtype">markdown</field>
|
||||
<field name="send_type">timing</field>
|
||||
<field name="urgency">normal</field>
|
||||
<field name="content">### 工单逾期预警
|
||||
事项:共有[{{warning_num}}]({{url}})工单有逾期风险</field>
|
||||
</record>
|
||||
|
||||
<record id="template_mrp_workorder_surface_overdue" model="jikimo.message.template">
|
||||
<field name="name">表面工艺工单已逾期</field>
|
||||
<field name="model_id" ref="mrp_workorder.model_mrp_workorder"/>
|
||||
<field name="model">mrp.workorder</field>
|
||||
<field name="bussiness_node_id" ref="bussiness_mrp_workorder_surface_overdue"/>
|
||||
<field name="msgtype">markdown</field>
|
||||
<field name="send_type">timing</field>
|
||||
<field name="urgency">normal</field>
|
||||
<field name="content">### 工单已逾期提醒
|
||||
事项:共有[{{overdue_num}}]({{url}})工单已逾期</field>
|
||||
</record>
|
||||
|
||||
|
||||
|
||||
<record id="template_transfer_inventory_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="transfer_inventory"/>
|
||||
<field name="msgtype">markdown</field>
|
||||
<field name="urgency">normal</field>
|
||||
<field name="content">### 调拨入库通知:
|
||||
单号:调拨入库单[{{name}}]({{transfer_inventory_special_url}})
|
||||
事项:完成刀具物料上架入库</field>
|
||||
</record>
|
||||
|
||||
<record id="template_tool_expired_remind" model="jikimo.message.template">
|
||||
<!-- <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="name">功能刀具寿命到期</field>
|
||||
<field name="model_id" ref="sf_tool_management.model_sf_functional_tool_dismantle"/>
|
||||
<field name="model">sf.functional.tool.dismantle</field>
|
||||
<field name="bussiness_node_id" ref="tool_dismantle"/>
|
||||
<field name="msgtype">markdown</field>
|
||||
<field name="urgency">normal</field>
|
||||
<field name="content">### 功能刀具寿命到期提醒:
|
||||
单号:拆解单[{{code}}]({{tool_expired_remind_special_url}})
|
||||
事项:{{functional_tool_id.tool_name_id.name}}寿命已到期,需拆解</field>
|
||||
</record>
|
||||
|
||||
<record id="template_tool_assembly_remind" model="jikimo.message.template">
|
||||
<!-- <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="name">功能刀具组装</field>
|
||||
<field name="model_id" ref="sf_tool_management.model_sf_functional_tool_assembly"/>
|
||||
<field name="model">sf.functional.tool.assembly</field>
|
||||
<field name="bussiness_node_id" ref="tool_assembly"/>
|
||||
<field name="msgtype">markdown</field>
|
||||
<field name="urgency">normal</field>
|
||||
<field name="content">### 功能刀具组装通知:
|
||||
单号:组装任务单[{{name}}]({{tool_assembly_special_url}})
|
||||
事项:{{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 id="template_quality_cnc_test" model="jikimo.message.template">
|
||||
<field name="name">待质量判定</field>
|
||||
<field name="model_id" ref="sf_quality.model_quality_cnc_test"/>
|
||||
<field name="model">quality.cnc.test</field>
|
||||
<field name="bussiness_node_id" ref="bussiness_quality_cnc_test"/>
|
||||
<field name="msgtype">markdown</field>
|
||||
<field name="urgency">normal</field>
|
||||
<field name="content">### 待质量判定提醒
|
||||
事项:共有[{{judge_num}}]({{url}})个工单需判定质量结果</field>
|
||||
</record>
|
||||
<record id="template_maintenance_logs" model="jikimo.message.template">
|
||||
<field name="name">设备故障</field>
|
||||
<field name="model_id" ref="sf_maintenance.model_sf_maintenance_logs"/>
|
||||
<field name="model">sf.maintenance.logs</field>
|
||||
<field name="bussiness_node_id" ref="bussiness_maintenance_logs"/>
|
||||
<field name="msgtype">markdown</field>
|
||||
<field name="urgency">urgent</field>
|
||||
<field name="content">### 设备故障及异常提醒:
|
||||
机台号:[{{maintenance_equipment_id.name}}]({{url}})
|
||||
事项:{{create_date}}故障报警</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -6,3 +6,7 @@ from . import sf_message_cam_program
|
||||
from . import sf_message_functional_tool_assembly
|
||||
from . import sf_message_purchase
|
||||
from . import sf_message_workorder
|
||||
from . import sf_message_functional_tool_dismantle
|
||||
from . import sf_message_mrp_production
|
||||
from . import sf_message_quality_cnc_test
|
||||
from . import sf_message_maintenance_logs
|
||||
|
||||
@@ -3,4 +3,4 @@ from odoo import models, fields, api, _
|
||||
|
||||
class SFMessageCamProgram(models.Model):
|
||||
_name = 'sf.cam.work.order.program.knife.plan'
|
||||
_inherit = ['sf.cam.work.order.program.knife.plan', 'sf.message.template']
|
||||
_inherit = ['sf.cam.work.order.program.knife.plan', 'jikimo.message.dispatch']
|
||||
|
||||
@@ -3,4 +3,24 @@ from odoo import models, fields, api, _
|
||||
|
||||
class SFMessagefunctionalToolAssembly(models.Model):
|
||||
_name = 'sf.functional.tool.assembly'
|
||||
_inherit = ['sf.functional.tool.assembly', 'sf.message.template']
|
||||
_description = "刀具组装单"
|
||||
_inherit = ['sf.functional.tool.assembly', 'jikimo.message.dispatch']
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals):
|
||||
result = super(SFMessagefunctionalToolAssembly, self).create(vals)
|
||||
for obj in result:
|
||||
if obj.loading_task_source == '0' and obj.assemble_status == '0':
|
||||
obj.add_queue('功能刀具组装')
|
||||
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)
|
||||
29
sf_message/models/sf_message_functional_tool_dismantle.py
Normal file
29
sf_message/models/sf_message_functional_tool_dismantle.py
Normal file
@@ -0,0 +1,29 @@
|
||||
from odoo import models, api
|
||||
|
||||
|
||||
class SFMessagefunctionalToolDismantle(models.Model):
|
||||
_name = 'sf.functional.tool.dismantle'
|
||||
_description = "刀具拆解单"
|
||||
_inherit = ['sf.functional.tool.dismantle', 'jikimo.message.dispatch']
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
# 判断是否为web页面创建请求
|
||||
is_web_request = self.env.context.get('is_web_request', False)
|
||||
result = super(SFMessagefunctionalToolDismantle, self).create(vals)
|
||||
if is_web_request:
|
||||
return result
|
||||
for obj in result:
|
||||
if obj.dismantle_cause in ['寿命到期报废', '崩刀报废'] and obj.state == '待拆解':
|
||||
obj.add_queue('功能刀具寿命到期')
|
||||
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)
|
||||
22
sf_message/models/sf_message_maintenance_logs.py
Normal file
22
sf_message/models/sf_message_maintenance_logs.py
Normal file
@@ -0,0 +1,22 @@
|
||||
from odoo import models, fields, api
|
||||
|
||||
class SFMessageMaintenanceLogs(models.Model):
|
||||
_name = 'sf.maintenance.logs'
|
||||
_inherit = ['sf.maintenance.logs', 'jikimo.message.dispatch']
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
res = super(SFMessageMaintenanceLogs, self).create(vals_list)
|
||||
for rec in res:
|
||||
rec.add_queue('设备故障')
|
||||
return res
|
||||
|
||||
def _get_message(self, message_queue_ids):
|
||||
contents = super(SFMessageMaintenanceLogs, self)._get_message(message_queue_ids)
|
||||
url = self.env['ir.config_parameter'].get_param('web.base.url')
|
||||
action_id = self.env.ref('sf_maintenance.action_maintenance_logs').id
|
||||
for index, content in enumerate(contents):
|
||||
maintenance_logs_id = self.env['sf.maintenance.logs'].browse(message_queue_ids[index].res_id)
|
||||
url = url + '/web#id=%s&view_type=form&action=%s' % (maintenance_logs_id.id, action_id)
|
||||
contents[index] = content.replace('{{url}}', url)
|
||||
return contents
|
||||
57
sf_message/models/sf_message_mrp_production.py
Normal file
57
sf_message/models/sf_message_mrp_production.py
Normal file
@@ -0,0 +1,57 @@
|
||||
import logging
|
||||
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(stock_picking_sfp.id)
|
||||
content = content.replace('{{name}}', stock_picking_sfp.name).replace(
|
||||
'{{sale_order_name}}', mrp_production.origin).replace('{{request_url}}', url)
|
||||
contents.append(content)
|
||||
logging.info('生产完工入库提醒: %s' % contents)
|
||||
return contents
|
||||
|
||||
def request_url(self, id):
|
||||
url = self.env['ir.config_parameter'].get_param('web.base.url')
|
||||
action_id = self.env.ref('stock.action_picking_tree_all').id
|
||||
menu_id = self.env['ir.model.data'].search([('name', '=', 'module_theme_treehouse')]).id
|
||||
# 查询参数
|
||||
params = {'id': id, 'menu_id': menu_id, 'action': action_id, 'model': 'mrp.production',
|
||||
'view_type': 'form'}
|
||||
# 拼接查询参数
|
||||
query_string = urlencode(params)
|
||||
# 拼接URL
|
||||
full_url = url + "/web#" + query_string
|
||||
return full_url
|
||||
@@ -1,6 +1,25 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from odoo import models, fields, api, _
|
||||
|
||||
|
||||
class SFMessagePlan(models.Model):
|
||||
_name = 'sf.production.plan'
|
||||
_inherit = ['sf.production.plan', 'sf.message.template']
|
||||
_inherit = ['sf.production.plan', 'jikimo.message.dispatch']
|
||||
|
||||
# def create(self, vals_list):
|
||||
# res = super(SFMessagePlan, self).create(vals_list)
|
||||
# if res:
|
||||
# try:
|
||||
# res.add_queue('待排程')
|
||||
# except Exception as e:
|
||||
# logging.info('add_queue error:%s' % e)
|
||||
# return res
|
||||
#
|
||||
# def _get_message(self):
|
||||
# res = super(SFMessagePlan, self)._get_message()
|
||||
# if res:
|
||||
# try:
|
||||
# res.add_queue('待排程')
|
||||
# except Exception as e:
|
||||
# logging.info('_get_message error:%s' % e)
|
||||
# return res
|
||||
|
||||
@@ -1,6 +1,33 @@
|
||||
from odoo import models, fields, api, _
|
||||
from urllib.parse import urlencode
|
||||
|
||||
|
||||
class SFMessagePurchase(models.Model):
|
||||
_name = 'purchase.order'
|
||||
_inherit = ['purchase.order', 'sf.message.template']
|
||||
_inherit = ['purchase.order', 'jikimo.message.dispatch']
|
||||
|
||||
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
|
||||
url = self.request_url(int(message_queue_id.res_id))
|
||||
purchase_order_line = self.env['purchase.order'].search([('id', '=', int(message_queue_id.res_id))])
|
||||
content = content.replace('{{name}}', purchase_order_line.name).replace(
|
||||
'{{request_url}}', url)
|
||||
contents.append(content)
|
||||
return contents
|
||||
|
||||
def request_url(self, id):
|
||||
url = self.env['ir.config_parameter'].get_param('web.base.url')
|
||||
action_id = self.env.ref('purchase.purchase_form_action').id
|
||||
menu_id = self.env['ir.model.data'].search([('name', '=', 'module_website_payment')]).id
|
||||
# 查询参数
|
||||
params = {'id': id, 'menu_id': menu_id, 'action': action_id,
|
||||
'model': 'purchase.order',
|
||||
'view_type': 'form'}
|
||||
# 拼接查询参数
|
||||
query_string = urlencode(params)
|
||||
# 拼接URL
|
||||
full_url = url + "/web#" + query_string
|
||||
return full_url
|
||||
|
||||
36
sf_message/models/sf_message_quality_cnc_test.py
Normal file
36
sf_message/models/sf_message_quality_cnc_test.py
Normal file
@@ -0,0 +1,36 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
from odoo import models, fields, api, _
|
||||
|
||||
|
||||
class SFMessageQualityCncTest(models.Model):
|
||||
_name = 'quality.cnc.test'
|
||||
_inherit = ['quality.cnc.test', 'jikimo.message.dispatch']
|
||||
|
||||
def create(self, vals_list):
|
||||
res = super(SFMessageQualityCncTest, self).create(vals_list)
|
||||
if res:
|
||||
try:
|
||||
logging.info('add_queue res:%s' % res)
|
||||
res.add_queue('待质量判定')
|
||||
except Exception as e:
|
||||
logging.info('add_queue error:%s' % e)
|
||||
return res
|
||||
|
||||
# 继承并重写jikimo.message.dispatch的_get_message()
|
||||
def _get_message(self, message_queue_ids):
|
||||
contents = []
|
||||
url = self.env['ir.config_parameter'].get_param('web.base.url')
|
||||
i = 0
|
||||
for item in message_queue_ids:
|
||||
if item.message_template_id.bussiness_node_id.name == '待质量判定':
|
||||
content = item.message_template_id.content
|
||||
i += 1
|
||||
if i >= 1:
|
||||
action_id = self.env.ref('sf_quality.action_quality_cnc_test').id
|
||||
url_with_id = f"{url}/web#view_type=list&action={action_id}"
|
||||
content_template = content.replace('{{judge_num}}', str(i))
|
||||
content_template = content_template.replace('{{url}}', url_with_id)
|
||||
contents.append(content_template)
|
||||
return contents
|
||||
@@ -1,11 +1,141 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
from odoo import models, fields, api, _
|
||||
|
||||
|
||||
class SFMessageSale(models.Model):
|
||||
_name = 'sale.order'
|
||||
_inherit = ['sale.order', 'sf.message.template']
|
||||
_inherit = ['sale.order', 'jikimo.message.dispatch']
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
res = super(SFMessageSale, self).create(vals_list)
|
||||
if res:
|
||||
try:
|
||||
logging.info('add_queue res:%s' % res)
|
||||
res.add_queue('待接单')
|
||||
except Exception as e:
|
||||
logging.info('add_queue error:%s' % e)
|
||||
return res
|
||||
|
||||
# 确认接单
|
||||
def action_confirm(self):
|
||||
res = super(SFMessageSale, self).action_confirm()
|
||||
if res is True:
|
||||
try:
|
||||
self.add_queue('确认接单')
|
||||
picking_ids = self.mrp_production_ids
|
||||
purchase_order_id = []
|
||||
if picking_ids:
|
||||
for picking_id in picking_ids:
|
||||
purchase_order_ids = (
|
||||
picking_id.procurement_group_id.stock_move_ids.created_purchase_line_id.order_id |
|
||||
picking_id.procurement_group_id.stock_move_ids.move_orig_ids.purchase_line_id.order_id).ids
|
||||
purchase_order_id.extend(purchase_order_ids)
|
||||
if purchase_order_id:
|
||||
purchase_order_list = self.env['purchase.order'].search([('id', 'in', purchase_order_id)])
|
||||
for purchase_order_info in purchase_order_list:
|
||||
purchase_order_info.add_queue('坯料采购提醒')
|
||||
except Exception as e:
|
||||
logging.info('add_queue error:%s' % e)
|
||||
return res
|
||||
|
||||
# 继承并重写jikimo.message.dispatch的_get_message()
|
||||
def _get_message(self, message_queue_ids):
|
||||
contents = []
|
||||
bussiness_node = None
|
||||
url = self.env['ir.config_parameter'].get_param('web.base.url')
|
||||
current_time_strf = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
current_time = self.env['sf.sync.common'].sudo().get_add_time(current_time_strf)
|
||||
current_time_datetime = datetime.strptime(current_time, '%Y-%m-%d %H:%M:%S')
|
||||
time_range = timedelta(minutes=2)
|
||||
i = 0
|
||||
for item in message_queue_ids:
|
||||
if item.message_template_id.bussiness_node_id.name == '待接单':
|
||||
content = super(SFMessageSale, self)._get_message(item)
|
||||
action_id = self.env.ref('sale.action_quotations_with_onboarding').id
|
||||
url_with_id = f"{url}/web#id={item.res_id}&view_type=form&action={action_id}"
|
||||
content = content[0].replace('{{url}}', url_with_id)
|
||||
contents.append(content)
|
||||
elif item.message_template_id.bussiness_node_id.name == '确认接单':
|
||||
content = super(SFMessageSale, self)._get_message(item)
|
||||
sale_order_line = self.env['sale.order.line'].search([('order_id', '=', int(item.res_id))])
|
||||
product = sale_order_line[0].product_id.name if len(sale_order_line) == 1 else '%s...' % \
|
||||
sale_order_line[
|
||||
0].product_id.name
|
||||
action_id = self.env.ref('sf_plan.sf_production_plan_action1').id
|
||||
url_with_id = f"{url}/web#view_type=list&action={action_id}"
|
||||
content = content[0].replace('{{product_id}}', product).replace('{{url}}', url_with_id)
|
||||
contents.append(content)
|
||||
elif item.message_template_id.bussiness_node_id.name in ['销售订单逾期预警', '销售订单已逾期']:
|
||||
bussiness_node = item.message_template_id.bussiness_node_id.name
|
||||
for reminder_time in item.message_template_id.reminder_time_ids:
|
||||
content = item.message_template_id.content
|
||||
target_time = datetime.combine(current_time_datetime.date(), datetime.min.time()).replace(
|
||||
hour=reminder_time.time_point,
|
||||
minute=0,
|
||||
second=0,
|
||||
microsecond=0
|
||||
)
|
||||
logging.info(current_time)
|
||||
logging.info(target_time)
|
||||
if target_time - time_range <= current_time_datetime <= target_time + time_range:
|
||||
search_condition = [
|
||||
('delivery_warning', '=', 'warning')] if bussiness_node == '销售订单逾期预警' else [
|
||||
('delivery_warning', '=', 'overdue')]
|
||||
record = self.sudo().search(search_condition + [('id', '=', int(item.res_id))])
|
||||
if record:
|
||||
i += 1
|
||||
if i >= 1:
|
||||
action_id = self.env.ref('sale.action_orders').id
|
||||
url_with_id = f"{url}/web#view_type=list&action={action_id}"
|
||||
content_template = content.replace('{{url}}', url_with_id)
|
||||
if bussiness_node == '销售订单逾期预警':
|
||||
content = content_template.replace('{{warning_num}}', str(i))
|
||||
elif bussiness_node == '销售订单已逾期':
|
||||
content = content_template.replace('{{overdue_num}}', str(i))
|
||||
contents.append(content)
|
||||
return contents
|
||||
|
||||
# # 销售订单逾期预警和已逾期
|
||||
def _overdue_or_warning_func(self):
|
||||
today = datetime.today().date()
|
||||
deadline_check = today + timedelta(days=1)
|
||||
logging.info(f"today: {today}, deadline_check: {deadline_check}")
|
||||
sale_order = self.sudo().search([('state', 'in', ['sale']), ('deadline_of_delivery', '!=', False)])
|
||||
for item in sale_order:
|
||||
production = self.env['mrp.production'].search([('origin', '=', item.name)])
|
||||
production_not_done = production.filtered(lambda p: p.state not in ['done', 'scrap', 'cancel'])
|
||||
production_done_count = len(production.filtered(lambda p: p.state in ['done', 'scrap', 'cancel']))
|
||||
if len(production_not_done) != item.mrp_production_count:
|
||||
if deadline_check == item.deadline_of_delivery:
|
||||
item.delivery_warning = 'warning'
|
||||
elif today == item.deadline_of_delivery:
|
||||
item.delivery_warning = 'overdue'
|
||||
elif production_done_count == item.mrp_production_count:
|
||||
if item.delivery_status in ['pending', 'partial']:
|
||||
if deadline_check == item.deadline_of_delivery:
|
||||
item.delivery_warning = 'warning'
|
||||
elif today == item.deadline_of_delivery:
|
||||
item.delivery_warning = 'overdue'
|
||||
else:
|
||||
continue
|
||||
overdue_orders = self.sudo().search([('delivery_warning', 'in', ['warning', 'overdue'])])
|
||||
for wo in overdue_orders:
|
||||
message_template = self.env["jikimo.message.template"].search([
|
||||
("model", "=", self._name),
|
||||
("bussiness_node_id", "=", self.env.ref('sf_message.bussiness_sale_order_overdue_warning').id)
|
||||
])
|
||||
sale_order_has = self.env['jikimo.message.queue'].search([
|
||||
('res_id', '=', wo.id),
|
||||
('message_status', '=', 'pending'),
|
||||
('message_template_id', '=', message_template.id)
|
||||
])
|
||||
if not sale_order_has:
|
||||
if wo.delivery_warning == 'warning':
|
||||
wo.add_queue('销售订单逾期预警')
|
||||
elif wo.delivery_warning == 'overdue':
|
||||
wo.add_queue('销售订单已逾期')
|
||||
|
||||
# def create(self):
|
||||
# res = super(SFMessageSale, self).create()
|
||||
# if res is True:
|
||||
|
||||
|
||||
@@ -1,6 +1,113 @@
|
||||
import logging
|
||||
import re
|
||||
from odoo import models, fields, api, _
|
||||
from urllib.parse import urlencode
|
||||
|
||||
|
||||
class SFMessageStockPicking(models.Model):
|
||||
_name = 'stock.picking'
|
||||
_inherit = ['stock.picking', 'sf.message.template']
|
||||
_description = "库存调拨"
|
||||
_inherit = ['stock.picking', 'jikimo.message.dispatch']
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals):
|
||||
result = super(SFMessageStockPicking, self).create(vals)
|
||||
for obj in result:
|
||||
if obj.location_id.name == '进货' and obj.location_dest_id.name == '刀具房':
|
||||
obj.add_queue('调拨入库')
|
||||
return result
|
||||
|
||||
@api.depends('move_type', 'immediate_transfer', 'move_ids.state', 'move_ids.picking_id')
|
||||
def _compute_state(self):
|
||||
super(SFMessageStockPicking, self)._compute_state()
|
||||
for record in self:
|
||||
if record.state == 'assigned' and record.check_in == 'PC':
|
||||
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_url1(stock_picking_out.id)
|
||||
content = content.replace('{{name}}', stock_picking_out.name).replace(
|
||||
'{{sale_order_name}}', stock_picking_out.origin).replace('{{request_url}}', url)
|
||||
logging.info('订单发货提醒: %s' % content)
|
||||
return content
|
||||
|
||||
def _get_message(self, message_queue_ids):
|
||||
contents = []
|
||||
product_id = []
|
||||
for message_queue_id in message_queue_ids:
|
||||
i = 0
|
||||
if message_queue_id.message_template_id.name == '坯料发料提醒':
|
||||
content = message_queue_id.message_template_id.content
|
||||
stock_picking_line = self.env['stock.picking'].search([('id', '=', int(message_queue_id.res_id))])
|
||||
mrp_production_info = self.env['mrp.production'].search(
|
||||
[('name', '=', stock_picking_line.origin)])
|
||||
mrp_production_list = self.env['mrp.production'].search(
|
||||
[('product_id', '=', mrp_production_info.product_id.id)])
|
||||
for mrp_production_line in mrp_production_list:
|
||||
picking_ids = mrp_production_line.picking_ids
|
||||
for picking_id in picking_ids:
|
||||
if picking_id.state == 'assigned' and picking_id.check_in == 'PC':
|
||||
i += 1
|
||||
if i > 0 and mrp_production_info.product_id.id not in product_id:
|
||||
url = self.request_url()
|
||||
content = content.replace('{{product_id}}', mrp_production_info.product_id.name).replace(
|
||||
'{{number}}', str(i)).replace('{{request_url}}', url)
|
||||
product_id.append(mrp_production_info.product_id.id)
|
||||
contents.append(content)
|
||||
elif message_queue_id.message_template_id.name == '订单发货提醒':
|
||||
content = self.deal_stock_picking_sfp(message_queue_id)
|
||||
if content:
|
||||
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):
|
||||
url = self.env['ir.config_parameter'].get_param('web.base.url')
|
||||
action_id = self.env.ref('stock.stock_picking_type_action').id
|
||||
menu_id = self.env['ir.model.data'].search([('name', '=', 'module_theme_treehouse')]).id
|
||||
# 查询参数
|
||||
params = {'menu_id': menu_id, 'action': action_id, 'model': 'stock.picking',
|
||||
'view_type': 'kanban'}
|
||||
# 拼接查询参数
|
||||
query_string = urlencode(params)
|
||||
# 拼接URL
|
||||
full_url = url + "/web#" + query_string
|
||||
return full_url
|
||||
|
||||
def request_url1(self, id):
|
||||
url = self.env['ir.config_parameter'].get_param('web.base.url')
|
||||
action_id = self.env.ref('stock.action_picking_tree_all').id
|
||||
menu_id = self.env['ir.model.data'].search([('name', '=', 'module_theme_treehouse')]).id
|
||||
# 查询参数
|
||||
params = {'id': id, 'menu_id': menu_id, 'action': action_id, 'model': 'stock.picking',
|
||||
'view_type': 'form'}
|
||||
# 拼接查询参数
|
||||
query_string = urlencode(params)
|
||||
# 拼接URL
|
||||
full_url = url + "/web#" + query_string
|
||||
return full_url
|
||||
|
||||
@@ -4,31 +4,16 @@ from abc import ABC, abstractmethod
|
||||
|
||||
|
||||
class SfMessageTemplate(models.Model):
|
||||
_name = "sf.message.template"
|
||||
_description = u'消息模板'
|
||||
_inherit = "jikimo.message.template"
|
||||
|
||||
name = fields.Char(string=u"名称", required=True)
|
||||
description = fields.Char(string=u"描述")
|
||||
content = fields.Html(string=u"内容", render_engine='qweb', translate=True, prefetch=True, sanitize=False)
|
||||
msgtype = fields.Selection(
|
||||
[('text', u'文字'), ('markdown', u'Markdown')], u'消息类型',
|
||||
required=True, default='markdown')
|
||||
notification_department_id = fields.Many2one('hr.department', u'通知部门', required=True)
|
||||
notification_employee_ids = fields.Many2many('hr.employee', string=u'员工',
|
||||
domain="[('department_id', '=',notification_department_id)]",
|
||||
required=True)
|
||||
is_send_time = fields.Boolean(string=u"定时发送", default=False)
|
||||
send_time_1 = fields.Integer('发送时间点1')
|
||||
send_time_2 = fields.Integer('发送时间点2')
|
||||
active = fields.Boolean(string=u"是否有效", default=True)
|
||||
|
||||
@api.onchange('notification_department_id')
|
||||
def _clear_employee_ids(self):
|
||||
if self.notification_department_id:
|
||||
self.notification_employee_ids = False
|
||||
|
||||
@abstractmethod
|
||||
def dispatch(self, args):
|
||||
"""
|
||||
强迫继承该类必走该抽象方法'
|
||||
"""
|
||||
def _get_message_model(self):
|
||||
res = super(SfMessageTemplate, self)._get_message_model()
|
||||
res.append("sale.order")
|
||||
res.append("stock.picking")
|
||||
res.append('sf.functional.tool.assembly')
|
||||
res.append('sf.functional.tool.dismantle')
|
||||
res.append('purchase.order')
|
||||
res.append('mrp.workorder')
|
||||
res.append('sf.maintenance.logs')
|
||||
res.append('quality.cnc.test')
|
||||
return res
|
||||
|
||||
@@ -1,6 +1,158 @@
|
||||
from datetime import datetime, timedelta
|
||||
from odoo import models, fields, api, _
|
||||
import logging, json
|
||||
import requests
|
||||
from odoo.addons.sf_base.commons.common import Common
|
||||
from urllib.parse import urlencode
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SFMessageWork(models.Model):
|
||||
_name = 'mrp.workorder'
|
||||
_inherit = ['mrp.workorder', 'sf.message.template']
|
||||
_inherit = ['mrp.workorder', 'jikimo.message.dispatch']
|
||||
|
||||
@api.depends('production_availability', 'blocked_by_workorder_ids.state', 'production_id.tool_state')
|
||||
def _compute_state(self):
|
||||
super(SFMessageWork, self)._compute_state()
|
||||
for workorder in self:
|
||||
if workorder.state == 'ready' and workorder.routing_type == '装夹预调':
|
||||
jikimo_message_queue = self.env['jikimo.message.queue'].sudo().search(
|
||||
[('res_id', '=', workorder.id), ("message_status", "=", "pending")])
|
||||
if not jikimo_message_queue:
|
||||
workorder.add_queue('工单已下发通知')
|
||||
|
||||
def _get_message(self, message_queue_ids):
|
||||
contents = []
|
||||
product_id = []
|
||||
bussiness_node = None
|
||||
url = self.env['ir.config_parameter'].get_param('web.base.url')
|
||||
current_time_strf = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
current_time = self.env['sf.sync.common'].sudo().get_add_time(current_time_strf)
|
||||
current_time_datetime = datetime.strptime(current_time, '%Y-%m-%d %H:%M:%S')
|
||||
time_range = timedelta(minutes=2)
|
||||
template_names = {
|
||||
'预警': ['装夹预调工单逾期预警', 'CNC加工工单逾期预警', '解除装夹工单逾期预警', '表面工艺工单逾期预警'],
|
||||
'已逾期': ['装夹预调工单已逾期', 'CNC加工工单已逾期', '解除装夹工单已逾期', '表面工艺工单已逾期']
|
||||
}
|
||||
i = 0
|
||||
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_workorder_line = self.env['mrp.workorder'].search([('id', '=', int(message_queue_id.res_id))])
|
||||
mrp_workorder_list = self.env['mrp.workorder'].search(
|
||||
[('product_id', '=', mrp_workorder_line.product_id.id), ('state', '=', 'ready'),
|
||||
('routing_type', '=', '装夹预调')])
|
||||
if len(mrp_workorder_list) > 0 and mrp_workorder_line.product_id.id not in product_id:
|
||||
url = self.request_url()
|
||||
content = content.replace('{{product_id}}', mrp_workorder_line.product_id.name).replace(
|
||||
'{{number}}', str(len(mrp_workorder_list))).replace(
|
||||
'{{request_url}}', url)
|
||||
product_id.append(mrp_workorder_line.product_id.id)
|
||||
contents.append(content)
|
||||
elif message_queue_id.message_template_id.name in template_names['预警'] + template_names['已逾期']:
|
||||
item = message_queue_id.message_template_id
|
||||
bussiness_node = item.bussiness_node_id.name
|
||||
for reminder_time in item.reminder_time_ids:
|
||||
content = item.content
|
||||
target_time = datetime.combine(current_time_datetime.date(), datetime.min.time()).replace(
|
||||
hour=reminder_time.time_point,
|
||||
minute=0,
|
||||
second=0,
|
||||
microsecond=0
|
||||
)
|
||||
logging.info(current_time)
|
||||
logging.info(target_time)
|
||||
logging.info(target_time - time_range)
|
||||
logging.info(target_time + time_range)
|
||||
if target_time - time_range <= current_time_datetime <= target_time + time_range:
|
||||
search_condition = [
|
||||
('delivery_warning', '=', 'warning')] if bussiness_node in template_names['预警'] else [
|
||||
('delivery_warning', '=', 'overdue')]
|
||||
record = self.sudo().search(search_condition + [('id', '=', int(item.res_id))])
|
||||
if record:
|
||||
i += 1
|
||||
if i >= 1:
|
||||
action_id = self.env.ref('sf_manufacturing.mrp_workorder_action_tablet').id
|
||||
url_with_id = f"{url}/web#view_type=list&action={action_id}"
|
||||
content_template = content.replace('{{url}}', url_with_id)
|
||||
if bussiness_node in template_names['预警']:
|
||||
content = content_template.replace('{{warning_num}}', str(i))
|
||||
elif bussiness_node in template_names['已逾期']:
|
||||
content = content_template.replace('{{overdue_num}}', str(i))
|
||||
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('sf_manufacturing.mrp_workorder_action_tablet').id
|
||||
menu_id = self.env['ir.model.data'].search([('name', '=', 'module_stock_dropshipping')]).id
|
||||
# 查询参数
|
||||
params = {'menu_id': menu_id, 'action': action_id, 'model': 'mrp.workorder',
|
||||
'view_type': 'list', 'active_id': 1}
|
||||
# 拼接查询参数
|
||||
query_string = urlencode(params)
|
||||
# 拼接URL
|
||||
full_url = url + "/web#" + query_string
|
||||
return full_url
|
||||
|
||||
def _overdue_or_warning_func(self):
|
||||
workorders = self.env['mrp.workorder'].search([("state", "in", ["ready", "progress", "to be detected"])])
|
||||
grouped_workorders = {}
|
||||
for workorder in workorders:
|
||||
routing_type = workorder.routing_type
|
||||
if routing_type not in grouped_workorders:
|
||||
grouped_workorders[routing_type] = []
|
||||
grouped_workorders[routing_type].append(workorder)
|
||||
for routing_type, orders in grouped_workorders.items():
|
||||
print(f"Routing Type: {routing_type}, Orders: {len(orders)}")
|
||||
for item in orders:
|
||||
if item.date_planned_finished:
|
||||
current_time_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
current_time = self.env['sf.sync.common'].sudo().get_add_time(current_time_str)
|
||||
current_time_datetime = datetime.strptime(current_time, '%Y-%m-%d %H:%M:%S')
|
||||
date_planned_finished_str = self.env['sf.sync.common'].sudo().get_add_time(
|
||||
item.date_planned_finished.strftime("%Y-%m-%d %H:%M:%S"))
|
||||
date_planned_finished = datetime.strptime(date_planned_finished_str, '%Y-%m-%d %H:%M:%S')
|
||||
twelve_hours_ago = current_time_datetime - timedelta(hours=12)
|
||||
if current_time_datetime >= date_planned_finished:
|
||||
logging.info("------overdue-------")
|
||||
logging.info(f"Workorder: {item.production_id.name}, Current Time: {current_time_datetime}, "
|
||||
f"Planned Finish: {date_planned_finished}")
|
||||
item.delivery_warning = 'overdue'
|
||||
elif twelve_hours_ago <= current_time_datetime <= date_planned_finished:
|
||||
logging.info("------warning-------")
|
||||
logging.info(f"Workorder: {item.production_id.name}, Current Time: {current_time_datetime}, "
|
||||
f"Planned Finish: {date_planned_finished}")
|
||||
item.delivery_warning = 'warning'
|
||||
business_node_ids = {
|
||||
'装夹预调': self.env.ref('sf_message.bussiness_mrp_workorder_pre_overdue_warning').id,
|
||||
'CNC加工': self.env.ref('sf_message.bussiness_mrp_workorder_cnc_overdue_warning').id,
|
||||
'解除装夹': self.env.ref('sf_message.bussiness_mrp_workorder_unclamp_overdue_warning').id,
|
||||
'表面工艺': self.env.ref('sf_message.bussiness_mrp_workorder_surface_overdue_warning').id,
|
||||
}
|
||||
message_templates = {key: self.env["jikimo.message.template"].sudo().search([
|
||||
("model", "=", self._name),
|
||||
("bussiness_node_id", "=", business_node_ids[key])
|
||||
]) for key in business_node_ids}
|
||||
for item in orders:
|
||||
if item.delivery_warning in ['overdue', 'warning']:
|
||||
bussiness_node_id = business_node_ids.get(item.routing_type)
|
||||
if bussiness_node_id and message_templates[item.routing_type]:
|
||||
message_queue_ids = self.env["jikimo.message.queue"].sudo().search([
|
||||
("message_template_id", "=", message_templates[item.routing_type].id),
|
||||
("message_status", "=", "pending"),
|
||||
("res_id", "=", item.id)
|
||||
])
|
||||
if not message_queue_ids:
|
||||
overdue_message = '工单已逾期' if item.delivery_warning == 'overdue' else '工单逾期预警'
|
||||
queue_method_name = f'add_queue'
|
||||
# 构建参数列表,其中包含item.routing_type和overdue_message
|
||||
args = [f'{item.routing_type}{overdue_message}']
|
||||
# 获取add_queue方法并调用它,传入参数列表
|
||||
getattr(item, queue_method_name)(*args)
|
||||
|
||||
def _recover_time_warning_func(self):
|
||||
workorder_done = self.env['mrp.workorder'].search([("state", "in", ["done", "rework", "cancel"])])
|
||||
workorder_overdue = workorder_done.filtered(lambda x: x.delivery_warning in ['overdue', 'warning'])
|
||||
workorder_overdue.write({'delivery_warning': 'normal'})
|
||||
|
||||
@@ -1,9 +1,29 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_sf_message_template_group_sale_salemanager,sf_message_template,model_sf_message_template,sf_base.group_sale_salemanager,1,1,1,0
|
||||
access_sf_message_template_group_purchase,sf_message_template,model_sf_message_template,sf_base.group_purchase,1,1,1,0
|
||||
access_sf_message_template_group_sf_stock_user,sf_message_template,model_sf_message_template,sf_base.group_sf_stock_user,1,1,1,0
|
||||
access_sf_message_template_group_sf_order_user,sf_message_template,model_sf_message_template,sf_base.group_sf_order_user,1,1,1,0
|
||||
access_sf_message_template_group_sf_tool_user,sf_message_template,model_sf_message_template,sf_base.group_sf_tool_user,1,1,1,0
|
||||
|
||||
access_jikimo_message_template_group_sale_salemanager,jikimo_message_template,jikimo_message_notify.model_jikimo_message_template,sf_base.group_sale_salemanager,1,1,1,0
|
||||
access_jikimo_message_template_group_purchase,jikimo_message_template,jikimo_message_notify.model_jikimo_message_template,sf_base.group_purchase,1,1,1,0
|
||||
access_jikimo_message_template_group_sf_stock_user,jikimo_message_template,jikimo_message_notify.model_jikimo_message_template,sf_base.group_sf_stock_user,1,1,1,0
|
||||
access_jikimo_message_template_group_sf_order_user,jikimo_message_template,jikimo_message_notify.model_jikimo_message_template,sf_base.group_sf_order_user,1,1,1,0
|
||||
access_jikimo_message_template_group_sf_tool_user,jikimo_message_template,jikimo_message_notify.model_jikimo_message_template,sf_base.group_sf_tool_user,1,1,1,0
|
||||
|
||||
access_jikimo_message_bussiness_node_group_sale_salemanager,jikimo_message_bussiness_node,jikimo_message_notify.model_jikimo_message_bussiness_node,sf_base.group_sale_salemanager,1,1,1,0
|
||||
access_jikimo_message_bussiness_node_group_purchase,jikimo_message_bussiness_node,jikimo_message_notify.model_jikimo_message_bussiness_node,sf_base.group_purchase,1,1,1,0
|
||||
access_jikimo_message_bussiness_node_group_sf_stock_user,jikimo_message_bussiness_node,jikimo_message_notify.model_jikimo_message_bussiness_node,sf_base.group_sf_stock_user,1,1,1,0
|
||||
access_jikimo_message_bussiness_node_group_sf_order_user,jikimo_message_bussiness_node,jikimo_message_notify.model_jikimo_message_bussiness_node,sf_base.group_sf_order_user,1,1,1,0
|
||||
access_jikimo_message_bussiness_node_group_sf_tool_user,jikimo_message_bussiness_node,jikimo_message_notify.model_jikimo_message_bussiness_node,sf_base.group_sf_tool_user,1,1,1,0
|
||||
|
||||
access_jikimo_message_queue_group_sale_salemanager,jikimo_message_queue,jikimo_message_notify.model_jikimo_message_queue,sf_base.group_sale_salemanager,1,1,1,0
|
||||
access_jikimo_message_queue_group_purchase,jikimo_message_queue,jikimo_message_notify.model_jikimo_message_queue,sf_base.group_purchase,1,1,1,0
|
||||
access_jikimo_message_queue_group_sf_stock_user,jikimo_message_queue,jikimo_message_notify.model_jikimo_message_queue,sf_base.group_sf_stock_user,1,1,1,0
|
||||
access_jikimo_message_queue_group_sf_order_user,jikimo_message_queue,jikimo_message_notify.model_jikimo_message_queue,sf_base.group_sf_order_user,1,1,1,0
|
||||
access_jikimo_message_queue_group_sf_tool_user,jikimo_message_queue,jikimo_message_notify.model_jikimo_message_queue,sf_base.group_sf_tool_user,1,1,1,0
|
||||
|
||||
access_jikimo_message_reminder_time_group_sale_salemanager,jikimo_message_reminder_time,jikimo_message_notify.model_jikimo_message_reminder_time,sf_base.group_sale_salemanager,1,1,1,0
|
||||
access_jikimo_message_reminder_time_group_purchase,jikimo_message_reminder_time,jikimo_message_notify.model_jikimo_message_reminder_time,sf_base.group_purchase,1,1,1,0
|
||||
access_jikimo_message_reminder_time_group_sf_stock_user,jikimo_message_reminder_time,jikimo_message_notify.model_jikimo_message_reminder_time,sf_base.group_sf_stock_user,1,1,1,0
|
||||
access_jikimo_message_reminder_time_group_sf_order_user,jikimo_message_reminder_time,jikimo_message_notify.model_jikimo_message_reminder_time,sf_base.group_sf_order_user,1,1,1,0
|
||||
access_jikimo_message_reminder_time_group_sf_tool_user,jikimo_message_reminder_time,jikimo_message_notify.model_jikimo_message_reminder_time,sf_base.group_sf_tool_user,1,1,1,0
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
10
sf_message/views/sf_message_sale_view.xml
Normal file
10
sf_message/views/sf_message_sale_view.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- © <2016> <top hy>
|
||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -->
|
||||
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -1,81 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- © <2016> <top hy>
|
||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -->
|
||||
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
<record id="sf_message_template_view_form" model="ir.ui.view">
|
||||
<field name="name">sf.message.template.view.form</field>
|
||||
<field name="model">sf.message.template</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="消息模板">
|
||||
<sheet>
|
||||
<div class="oe_title">
|
||||
<label for="name"/>
|
||||
<h1>
|
||||
<field name="name" class="w-100" required="1"/>
|
||||
</h1>
|
||||
</div>
|
||||
<group>
|
||||
<!-- <field name="type"/>-->
|
||||
<field name="content" widget="html" class="oe-bordered-editor"
|
||||
options="{'style-inline': true, 'codeview': true, 'dynamic_placeholder': true}"/>
|
||||
<field name="description"/>
|
||||
<field name="msgtype"/>
|
||||
<field name="is_send_time"/>
|
||||
<field name="send_time_1" attrs="{'invisible': [('is_send_time', '=', False)]}"/>
|
||||
<field name="send_time_2" attrs="{'invisible': [('is_send_time', '=', False)]}"/>
|
||||
<field name="notification_department_id"/>
|
||||
<field name="notification_employee_ids" widget="many2many_tags"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="sf_message_template_view_tree" model="ir.ui.view">
|
||||
<field name="name">sf.message.template.view.tree</field>
|
||||
<field name="model">sf.message.template</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="消息模板">
|
||||
<field name="name"/>
|
||||
<!-- <field name="type"/>-->
|
||||
<field name="content"/>
|
||||
<field name="msgtype"/>
|
||||
<field name="is_send_time"/>
|
||||
<field name="send_time_1" attrs="{'invisible': [('is_send_time', '=', False)]}"/>
|
||||
<field name="send_time_2" attrs="{'invisible': [('is_send_time', '=', False)]}"/>
|
||||
<field name="notification_department_id"/>
|
||||
<field name="notification_employee_ids" widget="many2many_tags"/>
|
||||
<field name="description"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="sf_message_template_search_view" model="ir.ui.view">
|
||||
<field name="name">sf.message.template.search.view</field>
|
||||
<field name="model">sf.message.template</field>
|
||||
<field name="arch" type="xml">
|
||||
<search>
|
||||
<field name="name" string="模糊搜索"
|
||||
filter_domain="['|','|',('name','like',self),('description','like',self)]"/>
|
||||
<field name="name"/>
|
||||
<filter name="filter_active" string="已归档" domain="[('active','=',False)]"/>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!--定义单证类型视图动作-->
|
||||
<record id="sf_message_template_action" model="ir.actions.act_window">
|
||||
<field name="name">消息模板</field>
|
||||
<field name="res_model">sf.message.template</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="view_id" ref="sf_message_template_view_tree"/>
|
||||
</record>
|
||||
|
||||
<menuitem id="msg_set_menu" name="消息设置" parent="base.menu_administration" sequence="1"/>
|
||||
<menuitem id="sf_message_template_send_menu" name="消息模板" parent="msg_set_menu"
|
||||
action="sf_message_template_action" sequence="1"/>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -3,11 +3,12 @@ import logging
|
||||
import os
|
||||
import json
|
||||
import base64
|
||||
from odoo import http
|
||||
from odoo import http, fields, models
|
||||
from odoo.http import request
|
||||
from odoo.addons.sf_base.controllers.controllers import MultiInheritController
|
||||
|
||||
|
||||
class Sf_Mrs_Connect(http.Controller):
|
||||
class Sf_Mrs_Connect(http.Controller, MultiInheritController):
|
||||
|
||||
@http.route('/api/cnc_processing/create', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False,
|
||||
cors="*")
|
||||
@@ -25,7 +26,7 @@ class Sf_Mrs_Connect(http.Controller):
|
||||
ret = json.loads(ret['result'])
|
||||
logging.info('下发编程单:%s' % ret)
|
||||
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'])]
|
||||
productions = request.env['mrp.production'].with_user(
|
||||
request.env.ref("base.user_admin")).search(domain)
|
||||
@@ -62,8 +63,8 @@ class Sf_Mrs_Connect(http.Controller):
|
||||
if cnc_workorder_has.cnc_ids:
|
||||
cnc_workorder_has.cmm_ids.sudo().unlink()
|
||||
cnc_workorder_has.cnc_ids.sudo().unlink()
|
||||
request.env['sf.cam.work.order.program.knife.plan'].sudo().unlink_cam_plan(
|
||||
production)
|
||||
# request.env['sf.cam.work.order.program.knife.plan'].sudo().unlink_cam_plan(
|
||||
# production)
|
||||
cnc_workorder_has.write(
|
||||
{'cnc_ids': cnc_workorder_has.cnc_ids.sudo()._json_cnc_processing(panel, ret),
|
||||
'cmm_ids': cnc_workorder_has.cmm_ids.sudo()._json_cmm_program(panel, ret)})
|
||||
@@ -81,23 +82,40 @@ class Sf_Mrs_Connect(http.Controller):
|
||||
if files_panel:
|
||||
for file in files_panel:
|
||||
file_extension = os.path.splitext(file)[1]
|
||||
logging.info('file_extension:%s' % file_extension)
|
||||
if file_extension.lower() == '.pdf':
|
||||
panel_file_path = os.path.join(program_path_tmp_panel, file)
|
||||
logging.info('panel_file_path:%s' % panel_file_path)
|
||||
logging.info('更新工作指令:%s' % cnc_workorder)
|
||||
cnc_workorder.write({'cnc_worksheet': base64.b64encode(open(panel_file_path, 'rb').read())})
|
||||
logging.info('更新工作指令完成:%s' % cnc_workorder)
|
||||
pre_workorder = productions.workorder_ids.filtered(
|
||||
lambda ap: ap.routing_type == '装夹预调' and ap.state not in ['done', 'rework'
|
||||
'cancel'] and ap.processing_panel == panel)
|
||||
if pre_workorder:
|
||||
logging.info('更新加工图纸:%s' % pre_workorder)
|
||||
pre_workorder.write(
|
||||
{'processing_drawing': base64.b64encode(open(panel_file_path, 'rb').read())})
|
||||
logging.info('更新加工图纸完成:%s' % pre_workorder)
|
||||
productions.write({'programming_state': '已编程', 'work_state': '已编程'})
|
||||
logging.info('已更新制造订单编程状态:%s' % productions.ids)
|
||||
res.update({
|
||||
'production_ids': productions.ids
|
||||
})
|
||||
|
||||
# 对制造订单所以面的cnc工单的程序用刀进行校验
|
||||
try:
|
||||
logging.info(f'已更新制造订单:{productions}')
|
||||
productions.production_cnc_tool_checkout()
|
||||
except Exception as e:
|
||||
logging.info(f'对cnc工单的程序用刀进行校验报错:{e}')
|
||||
return json.JSONEncoder().encode(res)
|
||||
|
||||
return json.JSONEncoder().encode(res)
|
||||
else:
|
||||
res = {'status': 0, 'message': '该制造订单暂未开始'}
|
||||
return json.JSONEncoder().encode(res)
|
||||
except Exception as e:
|
||||
res = {'status': -1, 'message': '系统解析失败'}
|
||||
request.cr.rollback()
|
||||
logging.info('get_cnc_processing_create error:%s' % e)
|
||||
return json.JSONEncoder().encode(res)
|
||||
|
||||
@@ -18,7 +18,7 @@ class OrderPrice(models.Model):
|
||||
return True
|
||||
except ValueError:
|
||||
return False
|
||||
@api.depends('sale_order_id.remark')
|
||||
@api.depends('sale_order_id.order_line.remark')
|
||||
def _compute_bfm_amount_total(self):
|
||||
for record in self:
|
||||
amount_total = 0
|
||||
|
||||
@@ -175,7 +175,7 @@ class ResConfigSettings(models.TransientModel):
|
||||
new_price = res_order_lines_map.get(str(index))
|
||||
if order_line:
|
||||
# 修改单价
|
||||
order_line.write({'remark': new_price*order_line.product_uom_qty})
|
||||
order_line.write({'remark': round(new_price*order_line.product_uom_qty,2)})
|
||||
order_price = self.env['order.price'].sudo().search([('sale_order_id', '=',need_change_sale_order.id )])
|
||||
if not order_price:
|
||||
self.env['order.price'].sudo().create({'sale_order_id':need_change_sale_order.id})
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user