Compare commits
667 Commits
master_sf_
...
feature/ma
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
906a0ea767 | ||
|
|
06eaebd098 | ||
|
|
8578a9a538 | ||
|
|
b9039ef466 | ||
|
|
2fe8e0e356 | ||
|
|
bbe2e5792f | ||
|
|
69f3453134 | ||
|
|
516a98e102 | ||
|
|
390213881a | ||
|
|
e264306fed | ||
|
|
f557cb8d10 | ||
|
|
cf34e28e35 | ||
|
|
b7595fe4ec | ||
|
|
2551d82a99 | ||
|
|
0cedba8ea4 | ||
|
|
d64a814251 | ||
|
|
86d5de4fa1 | ||
|
|
ed15958319 | ||
|
|
0bf9c752f6 | ||
|
|
e62eb060fb | ||
|
|
563b44adb1 | ||
|
|
a094946428 | ||
|
|
d28fd5d787 | ||
|
|
41c357d124 | ||
|
|
f94d52a4d9 | ||
|
|
74dc31b083 | ||
|
|
3b1fca529a | ||
|
|
843eeb8a89 | ||
|
|
12e81ec702 | ||
|
|
fe2af8a4ce | ||
|
|
f337a30aba | ||
|
|
7f3a8d877f | ||
|
|
5e6a7d0a39 | ||
|
|
5788a157c5 | ||
|
|
0b70447333 | ||
|
|
de8ba77bef | ||
|
|
1026c0edd0 | ||
|
|
f366b650d0 | ||
|
|
6054ce39f3 | ||
|
|
e1e9dc425b | ||
|
|
3a2a942f3b | ||
|
|
bf9d848f10 | ||
|
|
add7d404a0 | ||
|
|
7e6fa7b6c7 | ||
|
|
3fd17302f5 | ||
|
|
db9cee3e28 | ||
|
|
97a26dfa06 | ||
|
|
cebd895811 | ||
|
|
bb6c208d37 | ||
|
|
921f2c3503 | ||
|
|
1224e0e094 | ||
|
|
a571a870d3 | ||
|
|
a28d20b3bf | ||
|
|
d4e0d36166 | ||
|
|
39579aef10 | ||
|
|
1fe8fd6933 | ||
|
|
3b1caca62b | ||
|
|
3fe8c957be | ||
|
|
98923c1797 | ||
|
|
8661dd16c1 | ||
|
|
7157ea1587 | ||
|
|
a580a12d69 | ||
|
|
0d512e2e43 | ||
|
|
103069c554 | ||
|
|
f2482b5128 | ||
|
|
da38d24111 | ||
|
|
1f0ebe71c7 | ||
|
|
f897955f68 | ||
|
|
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 | ||
|
|
00a04af94f | ||
|
|
33d23c28b1 | ||
|
|
571fb29482 | ||
|
|
1dae20e055 | ||
|
|
6be9d44572 | ||
|
|
8a459e7e90 | ||
|
|
4c486e53aa | ||
|
|
19509a3ce2 | ||
|
|
4d424e90d3 | ||
|
|
9bbf613bcb | ||
|
|
cc40d479a9 | ||
|
|
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 | ||
|
|
4a22071f94 | ||
|
|
ec2075c874 | ||
|
|
ebc9716019 | ||
|
|
d067c5b8c4 | ||
|
|
72c9c9872e | ||
|
|
2b541fe041 | ||
|
|
f6e371f223 | ||
|
|
0aefe9e656 | ||
|
|
4f8f29e41a | ||
|
|
ffb32c7ce2 | ||
|
|
3cacca80e9 | ||
|
|
a3356fe195 | ||
|
|
3c70e1623e | ||
|
|
51628c081a | ||
|
|
18ebe6bcb4 | ||
|
|
63ac4f2b44 | ||
|
|
1b6ca170c0 | ||
|
|
2966260f51 | ||
|
|
c4069995e0 | ||
|
|
54920982a2 | ||
|
|
f947a3ed58 | ||
|
|
5e86d67316 | ||
|
|
7354a19696 | ||
|
|
78e3d77000 | ||
|
|
47ad2410cf | ||
|
|
fa2eb7b3cf | ||
|
|
0cee6ebd77 | ||
|
|
73f161ffa4 | ||
|
|
ff06d5c3be | ||
|
|
78cb177ac5 | ||
|
|
9601502360 | ||
|
|
739b7e600c | ||
|
|
27da7639b2 | ||
|
|
258e3bdb9f | ||
|
|
7f3e9927d5 | ||
|
|
927f726030 | ||
|
|
7576200fc4 | ||
|
|
4ffb9d636f | ||
|
|
24f0547343 | ||
|
|
402a323673 | ||
|
|
5e1685417f | ||
|
|
9dfe34ce9a | ||
|
|
190d6da217 | ||
|
|
8946b44280 | ||
|
|
f21d085462 | ||
|
|
43f53197c4 | ||
|
|
ac43be1262 | ||
|
|
8bf1c56efd | ||
|
|
d66791dc41 | ||
|
|
ce6b36a77e | ||
|
|
4acae01009 | ||
|
|
213b76a280 | ||
|
|
c675e1d67c | ||
|
|
8a9f6ab34f | ||
|
|
8aca6ce084 | ||
|
|
cb0c093006 | ||
|
|
1e7d7008b4 | ||
|
|
ef453dbc1e | ||
|
|
9f1e635c8d | ||
|
|
92037f3f04 | ||
|
|
4ba0566d27 | ||
|
|
5d870d53f6 | ||
|
|
6a674cdb5b | ||
|
|
fbce132173 | ||
|
|
98e13cba99 | ||
|
|
7ce2f6c797 | ||
|
|
7502792438 | ||
|
|
9efb13cf01 | ||
|
|
b14e263a2e | ||
|
|
6f41ef3047 | ||
|
|
de29f0c938 | ||
|
|
589a24f595 | ||
|
|
85186f94f5 | ||
|
|
ffc363a31c | ||
|
|
a047ad3bb6 | ||
|
|
cc3289c834 | ||
|
|
e1093a3c69 | ||
|
|
ba955ca658 | ||
|
|
946abc5cf7 | ||
|
|
57ea9ed8f4 | ||
|
|
f8a12b1fe3 | ||
|
|
722f6257ae | ||
|
|
ccbe311c58 | ||
|
|
0f6ca0876c | ||
|
|
bb60fd0339 | ||
|
|
eefbd5939b | ||
|
|
7c01196f17 | ||
|
|
4c58f4d7f3 | ||
|
|
c741587341 | ||
|
|
6ad2fa80f1 | ||
|
|
55694ae303 | ||
|
|
8423b12119 | ||
|
|
5a7ba7f87e | ||
|
|
d36da3df24 | ||
|
|
302a5f0cf1 | ||
|
|
b4fef421dd | ||
|
|
5f5e991d33 | ||
|
|
43a49242c5 | ||
|
|
2e5694cd1a | ||
|
|
3c123a07ea | ||
|
|
dec5ddd154 | ||
|
|
22864b9669 | ||
|
|
528fa483a2 | ||
|
|
b5a3815f1f | ||
|
|
1719e0394e | ||
|
|
eae902ee92 | ||
|
|
01c57a8691 | ||
|
|
2808005ce9 | ||
|
|
06c5f65df2 | ||
|
|
e0bad6ed40 | ||
|
|
35c8b9316e | ||
|
|
2ddfbb0dde | ||
|
|
988c4a460d | ||
|
|
c050e2f11a | ||
|
|
95e49c7b0f | ||
|
|
e46a13889e | ||
|
|
335151f6ca | ||
|
|
908e4a3df6 | ||
|
|
6b403f2c1e | ||
|
|
da5e322229 | ||
|
|
4f9ac16a07 | ||
|
|
4342844845 | ||
|
|
8bc68e1edd | ||
|
|
763d64a1ca | ||
|
|
8a10d18f37 | ||
|
|
181f490d47 | ||
|
|
322718d19a | ||
|
|
96eeb1cab7 | ||
|
|
d5c82e4a28 | ||
|
|
436adc5ff6 | ||
|
|
97a5d91fce | ||
|
|
9eee8557c2 | ||
|
|
4394dd023e | ||
|
|
d39bbe4f99 | ||
|
|
b188c8adfe | ||
|
|
ea6fd42b2e | ||
|
|
625499f758 | ||
|
|
0c2d6dd582 | ||
|
|
f6a9c97e7f | ||
|
|
e2d741937e | ||
|
|
9a3404f4b8 | ||
|
|
d000aa095f | ||
|
|
8945801db8 | ||
|
|
40f327ea17 | ||
|
|
44a2604dbd | ||
|
|
0ae7424f04 | ||
|
|
091af92e8d | ||
|
|
3ed338ed64 | ||
|
|
65781570e7 | ||
|
|
e1f7aeaa96 | ||
|
|
a0a580588c | ||
|
|
a2f2a21a4c | ||
|
|
1ddb446bc6 | ||
|
|
dcdc6851b6 | ||
|
|
ece4e56118 | ||
|
|
7cd104c047 | ||
|
|
80a7dd75c8 | ||
|
|
ec2ef752b3 | ||
|
|
57d5c57048 | ||
|
|
9b7c22c35e | ||
|
|
6711f6f6e6 | ||
|
|
7ac7077f8c | ||
|
|
ed20249988 | ||
|
|
dba8d23348 | ||
|
|
ab9062983c | ||
|
|
37493a4688 | ||
|
|
689d149edc | ||
|
|
9b9106c5da | ||
|
|
9a43af98c3 | ||
|
|
1eae92f2b2 | ||
|
|
4560bbc0ed | ||
|
|
2d5ef0aae4 | ||
|
|
096542c8ff | ||
|
|
d123ca5173 | ||
|
|
388929f207 | ||
|
|
29b9223a14 | ||
|
|
cf283377cd | ||
|
|
5f218e69d4 | ||
|
|
7f5fb165de | ||
|
|
eec79d7d3e | ||
|
|
00809d88f6 | ||
|
|
18b0cc6ea0 | ||
|
|
54d9902ae4 | ||
|
|
eff39a39e3 | ||
|
|
005a7bb68e | ||
|
|
afc1701150 | ||
|
|
72eada0639 | ||
|
|
979e8a47ab | ||
|
|
e119a364c1 | ||
|
|
c7b9bbb18d | ||
|
|
3cb3dce247 | ||
|
|
a7e1c89e66 | ||
|
|
ca1060ada5 | ||
|
|
69578ab054 | ||
|
|
511bed47ff | ||
|
|
26a0412649 | ||
|
|
4989ff773c | ||
|
|
04c0d68763 | ||
|
|
589035d42b | ||
|
|
5726d50106 | ||
|
|
6e9c326a52 | ||
|
|
fc1cdf4d0e | ||
|
|
45178ac38b | ||
|
|
09ee1c26b1 | ||
|
|
64180ca57f | ||
|
|
0c052a118c | ||
|
|
cb9fd43f11 | ||
|
|
1dc55c2770 | ||
|
|
0ca6d46a7b | ||
|
|
3624ef4755 | ||
|
|
f07a003f2c | ||
|
|
0ef46407dd | ||
|
|
f7f3c005f8 | ||
|
|
9a5aa52801 | ||
|
|
1e172cb4e3 |
@@ -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"/>
|
||||
|
||||
4
jikimo_workorder_exception/__init__.py
Normal file
4
jikimo_workorder_exception/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
# -*- 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)
|
||||
|
||||
|
||||
@@ -1273,3 +1273,18 @@ msgstr ""
|
||||
#: model:product.template,description_sale:mrp_workorder.product_template_stool_top
|
||||
msgid "wooden stool top"
|
||||
msgstr ""
|
||||
|
||||
#. module: mrp_workorder
|
||||
#: model:quality.point.test_type,name:mrp_workorder.test_type_register_consumed_materials
|
||||
msgid "Register Consumed Materials"
|
||||
msgstr "登记消耗材料"
|
||||
|
||||
#. module: mrp_workorder
|
||||
#: model:quality.point.test_type,name:mrp_workorder.test_type_register_byproducts
|
||||
msgid "Register By-products"
|
||||
msgstr "按产品注册"
|
||||
|
||||
#. module: mrp_workorder
|
||||
#: model:quality.point.test_type,name:mrp_workorder.test_type_print_label
|
||||
msgid "Print label"
|
||||
msgstr "打印标签"
|
||||
@@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models
|
||||
from odoo import fields, models, api
|
||||
|
||||
|
||||
class MrpProduction(models.Model):
|
||||
@@ -12,7 +12,8 @@ class MrpProduction(models.Model):
|
||||
check_ids = fields.One2many('quality.check', 'production_id', string="Checks")
|
||||
|
||||
def _split_productions(self, amounts=False, cancel_remaining_qty=False, set_consumed_qty=False):
|
||||
productions = super()._split_productions(amounts=amounts, cancel_remaining_qty=cancel_remaining_qty, set_consumed_qty=set_consumed_qty)
|
||||
productions = super()._split_productions(amounts=amounts, cancel_remaining_qty=cancel_remaining_qty,
|
||||
set_consumed_qty=set_consumed_qty)
|
||||
backorders = productions[1:]
|
||||
if not backorders:
|
||||
return productions
|
||||
@@ -20,3 +21,4 @@ class MrpProduction(models.Model):
|
||||
if wo.current_quality_check_id.component_id:
|
||||
wo.current_quality_check_id._update_component_quantity()
|
||||
return productions
|
||||
|
||||
|
||||
@@ -1,41 +1,44 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- Manufacturing Order for Planing view -->
|
||||
<record id="mrp_production_tree_view_planning" model="ir.ui.view">
|
||||
<field name="name">mrp.production.tree.inherit.planning</field>
|
||||
<field name="model">mrp.production</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree default_order="date_planned_start asc" decoration-info="state=='confirmed'" decoration-danger="date_planned_start<current_date and state not in ('done','cancel')" decoration-muted="state in ('done','cancel')" string="Manufacturing Orders" name="Production">
|
||||
<field name="message_needaction" invisible="1"/>
|
||||
<field name="name"/>
|
||||
<field name="date_planned_start"/>
|
||||
<field name="product_id"/>
|
||||
<field name="product_qty" sum="Total Qty" string="Quantity"/>
|
||||
<field name="product_uom_id" string="Unit of Measure" options="{'no_open':True,'no_create':True}" groups="uom.group_uom"/>
|
||||
<field name="reservation_state" string="Availability"/>
|
||||
<field name="origin"/>
|
||||
<field name="state"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
<!-- Manufacturing Order for Planing view -->
|
||||
<record id="mrp_production_tree_view_planning" model="ir.ui.view">
|
||||
<field name="name">mrp.production.tree.inherit.planning</field>
|
||||
<field name="model">mrp.production</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree default_order="date_planned_start asc" decoration-info="state=='confirmed'"
|
||||
decoration-danger="date_planned_start<current_date and state not in ('done','cancel')"
|
||||
decoration-muted="state in ('done','cancel')" string="Manufacturing Orders" name="Production">
|
||||
<field name="message_needaction" invisible="1"/>
|
||||
<field name="name"/>
|
||||
<field name="date_planned_start"/>
|
||||
<field name="product_id"/>
|
||||
<field name="product_qty" sum="Total Qty" string="Quantity"/>
|
||||
<field name="product_uom_id" string="Unit of Measure" options="{'no_open':True,'no_create':True}"
|
||||
groups="uom.group_uom"/>
|
||||
<field name="reservation_state" string="Availability"/>
|
||||
<field name="origin"/>
|
||||
<field name="state"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- <record id="mrp_production_form_inherit_planning" model="ir.ui.view">-->
|
||||
<!-- <field name="name">mrp.production.form_inherit_planning</field>-->
|
||||
<!-- <field name="model">mrp.production</field>-->
|
||||
<!-- <field name="inherit_id" ref="mrp.mrp_production_form_view"/>-->
|
||||
<!-- <field name="arch" type="xml">-->
|
||||
<!-- <xpath expr="div[hasclass('oe_chatter')]" position="replace">-->
|
||||
<!-- <!– 这里放置替换后的内容 –>-->
|
||||
<!-- </xpath>-->
|
||||
<!-- <xpath expr="//notebook" position="after">-->
|
||||
<!-- <div class="oe_chatter">-->
|
||||
<!-- <field name="message_follower_ids"/>-->
|
||||
<!-- <field name="activity_ids"/>-->
|
||||
<!-- <field name="message_ids"/>-->
|
||||
<!-- </div>-->
|
||||
<!-- </xpath>-->
|
||||
<!-- </field>-->
|
||||
<!-- </record>-->
|
||||
<!-- <record id="mrp_production_form_inherit_planning" model="ir.ui.view">-->
|
||||
<!-- <field name="name">mrp.production.form_inherit_planning</field>-->
|
||||
<!-- <field name="model">mrp.production</field>-->
|
||||
<!-- <field name="inherit_id" ref="mrp.mrp_production_form_view"/>-->
|
||||
<!-- <field name="arch" type="xml">-->
|
||||
<!-- <xpath expr="div[hasclass('oe_chatter')]" position="replace">-->
|
||||
<!-- <!– 这里放置替换后的内容 –>-->
|
||||
<!-- </xpath>-->
|
||||
<!-- <xpath expr="//notebook" position="after">-->
|
||||
<!-- <div class="oe_chatter">-->
|
||||
<!-- <field name="message_follower_ids"/>-->
|
||||
<!-- <field name="activity_ids"/>-->
|
||||
<!-- <field name="message_ids"/>-->
|
||||
<!-- </div>-->
|
||||
<!-- </xpath>-->
|
||||
<!-- </field>-->
|
||||
<!-- </record>-->
|
||||
|
||||
<record id="mrp_production_view_search_inherit_planning" model="ir.ui.view">
|
||||
<field name="name">mrp.production.search.view.inherit.planning</field>
|
||||
@@ -43,7 +46,9 @@
|
||||
<field name="inherit_id" ref="mrp.view_mrp_production_filter"/>
|
||||
<field name="arch" type="xml">
|
||||
<filter name="filter_planned" position="attributes">
|
||||
<attribute name="domain">[('is_planned', '=', True), ('date_planned_start', '!=', False), ('date_planned_finished', '!=', False)]</attribute>
|
||||
<attribute name="domain">[('is_planned', '=', True), ('date_planned_start', '!=', False),
|
||||
('date_planned_finished', '!=', False)]
|
||||
</attribute>
|
||||
</filter>
|
||||
</field>
|
||||
</record>
|
||||
@@ -51,30 +56,33 @@
|
||||
<record id="production_order_unplan_server_action" model="ir.actions.server">
|
||||
<field name="name">Unplan orders</field>
|
||||
<field name="model_id" ref="mrp.model_mrp_production"/>
|
||||
<field name="binding_model_id" ref="mrp.model_mrp_production" />
|
||||
<field name="binding_model_id" ref="mrp.model_mrp_production"/>
|
||||
<field name="binding_view_types">list</field>
|
||||
<field name="state">code</field>
|
||||
<field name="code">records.button_unplan()</field>
|
||||
</record>
|
||||
|
||||
<record id="mrp.act_product_mrp_production_workcenter" model="ir.actions.act_window">
|
||||
<field name="domain">[('bom_id', '!=', False), ('bom_id.operation_ids.workcenter_id', '=', active_id), ('date_planned_start', '!=', False), ('date_planned_finished', '!=', False)]</field>
|
||||
<field name="domain">[('bom_id', '!=', False), ('bom_id.operation_ids.workcenter_id', '=', active_id),
|
||||
('date_planned_start', '!=', False), ('date_planned_finished', '!=', False)]
|
||||
</field>
|
||||
<field name="view_id" ref="mrp_production_tree_view_planning"/>
|
||||
</record>
|
||||
|
||||
|
||||
<menuitem id="mrp_workorder_menu_planning"
|
||||
name="Work Orders"
|
||||
sequence="2"
|
||||
parent="mrp.mrp_planning_menu_root"
|
||||
groups="mrp.group_mrp_routings"/>
|
||||
name="Work Orders"
|
||||
sequence="2"
|
||||
parent="mrp.mrp_planning_menu_root"
|
||||
groups="mrp.group_mrp_routings"/>
|
||||
<menuitem id="menu_mrp_workorder_production"
|
||||
name="Planning by Production"
|
||||
sequence="1"
|
||||
action="mrp.action_mrp_workorder_production"
|
||||
parent="mrp_workorder_menu_planning"/>
|
||||
name="Planning by Production"
|
||||
sequence="1"
|
||||
action="mrp.action_mrp_workorder_production"
|
||||
parent="mrp_workorder_menu_planning"/>
|
||||
<menuitem id="menu_mrp_workorder_workcenter"
|
||||
name="Planning by Workcenter"
|
||||
sequence="2"
|
||||
action="mrp_workorder.action_mrp_workorder_dependencies_workcenter"
|
||||
parent="mrp_workorder_menu_planning"/>
|
||||
name="Planning by Workcenter"
|
||||
sequence="2"
|
||||
action="mrp_workorder.action_mrp_workorder_dependencies_workcenter"
|
||||
parent="mrp_workorder_menu_planning"/>
|
||||
</odoo>
|
||||
|
||||
@@ -1050,3 +1050,13 @@ msgstr "工作中心故障"
|
||||
#: model:ir.model.fields,field_description:quality.field_quality_point_test_type__active
|
||||
msgid "active"
|
||||
msgstr "有效"
|
||||
|
||||
#. module: quality
|
||||
#: model:quality.point.test_type,name:quality.test_type_instructions
|
||||
msgid "Instructions"
|
||||
msgstr "使用说明"
|
||||
|
||||
#. module: quality
|
||||
#: model:quality.point.test_type,name:quality.test_type_picture
|
||||
msgid "Take a Picture"
|
||||
msgstr "照片"
|
||||
@@ -15,7 +15,7 @@ class TestType(models.Model):
|
||||
_description = "Quality Control Test Type"
|
||||
|
||||
# Used instead of selection field in order to hide a choice depending on the view.
|
||||
name = fields.Char('Name', required=True)
|
||||
name = fields.Char('Name', required=True,translate=True)
|
||||
technical_name = fields.Char('Technical name', required=True)
|
||||
active = fields.Boolean('active', default=True)
|
||||
|
||||
|
||||
@@ -1185,3 +1185,14 @@ msgstr "请先进行质量检查!"
|
||||
#: model_terms:ir.ui.view,arch_db:quality_control.quality_alert_team_view_form
|
||||
msgid "e.g. The QA Masters"
|
||||
msgstr "例如:QA大师"
|
||||
|
||||
|
||||
#. module: quality_control
|
||||
#: model:quality.point.test_type,name:quality_control.test_type_passfail
|
||||
msgid "Pass - Fail"
|
||||
msgstr "通过-失败"
|
||||
|
||||
#. module: quality_control
|
||||
#: model:quality.point.test_type,name:quality_control.test_type_measure
|
||||
msgid "Measure"
|
||||
msgstr "测量"
|
||||
@@ -6,3 +6,4 @@ from . import stock_move
|
||||
from . import stock_move_line
|
||||
from . import stock_picking
|
||||
from . import stock_lot
|
||||
from . import product_category
|
||||
32
quality_control/models/product_category.py
Normal file
32
quality_control/models/product_category.py
Normal file
@@ -0,0 +1,32 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
from math import sqrt
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from datetime import datetime
|
||||
|
||||
import random
|
||||
|
||||
from odoo import api, models, fields, _
|
||||
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT, float_round
|
||||
from odoo.osv.expression import OR
|
||||
|
||||
|
||||
class ProductCategory(models.Model):
|
||||
_inherit = 'product.category'
|
||||
@api.model
|
||||
def name_search(self, name='', args=None, operator='ilike', limit=100):
|
||||
if args is None:
|
||||
args = []
|
||||
# 添加过滤条件,确保只返回名称为 'abc' 的记录
|
||||
args += [('name', 'not in', ['Saleable', 'Expenses', 'Deliveries'])]
|
||||
|
||||
# 调用父类的 name_search 方法
|
||||
return super(ProductCategory, self).name_search(name, args=args, operator=operator, limit=limit)
|
||||
|
||||
@api.model
|
||||
def search(self, args, limit=100, offset=0, order=None, count=False):
|
||||
# 添加过滤条件,确保只返回名称不在指定列表中的记录
|
||||
args += [('name', 'not in', ['Saleable', 'Expenses', 'Deliveries'])]
|
||||
|
||||
# 调用父类的 search 方法
|
||||
return super(ProductCategory, self).search(args, limit=limit, offset=offset, order=order, count=count)
|
||||
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)
|
||||
|
||||
@@ -394,3 +394,11 @@ class MachineToolCategory(models.Model):
|
||||
active = fields.Boolean('有效', default=True)
|
||||
category = fields.Selection([('shukong', u'数控'), ('putong', u'普通')], string=u'机床类别',
|
||||
default='shukong')
|
||||
|
||||
|
||||
class MachiningAccuracy(models.Model):
|
||||
_name = 'sf.machining.accuracy'
|
||||
_description = '加工精度'
|
||||
name = fields.Char('一般公差', index=True)
|
||||
standard_tolerance = fields.Char(string="标准公差")
|
||||
sync_id = fields.Char('同步ID')
|
||||
|
||||
@@ -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([('无', '无'), ('单面', '单面'), ('双面', '双面')],
|
||||
|
||||
@@ -242,3 +242,12 @@ access_sf_fixture_materials_basic_parameters_group_sf_stock_manager,sf_fixture_m
|
||||
access_sf_multi_mounting_type_group_sf_stock_manager,sf_multi_mounting_type_group_sf_stock_manager,model_sf_multi_mounting_type,sf_base.group_sf_stock_manager,1,0,0,0
|
||||
access_sf_machine_brand_group_sf_stock_manager,sf_machine_brand_group_sf_stock_manager,model_sf_machine_brand,sf_base.group_sf_stock_manager,1,0,0,0
|
||||
access_sf_cutting_tool_type_group_sf_stock_manager,sf_cutting_tool_type_group_sf_stock_manager,model_sf_cutting_tool_type,sf_base.group_sf_stock_manager,1,0,0,0
|
||||
|
||||
|
||||
access_sf_cutting_tool_material_group_plan_dispatch,sf_cutting_tool_material_group_plan_dispatch,model_sf_cutting_tool_material,sf_base.group_plan_dispatch,1,0,0,0
|
||||
access_sf_functional_cutting_tool_model_group_plan_dispatch,sf_functional_cutting_tool_model_group_plan_dispatch,model_sf_functional_cutting_tool_model,sf_base.group_plan_dispatch,1,0,0,0
|
||||
access_sf_cutting_tool_type_group_plan_dispatch,sf_cutting_tool_type_group_plan_dispatch,model_sf_cutting_tool_type,sf_base.group_plan_dispatch,1,0,0,0
|
||||
|
||||
|
||||
access_sf_machining_accuracy,sf_machining_accuracy,model_sf_machining_accuracy,base.group_user,1,0,0,0
|
||||
access_sf_machining_accuracy_admin,sf_machining_accuracy_admin,model_sf_machining_accuracy,base.group_system,1,0,0,0
|
||||
|
||||
|
125
sf_base/static/js/customTable.js
Normal file
125
sf_base/static/js/customTable.js
Normal file
@@ -0,0 +1,125 @@
|
||||
// 获取表格数据
|
||||
function getDomData() {
|
||||
const dom = $('div[name=cutting_speed_ids]')
|
||||
if (!dom.length) return
|
||||
const table = dom.find('.o_list_table')
|
||||
const thead = table.children('thead')
|
||||
const tbody = table.children('tbody')
|
||||
const tbody_child = tbody.children()
|
||||
const hideTheadDom = thead.find('[data-name=process_capability]')
|
||||
hideTheadDom.hide().next().hide()
|
||||
hideTheadDom.before('<th customTh>精加工</th><th customTh>粗加工</th>')
|
||||
tbody_child.each(function () {
|
||||
const dom = $(this).children('[name=process_capability]')
|
||||
if(!dom.length) return
|
||||
dom.css('cssText', 'display: none!important').next().css('cssText', 'display: none!important')
|
||||
const isCu = dom.text() == '粗加工' // 是否粗加工
|
||||
const v = dom.next().text() // 切削速度
|
||||
dom.after(`<td customSpeed="1" name="process_capability" is="精加工" val="${ v }">${!isCu ? v : ''}</td><td customSpeed="1" name="process_capability" is="粗加工" val="${ v }">${isCu ? v : ''}</td>`)
|
||||
setListenClick()
|
||||
})
|
||||
return;
|
||||
handleTbody(tbody, newTableData, ΦList, table)
|
||||
}
|
||||
|
||||
// 监听点击
|
||||
function setListenClick() {
|
||||
$(document).click(function (e) {
|
||||
if ($(e.target).attr('customSpeed')) {
|
||||
const orginV = $('[customInput=1]').children('input').val()
|
||||
$('[customInput=1]').parent().html(orginV)
|
||||
const v = $(e.target).attr('val')
|
||||
const is = $(e.target).attr('is')
|
||||
$(e.target).html('')
|
||||
const input = $('<div customInput="1" is="' + is + '" class="o_field_widget o_field_char"><input class="o_input" type="text" autocomplete="off" maxlength="20"></div>')
|
||||
input.children('input').val(v)
|
||||
$(e.target).append(input)
|
||||
input.children('input').focus()
|
||||
input.children('input').select()
|
||||
} else if ($(e.target).attr('customInput')) {
|
||||
|
||||
} else {
|
||||
const orginV = $('[customInput=1]').children('input').val()
|
||||
$('[customInput=1]').parent().html(orginV)
|
||||
const v = $(e.target).attr('val')
|
||||
}
|
||||
})
|
||||
$(document).off('change') // 防止重复绑定
|
||||
$(document).on('change', '[customInput] input', async function () {
|
||||
$(this).parents('td').attr('val', $(this).val())
|
||||
$(this).parents('td').siblings('[customspeed]').attr('val', $(this).val())
|
||||
var eve1 = new Event('change')
|
||||
var eve2 = new Event('input')
|
||||
var eve3 = new Event('click')
|
||||
let patchSpeedDom = $(this).parents('td').siblings('[name=cutting_speed]')
|
||||
let patchProcessDom = $(this).parents('td').siblings('[name=process_capability]')
|
||||
$(this).parents('td').siblings('[customspeed]').text('') // 清空其他加工类型的数据
|
||||
await timeOut(500)
|
||||
patchProcessDom[0].dispatchEvent(eve3)
|
||||
await timeOut(200)
|
||||
const processVal = $(this).parent().attr('is')
|
||||
patchProcessDom.find('select').val(`"${processVal}"`) // 设置源select的val为“加工类型 is”、
|
||||
patchProcessDom.attr("data-tooltip", `${processVal}`)
|
||||
patchProcessDom.find('select')[0].dispatchEvent(eve1)
|
||||
|
||||
patchSpeedDom[0].dispatchEvent(eve3)
|
||||
await timeOut(200)
|
||||
patchSpeedDom.find('input').val($(this).val())
|
||||
await timeOut(50)
|
||||
patchSpeedDom.find('input')[0].dispatchEvent(eve2)
|
||||
patchSpeedDom.find('input')[0].dispatchEvent(eve1)
|
||||
})
|
||||
$(document).off('blur') // 防止重复绑定
|
||||
$(document).on('blur', '[customInput] input', async function () {
|
||||
if(!$(this).length) return
|
||||
|
||||
$(this).parents('td').siblings('[customspeed]').text('') // 清空其他加工类型的数据
|
||||
let patchProcessDom = $(this).parents('td').siblings('[name=process_capability]')
|
||||
try {
|
||||
patchProcessDom[0].dispatchEvent(new Event('click'))
|
||||
const processVal = $(this).parent().attr('is')
|
||||
patchProcessDom.find('select').val(`"${processVal}"`) // 设置源select的val为“加工类型 is”、
|
||||
patchProcessDom.attr("data-tooltip", `${processVal}`)
|
||||
patchProcessDom.find('select')[0].dispatchEvent(new Event('change'))
|
||||
} catch {
|
||||
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
function timeOut(time) {
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
resolve()
|
||||
}, time)
|
||||
})
|
||||
}
|
||||
|
||||
function listenAdd() {
|
||||
$('td.o_field_x2many_list_row_add a').click(async function () {
|
||||
await timeOut(500)
|
||||
const tr = $('.o_list_table').children('tbody').children('tr').eq(-2)
|
||||
if(tr.children('td').eq(2).text() == '') {
|
||||
const dom = tr.children('[name=process_capability]')
|
||||
if(!dom.length) return
|
||||
dom.css('cssText', 'display: none!important').next().css('cssText', 'display: none!important')
|
||||
const isCu = dom.text() == '粗加工' // 是否粗加工
|
||||
const v = dom.next().text() // 切削速度
|
||||
dom.after(`<td customSpeed="1" name="process_capability" is="精加工" val="${ v }">${!isCu ? v : ''}</td><td customSpeed="1" name="process_capability" is="粗加工" val="${ v }">${isCu ? v : ''}</td>`)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function listenSave() {
|
||||
$('.o_form_button_save').click( async function () {
|
||||
await timeOut(1000)
|
||||
if($(this).parent().next().length) return
|
||||
$('th[customTh],td[cusomSpeed]').remove()
|
||||
getDomData()
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
listenAdd()
|
||||
listenSave()
|
||||
getDomData()
|
||||
47
sf_base/static/js/setTableWidth.js
Normal file
47
sf_base/static/js/setTableWidth.js
Normal file
@@ -0,0 +1,47 @@
|
||||
// 因为表格可以拖动设置宽度,所以需要用js设置初始宽度
|
||||
function setBasicParamTableWidth() {
|
||||
// const _100px = 'th[data-name="cutting_blade_length"],th[data-name="cutting_blade_length"],th[data-name="name"],th[data-name="tip_handling_size"],th[data-name="cutting_depth_max"],th[data-name="diameter_inner_circle"],th[data-name="diameter_mounting_hole" ],th[data-name="radius_tip_re" ],th[data-name="is_chip_breaker"],th[data-name="chip_breaker_type_code"],th[data-name="blade_profile"]'
|
||||
// const _65px = 'th[data-name="edge_angle"],th[data-name="relief_angle"],[data-name="total_length"],th[data-name="length"],th[data-name="thickness"],th[data-name="blade_number"]'
|
||||
// const _80px = 'th[data-name="arbor_diameter"],th[data-name="head_height"],th[data-name="head_width"],th[data-name="head_length"],th[data-name="blade_diameter"],th[data-name="blade_length"] ,th[data-name="neck_length"] ,th[data-name="neck_diameter"] ,th[data-name="shank_diameter"],th[data-name="shank_length"],th[data-name="tip_diameter"],th[data-name="knife_tip_taper"],th[data-name="blade_helix_angle"] ,th[data-name="blade_width"],th[data-name="blade_depth"]'
|
||||
// const _50px = 'th[data-name="pitch"],th[data-name="width"],th[data-name="height"]'
|
||||
|
||||
const basicParamDom = $('.fixTableCss')
|
||||
// const basicParamDom_100px = basicParamDom.find(_100px) // 四字以上
|
||||
// const basicParamDom_65px = basicParamDom.find(_65px) // 大概三个字加单位
|
||||
// const basicParamDom_80px = basicParamDom.find(_80px) // 大概四个字加单位
|
||||
// const basicParamDom_50px= basicParamDom.find(_50px) // 大概两个字加单位
|
||||
//
|
||||
// basicParamDom_100px.css({'width': '100px', 'max-width': 'auto', ',min-width': 'auto'})
|
||||
// basicParamDom_65px.css({'width': '65px', 'max-width': 'auto', ',min-width': 'auto'})
|
||||
// basicParamDom_80px.css({'width': '80px', 'max-width': 'auto', ',min-width': 'auto'})
|
||||
// basicParamDom_50px.css({'width': '50px', 'max-width': 'auto', ',min-width': 'auto'})
|
||||
let dom = []
|
||||
try {
|
||||
dom = basicParamDom.find('table').find('thead').children().children()
|
||||
|
||||
} catch {
|
||||
dom = []
|
||||
}
|
||||
if (!dom) return
|
||||
dom.each(function () {
|
||||
if ($(this).hasClass('row_no') >= 0) { // 序号列
|
||||
// 不设置 通过css设置
|
||||
}
|
||||
const text = $(this).text().split('(')
|
||||
if ($(this).attr('data-name') == 'name' || text[0].length > 4) {
|
||||
$(this).width('100px')
|
||||
} else if(text[0].length == 4){
|
||||
$(this).width('80px')
|
||||
} else if(text[0].length == 3){
|
||||
$(this).width('65px')
|
||||
} else if(text[0].length == 2){
|
||||
$(this).width('50px')
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
setBasicParamTableWidth()
|
||||
$('.o_field_many2one_selection').on('click', $('#cutting_tool_material_id + ul'), function () {
|
||||
setTimeout(setBasicParamTableWidth, 500)
|
||||
})
|
||||
159
sf_base/static/js/updateTable.js
Normal file
159
sf_base/static/js/updateTable.js
Normal file
@@ -0,0 +1,159 @@
|
||||
// 获取表格数据
|
||||
function getDomData() {
|
||||
const dom = $('#updateTable').prev()
|
||||
if (!dom.length) return
|
||||
const table = $('#updateTable').prev().find('.o_list_table')
|
||||
const customTable = table.clone()
|
||||
customTable.addClass('customTable')
|
||||
table.parent().append(customTable)
|
||||
table.hide()
|
||||
const thead = customTable.children('thead')
|
||||
const tbody = customTable.children('tbody')
|
||||
const tableData = []
|
||||
const tbody_child = tbody.children()
|
||||
|
||||
const tbody_child_len = tbody_child.length
|
||||
|
||||
for (let v = 0; v < tbody_child_len; v++) { // 将数据取出来到tableData里面
|
||||
const data = tbody_child[v].innerText.split('\t')
|
||||
// console.log('dom data',data)
|
||||
const [index, deep, name, Φ, value] = data
|
||||
tableData.push({index, deep, name, Φ, value})
|
||||
}
|
||||
const ΦList = [...new Set(tableData.map(_ => _.name))] // ΦList去重
|
||||
const newTableData = {}
|
||||
tableData.forEach(_ => {
|
||||
const key = _.deep + '|' + _.Φ
|
||||
!newTableData[key] ? newTableData[key] = {i: _.index} : '';
|
||||
if (_.Φ) { // 去除没有Φ的脏数据
|
||||
newTableData[key]['Φ' + _.Φ] = _.value
|
||||
newTableData[key]['Φ' + _.Φ + 'i'] = _.index
|
||||
}
|
||||
})
|
||||
// console.log('qwdh',tableData, ΦList, newTableData);
|
||||
|
||||
if (ΦList.filter(_ => _).length == 0) return;
|
||||
handleThead(thead, ΦList)
|
||||
|
||||
handleTbody(tbody, newTableData, ΦList, table)
|
||||
}
|
||||
|
||||
// 重新设置表头、
|
||||
function handleThead(thead, ΦList) {
|
||||
const dom = thead.children().eq(0).children()
|
||||
const len = dom.length
|
||||
dom.eq(0).attr('rowspan', 2)
|
||||
dom.eq(1).attr('rowspan', 2)
|
||||
len == 5 ? dom.eq(2).attr('rowspan', 2) : ''
|
||||
dom.eq(-2).attr('colspan', ΦList.length)
|
||||
dom.eq(-1).remove()
|
||||
|
||||
const tr = document.createElement('tr')
|
||||
for (let v = 0; v < ΦList.length; v++) {
|
||||
const th = document.createElement('th')
|
||||
th.innerText = 'Φ' + ΦList[v]
|
||||
tr.append(th)
|
||||
}
|
||||
thead.append(tr)
|
||||
}
|
||||
|
||||
// 重新设置表格
|
||||
function handleTbody(tbody, newTableData, ΦList, table) {
|
||||
console.log(newTableData)
|
||||
tbody.html('')
|
||||
let i = 0
|
||||
const data = Object.keys(newTableData)
|
||||
// data.sort((a, b) => {
|
||||
// a = a.split('=')[1].split('%')[0]
|
||||
// b = b.split('=')[1].split('%')[0]
|
||||
// return a - b
|
||||
// })
|
||||
// console.log('wqoqw ',ΦList)
|
||||
data.forEach(_ => {
|
||||
i++
|
||||
const tr = $('<tr class="o_data_row"></tr>')
|
||||
const td0 = $('<td></td>')
|
||||
td0.text(i)
|
||||
tr.append(td0)
|
||||
const lit = _.split('|')
|
||||
//
|
||||
const td1 = $('<td></td>')
|
||||
const td2 = $('<td></td>')
|
||||
td1.text(lit[0])
|
||||
td2.text(lit[1] || '')
|
||||
tr.append(td1)
|
||||
tr.append(td2)
|
||||
ΦList.forEach(Φ => {
|
||||
const td = $('<td class="o_data_cell cursor-pointer o_field_cell o_list_char"></td>')
|
||||
td.text(newTableData[_]['Φ' + Φ])
|
||||
td.attr('col', newTableData[_]['Φ' + Φ + 'i'])
|
||||
td.attr('val', newTableData[_]['Φ' + Φ])
|
||||
td.attr('coustomTd', 1)
|
||||
tr.append(td)
|
||||
})
|
||||
// // for (let j = 0; j < ΦList.length; j++) {
|
||||
// // const td = document.createElement('td')
|
||||
// // td.innerText = newTableData[data[v]][_]
|
||||
// // th.append(td)
|
||||
// // }
|
||||
tbody.append(tr)
|
||||
})
|
||||
// $(document).click(function (e) {
|
||||
// if ($(e.target).attr('coustomTd')) {
|
||||
// const orginV = $('[coustomInput=1]').children('input').val()
|
||||
// $('[coustomInput=1]').parent().html(orginV)
|
||||
// const v = $(e.target).attr('val')
|
||||
// console.log($(e.target));
|
||||
// $(e.target).html('')
|
||||
// const input = $('<div coustomInput="1" name="feed_per_tooth" class="o_field_widget o_field_char"><input class="o_input" type="text" autocomplete="off" maxlength="20"></div>')
|
||||
// input.children('input').val(v)
|
||||
// $(e.target).append(input)
|
||||
// input.children('input').focus()
|
||||
// input.children('input').select()
|
||||
// } else if ($(e.target).attr('coustomInput')) {
|
||||
//
|
||||
// } else {
|
||||
// const orginV = $('[coustomInput=1]').children('input').val()
|
||||
// $('[coustomInput=1]').parent().html(orginV)
|
||||
// const v = $(e.target).attr('val')
|
||||
// }
|
||||
// })
|
||||
// $(document).off('change') // 防止重复绑定
|
||||
// $(document).on('change', '[coustomInput] input', function () {
|
||||
// $(this).parents('td').attr('val', $(this).val());
|
||||
// var eve1 = new Event('change');
|
||||
// var eve2 = new Event('input');
|
||||
// var eve3 = new Event('click');
|
||||
// const i = $(this).parents('td').attr('col');
|
||||
// let patchDom = table.find('tbody').children('tr').eq(i - 1);
|
||||
//
|
||||
// if (patchDom.length === 0) {
|
||||
// console.error('No such row found');
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// patchDom = patchDom.children().eq(-1);
|
||||
//
|
||||
// setTimeout(() => {
|
||||
// if (patchDom.length === 0) {
|
||||
// console.error('No such cell found');
|
||||
// return;
|
||||
// }
|
||||
// patchDom[0].dispatchEvent(eve3); // Simulate click event
|
||||
//
|
||||
// setTimeout(() => {
|
||||
// patchDom = patchDom.find('input');
|
||||
// if (patchDom.length === 0) {
|
||||
// console.error('No input found in the target cell');
|
||||
// return;
|
||||
// }
|
||||
// patchDom.val($(this).val());
|
||||
// patchDom[0].dispatchEvent(eve2);
|
||||
// patchDom[0].dispatchEvent(eve1);
|
||||
// }, 200);
|
||||
// }, 500);
|
||||
// });
|
||||
|
||||
}
|
||||
|
||||
getDomData()
|
||||
62
sf_base/static/src/js/custom_barcode_handlers.js
Normal file
62
sf_base/static/src/js/custom_barcode_handlers.js
Normal file
@@ -0,0 +1,62 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { barcodeGenericHandlers } from '@barcodes/barcode_handlers';
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
|
||||
// 定义新的 clickOnButton 函数
|
||||
function customClickOnButton(selector) {
|
||||
console.log("This is the custom clickOnButton function!");
|
||||
|
||||
const buttons = document.body.querySelectorAll(selector);
|
||||
|
||||
let length = buttons.length;
|
||||
if (length > 0) {
|
||||
buttons[length - 1].click();
|
||||
} else {
|
||||
console.warn(`Button with selector ${selector} not found`);
|
||||
}
|
||||
}
|
||||
|
||||
patch(barcodeGenericHandlers, "start", {
|
||||
start(env, { ui, barcode, notification }) {
|
||||
// 使用新定义的 clickOnButton 函数
|
||||
const COMMANDS = {
|
||||
"O-CMD.EDIT": () => customClickOnButton(".o_form_button_edit"),
|
||||
"O-CMD.DISCARD": () => customClickOnButton(".o_form_button_cancel"),
|
||||
"O-CMD.SAVE": () => customClickOnButton(".o_form_button_save"),
|
||||
"O-CMD.PREV": () => customClickOnButton(".o_pager_previous"),
|
||||
"O-CMD.NEXT": () => customClickOnButton(".o_pager_next"),
|
||||
"O-CMD.PAGER-FIRST": () => updatePager("first"),
|
||||
"O-CMD.PAGER-LAST": () => updatePager("last"),
|
||||
"O-CMD.CONFIRM": () => customClickOnButton(".jikimo_button_confirm"),
|
||||
"O-CMD.FLUSHED": () => customClickOnButton(".jikimo_button_flushed"),
|
||||
};
|
||||
|
||||
barcode.bus.addEventListener("barcode_scanned", (ev) => {
|
||||
const barcode = ev.detail.barcode;
|
||||
if (barcode.startsWith("O-BTN.")) {
|
||||
let targets = [];
|
||||
try {
|
||||
targets = getVisibleElements(ui.activeElement, `[barcode_trigger=${barcode.slice(6)}]`);
|
||||
} catch (_e) {
|
||||
console.warn(`Barcode '${barcode}' is not valid`);
|
||||
}
|
||||
for (let elem of targets) {
|
||||
elem.click();
|
||||
}
|
||||
}
|
||||
if (barcode.startsWith("O-CMD.")) {
|
||||
const fn = COMMANDS[barcode];
|
||||
if (fn) {
|
||||
fn();
|
||||
} else {
|
||||
notification.add(env._t("Barcode: ") + `'${barcode}'`, {
|
||||
title: env._t("Unknown barcode command"),
|
||||
type: "danger"
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
@@ -5,9 +5,12 @@ import { registry } from '@web/core/registry';
|
||||
import { formView } from '@web/views/form/form_view';
|
||||
import { FormController } from '@web/views/form/form_controller';
|
||||
|
||||
import { listView } from '@web/views/list/list_view';
|
||||
import { ListController } from '@web/views/list/list_controller'
|
||||
|
||||
import { onRendered, onMounted } from "@odoo/owl";
|
||||
|
||||
export class RemoveFocusController extends FormController {
|
||||
export class RemoveFocusFormController extends FormController {
|
||||
setup() {
|
||||
super.setup();
|
||||
|
||||
@@ -17,7 +20,23 @@ export class RemoveFocusController extends FormController {
|
||||
}
|
||||
}
|
||||
|
||||
registry.category('views').add('remove_focus_view', {
|
||||
registry.category('views').add('remove_focus_form_view', {
|
||||
...formView,
|
||||
Controller: RemoveFocusController,
|
||||
Controller: RemoveFocusFormController,
|
||||
});
|
||||
|
||||
|
||||
export class RemoveFocusListController extends ListController {
|
||||
setup() {
|
||||
super.setup();
|
||||
|
||||
onMounted(() => {
|
||||
this.__owl__.bdom.el.querySelectorAll(':focus').forEach(element => element.blur());
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
registry.category('views').add('remove_focus_list_view', {
|
||||
...listView,
|
||||
Controller: RemoveFocusListController,
|
||||
});
|
||||
52
sf_base/static/src/scss/change.scss
Normal file
52
sf_base/static/src/scss/change.scss
Normal file
@@ -0,0 +1,52 @@
|
||||
.o_list_renderer .o_list_table tbody > tr > td:not(.o_list_record_selector):not(.o_handle_cell):not(.o_list_button):not(.o_list_record_remove) {
|
||||
white-space: nowrap !important;
|
||||
}
|
||||
|
||||
.text-truncate {
|
||||
overflow: unset !important;
|
||||
text-overflow: unset !important;
|
||||
white-space: unset !important;
|
||||
}
|
||||
|
||||
|
||||
// 设置表格不超出页面宽度
|
||||
.o_form_view .o_field_widget .o_list_renderer {
|
||||
width: calc(100% - 64px) !important;
|
||||
margin:0 auto;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
// 表格针对处理
|
||||
.fixTableCss {
|
||||
text-align: center;
|
||||
.o_list_number_th,.o_list_number {
|
||||
text-align: center!important;
|
||||
}
|
||||
.ui-sortable {
|
||||
tr > td:first-child {
|
||||
padding: 0!important;
|
||||
}
|
||||
}
|
||||
.row_no {
|
||||
padding: 0!important;;
|
||||
width: 35px!important;
|
||||
}
|
||||
|
||||
th[data-name="total_length"],th[data-name="length"],th[data-name="thickness"] {
|
||||
.flex-row-reverse {
|
||||
span {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 其他不能用js处理的表格
|
||||
.otherTableFix {
|
||||
th[data-name="cutting_tool_material_id"] {
|
||||
width: 100px!important;
|
||||
}
|
||||
th[data-name="ramping_angle_max"],th[data-name="ramping_angle_min"] {
|
||||
width: 200px!important;
|
||||
}
|
||||
}
|
||||
@@ -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"/>
|
||||
@@ -617,4 +614,23 @@
|
||||
<field name="res_model">sf.machine.control_system</field>
|
||||
<field name="view_mode">tree</field>
|
||||
</record>
|
||||
|
||||
#------------------加工精度------------------
|
||||
<record model="ir.ui.view" id="tree_sf_machining_accuracy_view">
|
||||
<field name="name">tree.sf.machining.accuracy</field>
|
||||
<field name="model">sf.machining.accuracy</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="加工精度" create="0" edit="0" delete="0">
|
||||
<field name="name"/>
|
||||
<field name="standard_tolerance"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_sf_machining_accuracy" model="ir.actions.act_window">
|
||||
<field name="name">加工精度</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">sf.machining.accuracy</field>
|
||||
<field name="view_mode">tree</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -12,7 +12,6 @@
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="mrs_production_process_parameter_form">
|
||||
<field name="model">sf.production.process.parameter</field>
|
||||
<field name="arch" type="xml">
|
||||
@@ -26,19 +25,19 @@
|
||||
<group>
|
||||
<group>
|
||||
<field name="code" readonly="1"/>
|
||||
<field name="process_id" readonly="1"/>
|
||||
<field name="process_description" readonly="1"/>
|
||||
<field name="process_id" attrs="{'readonly': [('code', '!=', False)]}"/>
|
||||
<field name="process_description" attrs="{'readonly': [('code', '!=', False)]}"/>
|
||||
<field name="gain_way"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="processing_day" readonly="1"/>
|
||||
<field name="travel_day" readonly="1"/>
|
||||
<field name="processing_mm" readonly="1"/>
|
||||
<field name="processing_day" attrs="{'readonly': [('code', '!=', False)]}"/>
|
||||
<field name="travel_day" attrs="{'readonly': [('code', '!=', False)]}"/>
|
||||
<field name="processing_mm" attrs="{'readonly': [('code', '!=', False)]}"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="适用材料">
|
||||
<field name="materials_model_ids" readonly="1"></field>
|
||||
<field name="materials_model_ids" attrs="{'readonly': [('code', '!=', False)]}"></field>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
|
||||
@@ -141,12 +141,18 @@
|
||||
sequence="1"
|
||||
action="action_sf_machine_brand"/>
|
||||
|
||||
<menuitem
|
||||
id="menu_sf_machining_accuracy"
|
||||
parent="menu_sf_base"
|
||||
name="加工精度"
|
||||
sequence="1"
|
||||
action="action_sf_machining_accuracy"/>
|
||||
|
||||
<menuitem
|
||||
id="menu_sf_machine_control_system"
|
||||
parent="menu_sf_base"
|
||||
name="数控系统"
|
||||
sequence="1"
|
||||
sequence="2"
|
||||
action="action_sf_machine_control_system"/>
|
||||
|
||||
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -80,10 +80,10 @@
|
||||
<field name="name">sf.cutter.function.tree</field>
|
||||
<field name="model">sf.functional.cutting.tool.model</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="功能刀具类型" create="0" delete="0" edit="1">
|
||||
<field name="name" string="名称"/>
|
||||
<field name="code"/>
|
||||
<field name="remark"/>
|
||||
<tree string="功能刀具类型" create="0" delete="0" edit="1" editable="bottom">
|
||||
<field name="name" string="名称" readonly="1"/>
|
||||
<field name="code" readonly="1"/>
|
||||
<field name="remark" readonly="1"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
@@ -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"/>
|
||||
@@ -359,6 +360,7 @@
|
||||
<field name="cooling_jacket"/>
|
||||
</tree>
|
||||
</field>
|
||||
<script src="/sf_base/static/js/setTableWidth.js?time=3"></script>
|
||||
</page>
|
||||
<page string="切削速度Vc"
|
||||
attrs="{'invisible': [('cutting_tool_type', 'not in', ('整体式刀具','刀片'))]}">
|
||||
@@ -379,6 +381,9 @@
|
||||
<field name="cutting_speed"/>
|
||||
</tree>
|
||||
</field>
|
||||
<script src="/sf_base/static/js/customTable.js?time=3"></script>
|
||||
<script src="/sf_base/static/js/setTableWidth.js?time=3"></script>
|
||||
|
||||
</page>
|
||||
<page string="每齿走刀量fz"
|
||||
attrs="{'invisible': [('cutting_tool_type', 'not in', ('整体式刀具','刀片'))]}">
|
||||
@@ -391,6 +396,8 @@
|
||||
<field name="feed_per_tooth"/>
|
||||
</tree>
|
||||
</field>
|
||||
<div id="updateTable"></div>
|
||||
<script src="/sf_base/static/js/updateTable.js?time=3"></script>
|
||||
<field name="feed_per_tooth_ids_3"
|
||||
attrs="{'invisible': [('cutting_tool_type', 'not in', ('刀片'))]}">
|
||||
<tree editable="bottom" class="center" create="0" delete="0">
|
||||
@@ -577,7 +584,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">
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import traceback
|
||||
from datetime import datetime
|
||||
import logging
|
||||
import requests
|
||||
@@ -53,11 +54,13 @@ class StatusChange(models.Model):
|
||||
if not ret.get('error'):
|
||||
logging.info('接口已经执行=============')
|
||||
else:
|
||||
logging.error('工厂加工同步订单状态失败 {}'.format(ret.text))
|
||||
raise UserError('工厂加工同步订单状态失败')
|
||||
traceback_error = traceback.format_exc()
|
||||
logging.error("bfm订单状态同步失败:%s" % traceback_error)
|
||||
raise UserError('工厂加工同步订单状态到bfm失败')
|
||||
except UserError as e:
|
||||
logging.error('工厂加工同步订单状态失败 {}'.format(e))
|
||||
raise UserError('工厂加工同步订单状态失败')
|
||||
traceback_error = traceback.format_exc()
|
||||
logging.error("工厂加工同步订单状态失败:%s " % traceback_error)
|
||||
raise UserError(e)
|
||||
return res
|
||||
|
||||
def action_cancel(self):
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<data>
|
||||
<record id="mrp.product_template_action" model="ir.actions.act_window">
|
||||
<field name="context">
|
||||
{"search_default_categ_id":1,"search_default_consumable": 1, 'default_detailed_type': 'product'}
|
||||
</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="view_sale_product_template_form_inherit_sf">
|
||||
<field name="name">product.template.form.inherit.sf</field>
|
||||
<field name="model">product.template</field>
|
||||
@@ -88,7 +93,8 @@
|
||||
</div>
|
||||
<field name="model_volume" string="体积[mm³]"/>
|
||||
<field name="product_model_type_id" string="模型类型"/>
|
||||
<field name="model_processing_panel" placeholder="例如R,U" string="加工面板" readonly="1"/>
|
||||
<field name="model_processing_panel" placeholder="例如R,U" string="加工面板"
|
||||
readonly="1"/>
|
||||
<field name="model_machining_precision"/>
|
||||
<field name="model_process_parameters_ids" string="表面工艺参数"
|
||||
widget="many2many_tags"
|
||||
@@ -103,11 +109,24 @@
|
||||
'刀具')], 'required': True}
|
||||
</attribute>
|
||||
</xpath>
|
||||
<!-- <xpath expr="//field[@name='default_code']" position="attributes">-->
|
||||
<!-- <attribute name="attrs">{'readonly': [('categ_type', '=', '刀具')], 'invisible':-->
|
||||
<!-- [('product_variant_count', '>' , 1)]}-->
|
||||
<!-- </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)]}-->
|
||||
<!-- </attribute>-->
|
||||
<!-- </xpath>-->
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -185,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,9 +7,11 @@
|
||||
'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',
|
||||
'data/cron_data.xml',
|
||||
],
|
||||
'demo': [
|
||||
],
|
||||
|
||||
15
sf_hr/data/cron_data.xml
Normal file
15
sf_hr/data/cron_data.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<record model="ir.cron" id="ir_cron_employee_info_sync">
|
||||
<field name="name">员工企微id同步</field>
|
||||
<field name="model_id" ref="hr.model_hr_employee"/>
|
||||
<field name="state">code</field>
|
||||
<field name="code">model._employee_info_sync()</field>
|
||||
<field name="interval_number">1</field>
|
||||
<field name="interval_type">days</field>
|
||||
<field name="numbercall">-1</field>
|
||||
<field name="doall" eval="False"/>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -1,2 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import hr_employee
|
||||
from . import res_config_setting
|
||||
|
||||
28
sf_hr/models/hr_employee.py
Normal file
28
sf_hr/models/hr_employee.py
Normal file
@@ -0,0 +1,28 @@
|
||||
import logging
|
||||
import requests
|
||||
from odoo import models, fields, api, _
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class JkmPracticeEmployee(models.Model):
|
||||
_inherit = 'hr.employee'
|
||||
_description = '员工信息'
|
||||
|
||||
we_id = fields.Char(string='企微ID', index=True)
|
||||
|
||||
def _employee_info_sync(self):
|
||||
url = '/api/get/organization'
|
||||
config = self.env['res.config.settings'].get_values()
|
||||
ret = requests.post((config['ims_url'] + url), json={}, data={})
|
||||
result = ret.json()['result']
|
||||
if result['code'] == 200:
|
||||
if result['employee_list']:
|
||||
for employee_info in result['employee_list']:
|
||||
if employee_info['work_email']:
|
||||
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'])
|
||||
30
sf_hr/models/res_config_setting.py
Normal file
30
sf_hr/models/res_config_setting.py
Normal file
@@ -0,0 +1,30 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import logging
|
||||
from odoo import api, fields, models
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ResIMSConfigSettings(models.TransientModel):
|
||||
_inherit = 'res.config.settings'
|
||||
|
||||
ims_url = fields.Char('综合管理系统访问地址')
|
||||
|
||||
@api.model
|
||||
def get_values(self):
|
||||
"""
|
||||
重载获取参数的方法,参数都存在系统参数中
|
||||
:return:
|
||||
"""
|
||||
values = super(ResIMSConfigSettings, self).get_values()
|
||||
config = self.env['ir.config_parameter'].sudo()
|
||||
ims_url = config.get_param('ims_url', default='')
|
||||
values.update(
|
||||
ims_url=ims_url,
|
||||
)
|
||||
return values
|
||||
|
||||
def set_values(self):
|
||||
super(ResIMSConfigSettings, self).set_values()
|
||||
ir_config = self.env['ir.config_parameter'].sudo()
|
||||
ir_config.set_param("ims_url", self.ims_url or "")
|
||||
@@ -7,7 +7,16 @@
|
||||
<field name="inherit_id" ref="hr.view_employee_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//group//field[@name='work_email']" position="attributes">
|
||||
<attribute name="required">1</attribute>
|
||||
<attribute name="required">1</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//page[@name='public']" position='after'>
|
||||
<page string="企业微信">
|
||||
<group col="2">
|
||||
<group>
|
||||
<field name="we_id"/>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
28
sf_hr/views/res_config_settings_views.xml
Normal file
28
sf_hr/views/res_config_settings_views.xml
Normal file
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<record id="res_config_settings_finance_view_form_extend" model="ir.ui.view">
|
||||
<field name="name">res.config.settings.finance.view.form.extend</field>
|
||||
<field name="model">res.config.settings</field>
|
||||
<field name="inherit_id" ref="base.res_config_settings_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//div[hasclass('app_settings_block')]/div" position="before">
|
||||
<div>
|
||||
<h2>综合管理系统接口配置</h2>
|
||||
<div class="row mt16 o_settings_container" id="jd_api">
|
||||
<div class="col-12 col-lg-6 o_setting_box">
|
||||
<div class="o_setting_left_pane"/>
|
||||
<div class="o_setting_right_pane">
|
||||
<div class="text-muted">
|
||||
<label for="ims_url"/>
|
||||
<field name="ims_url"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -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': {
|
||||
|
||||
@@ -6,7 +6,7 @@ import base64
|
||||
import logging
|
||||
import psycopg2
|
||||
from datetime import datetime, timedelta
|
||||
from odoo import http
|
||||
from odoo import http, fields
|
||||
from odoo.http import request
|
||||
|
||||
# 数据库连接配置
|
||||
@@ -15,14 +15,20 @@ db_config = {
|
||||
"user": "postgres",
|
||||
"password": "postgres",
|
||||
"port": "5432",
|
||||
"host": "172.16.10.98"
|
||||
"host": "172.16.10.131"
|
||||
}
|
||||
|
||||
|
||||
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:
|
||||
@@ -66,7 +72,7 @@ class Sf_Dashboard_Connect(http.Controller):
|
||||
|
||||
# 获取当前时间的时间戳
|
||||
current_timestamp = datetime.now().timestamp()
|
||||
print(current_timestamp)
|
||||
# print(current_timestamp)
|
||||
|
||||
# tem_list = [
|
||||
# "XT-GNJC-WZZX-X800-Y550-Z550-T24-A5-1", "XT-GNJC-LSZX-X800-Y550-Z550-T24-A3-3",
|
||||
@@ -158,11 +164,12 @@ 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,
|
||||
# 停机时间:关机时间 - 运行时间
|
||||
# 停机时长:关机时间 - 初次上线时间
|
||||
'img': f'data:image/png;base64,{machine_data.machine_tool_picture.decode("utf-8")}',
|
||||
# 'img': f'data:image/png;base64,{machine_data.machine_tool_picture.decode("utf-8")}',
|
||||
'img': f'https://xt.sf.jikimo.com/equipment/get_image/{machine_data.id}',
|
||||
'equipment_type': machine_data.category_id.name,
|
||||
})
|
||||
|
||||
@@ -182,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('"')
|
||||
@@ -194,7 +201,7 @@ class Sf_Dashboard_Connect(http.Controller):
|
||||
begin_time = datetime.strptime(begin_time_str, '%Y-%m-%d %H:%M:%S')
|
||||
end_time = datetime.strptime(end_time_str, '%Y-%m-%d %H:%M:%S')
|
||||
|
||||
print('begin_time: %s' % begin_time)
|
||||
# print('begin_time: %s' % begin_time)
|
||||
|
||||
for item in machine_list:
|
||||
sql = '''
|
||||
@@ -226,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="*")
|
||||
@@ -275,7 +376,7 @@ class Sf_Dashboard_Connect(http.Controller):
|
||||
res = {'Succeed': True}
|
||||
line_list_obj = request.env['sf.production.line'].sudo().search([('name', 'ilike', 'CNC')])
|
||||
line_list = list(map(lambda x: x.name, line_list_obj))
|
||||
print('line_list: %s' % line_list)
|
||||
# print('line_list: %s' % line_list)
|
||||
res['LineList'] = line_list
|
||||
|
||||
except Exception as e:
|
||||
@@ -300,21 +401,65 @@ 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)
|
||||
# print('line_list: %s' % line_list)
|
||||
for line in line_list:
|
||||
plan_data = plan_obj.search([('production_line_id.name', '=', line)])
|
||||
# 工单总量
|
||||
plan_data_total_counts = plan_obj.search_count([('production_line_id.name', '=', line)])
|
||||
# 工单完成量
|
||||
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_fault_counts = plan_obj.search_count(
|
||||
[('production_line_id.name', '=', line), ('production_id.state', 'in', ['scrap', 'cancel'])])
|
||||
|
||||
# # 工单计划量
|
||||
# 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([
|
||||
('production_line_id.name', '=', line),
|
||||
])
|
||||
|
||||
# 过滤出那些检测结果状态为 '返工' 或 '报废' 的记录
|
||||
faulty_plans = plan_data.filtered(lambda p: any(
|
||||
result.test_results in ['返工', '报废'] for result in p.production_id.detection_result_ids
|
||||
))
|
||||
|
||||
# 查找制造订单取消与归档的数量
|
||||
cancel_order_count = production_obj.search_count(
|
||||
[('production_line_id.name', '=', line), ('state', 'in', ['cancel']),
|
||||
('active', '=', False)])
|
||||
|
||||
# 计算符合条件的记录数量
|
||||
# plan_data_fault_counts = len(faulty_plans) + cancel_order_count
|
||||
plan_data_fault_counts = len(faulty_plans)
|
||||
|
||||
# 工单返工数量
|
||||
|
||||
@@ -326,17 +471,81 @@ class Sf_Dashboard_Connect(http.Controller):
|
||||
(plan_data_finish_counts / plan_data_total_counts if plan_data_total_counts > 0 else 0), 3)
|
||||
|
||||
# 工单进度偏差
|
||||
plan_data_progress_deviation = plan_data_finish_counts - plan_data_plan_counts
|
||||
plan_data_progress_deviation = plan_data_total_counts - plan_data_finish_counts - plan_data_fault_counts
|
||||
|
||||
# 完成记录
|
||||
plan_data_finish_orders = plan_obj.search(
|
||||
[('production_line_id.name', '=', line), ('state', 'in', ['finished'])])
|
||||
|
||||
# # 检测量
|
||||
# detection_nums = 0
|
||||
# for i in plan_data_finish_orders:
|
||||
# print('i: %s' % i)
|
||||
# if i.production_id.detection_result_ids:
|
||||
# detection_nums += 1
|
||||
#
|
||||
# print('detection_nums: %s' % detection_nums)
|
||||
|
||||
# 检测量
|
||||
detection_data = len(
|
||||
plan_data_finish_orders.mapped('production_id.detection_result_ids').filtered(lambda r: r))
|
||||
|
||||
# 检测合格量
|
||||
pass_nums = plan_data_finish_orders.filtered(lambda p: any(
|
||||
result.test_results in ['合格'] for result in p.production_id.detection_result_ids
|
||||
))
|
||||
|
||||
# 质量合格率
|
||||
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) / len(plan_data_finish_orders) if len(plan_data_finish_orders) > 0 else 0), 3)
|
||||
|
||||
# 返工率
|
||||
rework_rate = round(
|
||||
(plan_data_rework_counts / plan_data_finish_counts if plan_data_finish_counts > 0 else 0), 3)
|
||||
|
||||
# 交付准时率
|
||||
delay_num = 0
|
||||
for plan in plan_data_finish_orders:
|
||||
sale_obj = request.env['sale.order'].sudo().search([('name', '=', plan.origin)])
|
||||
if sale_obj:
|
||||
if sale_obj.deadline_of_delivery and plan.actual_end_time:
|
||||
# 将 deadline_of_delivery 转换为字符串
|
||||
date_as_string = sale_obj.deadline_of_delivery.strftime('%Y-%m-%d')
|
||||
# 然后使用 strptime 将字符串转换为 datetime 对象
|
||||
date_as_datetime = datetime.strptime(date_as_string, '%Y-%m-%d')
|
||||
|
||||
# 将 actual_end_time 转换为 datetime 对象
|
||||
datetime_value = fields.Datetime.from_string(plan.actual_end_time)
|
||||
|
||||
# 给 datetime_value 加1天
|
||||
new_datetime_value = datetime_value + timedelta(days=1)
|
||||
|
||||
# 比较 new_datetime_value 和 date_as_datetime 的大小
|
||||
if new_datetime_value.date() > date_as_datetime.date():
|
||||
delay_num += 1
|
||||
delay_rate = round((delay_num / plan_data_finish_counts if plan_data_finish_counts > 0 else 0), 3)
|
||||
on_time_rate = 1 - delay_rate
|
||||
|
||||
if plan_data:
|
||||
data = {
|
||||
'plan_data_total_counts': plan_data_total_counts,
|
||||
'plan_data_finish_counts': plan_data_finish_counts,
|
||||
'plan_data_plan_counts': plan_data_plan_counts,
|
||||
'plan_data_plan_counts': plan_data_total_counts,
|
||||
'plan_data_fault_counts': plan_data_fault_counts,
|
||||
'nopass_orders_counts': detection_data - len(pass_nums),
|
||||
'finishe_rate': finishe_rate,
|
||||
'plan_data_progress_deviation': plan_data_progress_deviation,
|
||||
'plan_data_rework_counts': plan_data_rework_counts
|
||||
'plan_data_rework_counts': plan_data_rework_counts,
|
||||
'on_time_rate': on_time_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
|
||||
|
||||
@@ -363,7 +572,28 @@ class Sf_Dashboard_Connect(http.Controller):
|
||||
end_time_str = kw['end_time'].strip('"')
|
||||
begin_time = datetime.strptime(begin_time_str, '%Y-%m-%d %H:%M:%S')
|
||||
end_time = datetime.strptime(end_time_str, '%Y-%m-%d %H:%M:%S')
|
||||
print('line_list: %s' % line_list)
|
||||
# print('line_list: %s' % line_list)
|
||||
print('kw', kw)
|
||||
time_unit = kw.get('time_unit', 'day').strip('"') # 默认单位为天
|
||||
print('time_unit: %s' % time_unit)
|
||||
|
||||
# 日期或小时循环生成器,根据time_unit决定是按天还是按小时
|
||||
def get_time_intervals(start_time, end_time, time_unit):
|
||||
intervals = []
|
||||
current_time = start_time
|
||||
if time_unit == 'hour':
|
||||
delta = timedelta(hours=1)
|
||||
else:
|
||||
delta = timedelta(days=1)
|
||||
|
||||
while current_time < end_time:
|
||||
next_time = current_time + delta
|
||||
# 确保最后一个时间段不会超出end_time
|
||||
if next_time > end_time:
|
||||
next_time = end_time
|
||||
intervals.append((current_time, next_time))
|
||||
current_time = next_time
|
||||
return intervals
|
||||
|
||||
def get_date_list(start_date, end_date):
|
||||
date_list = []
|
||||
@@ -374,10 +604,36 @@ class Sf_Dashboard_Connect(http.Controller):
|
||||
return date_list
|
||||
|
||||
for line in line_list:
|
||||
date_list = get_date_list(begin_time, end_time)
|
||||
date_field_name = 'actual_end_time' # 替换为你模型中的实际字段名
|
||||
order_counts = []
|
||||
|
||||
date_field_name = 'actual_end_time' # 替换为你模型中的实际字段名
|
||||
if time_unit == 'hour':
|
||||
time_intervals = get_time_intervals(begin_time, end_time, time_unit)
|
||||
print('============================= %s' % time_intervals)
|
||||
|
||||
time_count_dict = {}
|
||||
|
||||
for time_interval in time_intervals:
|
||||
start_time, end_time = time_interval
|
||||
|
||||
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)
|
||||
# order_counts.append()
|
||||
res['data'][line] = {
|
||||
'finish_order_nums': time_count_dict,
|
||||
'plan_order_nums': 28
|
||||
}
|
||||
return json.dumps(res)
|
||||
|
||||
date_list = get_date_list(begin_time, end_time)
|
||||
|
||||
for date in date_list:
|
||||
next_day = date + timedelta(days=1)
|
||||
@@ -487,7 +743,7 @@ class Sf_Dashboard_Connect(http.Controller):
|
||||
end_time_str = kw['end_time'].strip('"')
|
||||
begin_time = datetime.strptime(begin_time_str, '%Y-%m-%d %H:%M:%S')
|
||||
end_time = datetime.strptime(end_time_str, '%Y-%m-%d %H:%M:%S')
|
||||
print('line_list: %s' % line_list)
|
||||
# print('line_list: %s' % line_list)
|
||||
not_done_data = []
|
||||
done_data = []
|
||||
final_data = {}
|
||||
@@ -495,11 +751,22 @@ class Sf_Dashboard_Connect(http.Controller):
|
||||
for line in line_list:
|
||||
# 未完成订单
|
||||
not_done_orders = plan_obj.search(
|
||||
[('production_line_id.name', '=', line), ('state', 'not in', ['finished'])])
|
||||
print(not_done_orders)
|
||||
[('production_line_id.name', '=', line), ('state', 'not in', ['finished']),
|
||||
('production_id.state', 'not in', ['cancel', 'done']), ('active', '=', True)
|
||||
])
|
||||
# print(not_done_orders)
|
||||
|
||||
# 完成订单
|
||||
finish_orders = plan_obj.search([('production_line_id.name', '=', line), ('state', 'in', ['finished'])])
|
||||
print(finish_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),
|
||||
('actual_end_time', '>=', time_24_hours_ago)
|
||||
])
|
||||
# print(finish_orders)
|
||||
|
||||
# 获取所有未完成订单的ID列表
|
||||
order_ids = [order.id for order in not_done_orders]
|
||||
@@ -555,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
|
||||
@@ -575,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)
|
||||
@@ -613,27 +883,30 @@ 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命令
|
||||
cur.execute(sql, (item,))
|
||||
result = cur.fetchall()
|
||||
print('result========', result)
|
||||
# print('result========', result)
|
||||
|
||||
cur.execute(sql2, (item,))
|
||||
result2 = cur.fetchall()
|
||||
print('result2========', result2)
|
||||
# print('result2========', result2)
|
||||
#
|
||||
for row in result:
|
||||
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
|
||||
@@ -659,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="*")
|
||||
@@ -830,3 +1076,308 @@ class Sf_Dashboard_Connect(http.Controller):
|
||||
# 返回数据
|
||||
res['data'] = oee_data
|
||||
return json.dumps(res)
|
||||
|
||||
@http.route(['/equipment/get_image/<int:record_id>'], type='http', auth="public", website=True)
|
||||
def get_image(self, record_id, **kwargs):
|
||||
# 获取模型中的记录
|
||||
record = request.env['maintenance.equipment'].sudo().browse(record_id)
|
||||
|
||||
# 获取图片字段的数据
|
||||
image_data_base64 = record.machine_tool_picture
|
||||
|
||||
if image_data_base64:
|
||||
# 将Base64解码为二进制数据
|
||||
image_data_binary = base64.b64decode(image_data_base64)
|
||||
|
||||
# 返回图片数据,并设置正确的Content-Type
|
||||
return request.make_response(image_data_binary, headers=[('Content-Type', 'image/png')])
|
||||
else:
|
||||
# 如果没有图片数据,返回404
|
||||
return request.not_found()
|
||||
|
||||
# 设备运行率
|
||||
@http.route('/api/RunningTime', type='http', auth='public', methods=['GET', 'POST'], csrf=False, cors="*")
|
||||
def RunningTime(self, **kw):
|
||||
"""
|
||||
获取设备运行时长
|
||||
"""
|
||||
res = {'status': 1, 'message': '成功', 'data': {}}
|
||||
# 连接数据库
|
||||
conn = psycopg2.connect(**db_config)
|
||||
# 获取请求的机床数据
|
||||
machine_list = ast.literal_eval(kw['machine_list'])
|
||||
|
||||
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
|
||||
|
||||
# 初始化当天、当月和有记录以来的总时长
|
||||
day_total_running_time = 0
|
||||
day_total_process_time = 0
|
||||
day_work_rate = 0
|
||||
month_total_running_time = 0
|
||||
month_total_process_time = 0
|
||||
month_work_rate = 0
|
||||
all_time_total_running_time = 0
|
||||
all_time_total_process_time = 0
|
||||
all_time_work_rate = 0
|
||||
|
||||
for item in machine_list:
|
||||
# 获取当天第一条记录(排除device_state等于‘离线’的记录)
|
||||
with conn.cursor() as cur:
|
||||
cur.execute("""
|
||||
SELECT * FROM device_data
|
||||
WHERE device_name = %s
|
||||
AND time::date = CURRENT_DATE
|
||||
AND device_state != '离线'
|
||||
ORDER BY time ASC
|
||||
LIMIT 1;
|
||||
""", (item,))
|
||||
first_today = fetch_result_as_dict(cur)
|
||||
# print("当天第一条记录(非离线):", first_today)
|
||||
|
||||
# 获取当天最新一条记录(排除device_state等于‘离线’的记录)
|
||||
with conn.cursor() as cur:
|
||||
cur.execute("""
|
||||
SELECT * FROM device_data
|
||||
WHERE device_name = %s
|
||||
AND time::date = CURRENT_DATE
|
||||
AND device_state != '离线'
|
||||
ORDER BY time DESC
|
||||
LIMIT 1;
|
||||
""", (item,))
|
||||
last_today = fetch_result_as_dict(cur)
|
||||
# print("当天最新一条记录(非离线):", last_today)
|
||||
|
||||
# 计算当天运行时长
|
||||
if first_today and last_today:
|
||||
running_time = convert_to_seconds(last_today['run_time']) - convert_to_seconds(first_today['run_time'])
|
||||
process_time = convert_to_seconds(last_today['process_time']) - convert_to_seconds(
|
||||
first_today['process_time'])
|
||||
day_total_running_time += abs(running_time)
|
||||
day_total_process_time += abs(process_time)
|
||||
|
||||
# 获取当月第一条记录(排除device_state等于‘离线’的记录)
|
||||
with conn.cursor() as cur:
|
||||
cur.execute("""
|
||||
SELECT * FROM device_data
|
||||
WHERE device_name = %s
|
||||
AND EXTRACT(YEAR FROM time) = EXTRACT(YEAR FROM CURRENT_DATE)
|
||||
AND EXTRACT(MONTH FROM time) = EXTRACT(MONTH FROM CURRENT_DATE)
|
||||
AND device_state != '离线'
|
||||
ORDER BY time ASC
|
||||
LIMIT 1;
|
||||
""", (item,))
|
||||
first_month = fetch_result_as_dict(cur)
|
||||
# print("当月第一条记录(非离线):", first_month)
|
||||
|
||||
# 获取当月最新一条记录(排除device_state等于‘离线’的记录)
|
||||
with conn.cursor() as cur:
|
||||
cur.execute("""
|
||||
SELECT * FROM device_data
|
||||
WHERE device_name = %s
|
||||
AND EXTRACT(YEAR FROM time) = EXTRACT(YEAR FROM CURRENT_DATE)
|
||||
AND EXTRACT(MONTH FROM time) = EXTRACT(MONTH FROM CURRENT_DATE)
|
||||
AND device_state != '离线'
|
||||
ORDER BY time DESC
|
||||
LIMIT 1;
|
||||
""", (item,))
|
||||
last_month = fetch_result_as_dict(cur)
|
||||
# print("当月最新一条记录(非离线):", last_month)
|
||||
|
||||
# 计算当月运行时长
|
||||
if first_month and last_month:
|
||||
month_running_time = convert_to_seconds(last_month['run_time']) - convert_to_seconds(
|
||||
first_month['run_time'])
|
||||
month_process_time = convert_to_seconds(last_month['process_time']) - convert_to_seconds(
|
||||
first_month['process_time'])
|
||||
month_total_running_time += abs(month_running_time)
|
||||
month_total_process_time += abs(month_process_time)
|
||||
|
||||
# 获取有记录以来的第一条记录(排除device_state等于‘离线’的记录)
|
||||
with conn.cursor() as cur:
|
||||
cur.execute("""
|
||||
SELECT * FROM device_data
|
||||
WHERE device_name = %s
|
||||
AND device_state != '离线'
|
||||
ORDER BY time ASC
|
||||
LIMIT 1;
|
||||
""", (item,))
|
||||
first_all_time = fetch_result_as_dict(cur)
|
||||
# print("有记录以来的第一条记录(非离线):", first_all_time)
|
||||
|
||||
# 获取有记录以来的最新一条记录(排除device_state等于‘离线’的记录)
|
||||
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,))
|
||||
last_all_time = fetch_result_as_dict(cur)
|
||||
# print("有记录以来的最新一条记录(非离线):", last_all_time)
|
||||
|
||||
# 计算有记录以来的运行时长
|
||||
if first_all_time and last_all_time:
|
||||
all_time_running_time = convert_to_seconds(last_all_time['run_time']) - convert_to_seconds(
|
||||
first_all_time['run_time'])
|
||||
all_time_process_time = convert_to_seconds(last_all_time['process_time']) - convert_to_seconds(
|
||||
first_all_time['process_time'])
|
||||
all_time_total_running_time += abs(all_time_running_time)
|
||||
all_time_total_process_time += abs(all_time_process_time)
|
||||
|
||||
# 计算当天工作效率
|
||||
if day_total_running_time > day_total_process_time:
|
||||
day_work_rate = day_total_process_time / day_total_running_time if day_total_running_time != 0 else 0
|
||||
else:
|
||||
day_work_rate = day_total_running_time / day_total_process_time if day_total_process_time != 0 else 0
|
||||
print("当天工作效率: %s" % day_work_rate)
|
||||
|
||||
# 计算当月工作效率
|
||||
if month_total_running_time > month_total_process_time:
|
||||
month_work_rate = month_total_process_time / month_total_running_time if month_total_running_time != 0 else 0
|
||||
else:
|
||||
month_work_rate = month_total_running_time / month_total_process_time if month_total_process_time != 0 else 0
|
||||
print("当月工作效率: %s" % month_work_rate)
|
||||
|
||||
# 计算有记录以来的工作效率
|
||||
if all_time_total_running_time > all_time_total_process_time:
|
||||
all_time_work_rate = all_time_total_process_time / all_time_total_running_time if all_time_total_running_time != 0 else 0
|
||||
else:
|
||||
all_time_work_rate = all_time_total_running_time / all_time_total_process_time if all_time_total_process_time != 0 else 0
|
||||
print("有记录以来的工作效率: %s" % all_time_work_rate)
|
||||
|
||||
conn.close()
|
||||
|
||||
# 返回数据
|
||||
res['data']['day_work_rate'] = day_work_rate
|
||||
res['data']['month_work_rate'] = month_work_rate
|
||||
res['data']['all_time_work_rate'] = all_time_work_rate
|
||||
|
||||
return json.dumps(res)
|
||||
|
||||
# 设备运行时长
|
||||
@http.route('/api/RunningTimeDetail', type='http', auth='public', methods=['GET', 'POST'], csrf=False, cors="*")
|
||||
def RunningTimeDetail(self, **kw):
|
||||
"""
|
||||
获取
|
||||
"""
|
||||
res = {'status': 1, 'message': '成功', 'data': {}}
|
||||
# 连接数据库
|
||||
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 != '离线' 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,
|
||||
'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()
|
||||
|
||||
return json.dumps(res)
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -16,6 +16,7 @@ class ResBFMConfigSettings(models.TransientModel):
|
||||
# ("https://bfm.jikimo.com", "正式环境(https://bfm.jikimo.com)")], string='bfm环境', store=True)
|
||||
|
||||
bfm_url_new = fields.Char('业务平台环境路径', placeholder='请输入当前对应的业务平台环境路径')
|
||||
get_check_file_path = fields.Char('获取检查文件路径', default='')
|
||||
|
||||
@api.model
|
||||
def get_values(self):
|
||||
@@ -26,9 +27,11 @@ class ResBFMConfigSettings(models.TransientModel):
|
||||
values = super(ResBFMConfigSettings, self).get_values()
|
||||
config = self.env['ir.config_parameter'].sudo()
|
||||
bfm_url_new = config.get_param('bfm_url_new', default='')
|
||||
get_check_file_path = config.get_param('get_check_file_path', default='')
|
||||
|
||||
values.update(
|
||||
bfm_url_new=bfm_url_new,
|
||||
get_check_file_path=get_check_file_path
|
||||
)
|
||||
return values
|
||||
|
||||
@@ -36,3 +39,4 @@ class ResBFMConfigSettings(models.TransientModel):
|
||||
super(ResBFMConfigSettings, self).set_values()
|
||||
ir_config = self.env['ir.config_parameter'].sudo()
|
||||
ir_config.set_param("bfm_url_new", self.bfm_url_new or "")
|
||||
ir_config.set_param("get_check_file_path", self.get_check_file_path or "")
|
||||
|
||||
@@ -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>
|
||||
@@ -22,6 +22,33 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h2>获取检测报告服务配置</h2>
|
||||
<div class="row mt16 o_settings_container" id="check_report_config">
|
||||
<div class="col-12 col-lg-6 o_setting_box">
|
||||
<div class="o_setting_left_pane"/>
|
||||
<div class="o_setting_right_pane">
|
||||
<div class="text-muted">
|
||||
<label for="get_check_file_path" />
|
||||
<field name="get_check_file_path" string="检测报告服务地址"/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- </div> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</xpath>
|
||||
<xpath expr="//div[@id='check_report_config']/div" position="after">
|
||||
<div class="col-12 col-lg-6 o_setting_box">
|
||||
<div class="o_setting_left_pane">
|
||||
<field name="is_get_detection_file"/>
|
||||
</div>
|
||||
<div class="o_setting_right_pane">
|
||||
<div class="text-muted">
|
||||
<label for="is_get_detection_file"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -2,3 +2,4 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import models
|
||||
from . import wizard
|
||||
|
||||
@@ -11,12 +11,14 @@
|
||||
'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',
|
||||
'views/equipment_maintenance_standards_views.xml',
|
||||
'views/maintenance_request_views.xml',
|
||||
'views/maintenance_equipment_category_views.xml',
|
||||
'wizard/maintenance_request_wizard.xml',
|
||||
],
|
||||
'installable': True,
|
||||
'application': False,
|
||||
|
||||
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
@@ -24,6 +24,7 @@ class SfEquipmentSaintenanceStandards(models.Model):
|
||||
remark = fields.Char('备注')
|
||||
maintenance_type = fields.Selection([('保养', '保养'), ("检修", "检修")], string='类型', default='保养')
|
||||
name = fields.Char(string='名称')
|
||||
active = fields.Boolean(default=True)
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import json
|
||||
import base64
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
import requests
|
||||
from odoo.addons.sf_base.commons.common import Common
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
|
||||
|
||||
class SfMaintenanceEquipmentCategory(models.Model):
|
||||
@@ -38,8 +39,9 @@ class SfMaintenanceEquipment(models.Model):
|
||||
|
||||
crea_url = "/api/machine_tool/create"
|
||||
|
||||
run_status = fields.Char('机床运行状态', readonly=True)
|
||||
|
||||
#AGV运行日志
|
||||
# AGV运行日志
|
||||
agv_logs = fields.One2many('maintenance.equipment.agv.log', 'equipment_id', string='AGV运行日志')
|
||||
# 1212修改后的字段
|
||||
number_of_axles = fields.Selection(
|
||||
@@ -117,11 +119,17 @@ class SfMaintenanceEquipment(models.Model):
|
||||
# num = "%04d" % m
|
||||
# return num
|
||||
|
||||
|
||||
equipment_maintenance_standards_ids = fields.Many2many('equipment.maintenance.standards',
|
||||
'sf_maintenance_equipment_ids', string='设备维保标准')
|
||||
eq_maintenance_id = fields.Many2one('equipment.maintenance.standards', string='设备保养标准',
|
||||
domain="[('maintenance_type','=','保养')]")
|
||||
|
||||
initial_action_date = fields.Date(string='重置保养日期')
|
||||
initial_action_date_old = fields.Date(string='重置保养日期(旧)')
|
||||
next_action_date = fields.Date(string='下次预防保养')
|
||||
|
||||
initial_overhaul_date = fields.Date(string='重置维修日期')
|
||||
initial_overhaul_date_old = fields.Date(string='重置维修日期(旧)')
|
||||
overhaul_date = fields.Date(string='下次预防检修')
|
||||
overhaul_period = fields.Integer(string='预防检修频次')
|
||||
overhaul_duration = fields.Float(string='检修时长')
|
||||
@@ -129,6 +137,61 @@ class SfMaintenanceEquipment(models.Model):
|
||||
overhaul_id = fields.Many2one('equipment.maintenance.standards', string='设备检修标准',
|
||||
domain="[('maintenance_type','=','检修')]")
|
||||
|
||||
def confirm_maintenance(self):
|
||||
"""
|
||||
确认保养/检修
|
||||
"""
|
||||
context = self.env.context
|
||||
if context['type'] == '保养':
|
||||
if not self.initial_action_date:
|
||||
raise ValidationError('重置保养日期不能为空!!')
|
||||
elif self.initial_action_date < fields.Date.today():
|
||||
raise ValidationError('重置保养日期不能小于当前日期!!')
|
||||
elif context['type'] == '检修':
|
||||
if not self.initial_overhaul_date:
|
||||
raise ValidationError('重置检修日期不能为空!!')
|
||||
elif self.initial_overhaul_date < fields.Date.today():
|
||||
raise ValidationError('重置检修日期不能小于当前日期!!')
|
||||
|
||||
request_ids = self.env['maintenance.request'].search([('stage_id.done', '=', False),
|
||||
('equipment_id', '=', self.id),
|
||||
('maintenance_type', '=', 'preventive'),
|
||||
('sf_maintenance_type', '=', context['type'])])
|
||||
|
||||
if not request_ids:
|
||||
return self.create_maintenance_request(context['type'])
|
||||
else:
|
||||
return {
|
||||
"type": "ir.actions.act_window",
|
||||
"res_model": "maintenance.request.wizard",
|
||||
"views": [[False, "form"]],
|
||||
"target": "new",
|
||||
'context': {
|
||||
'equipment_id': self.id
|
||||
}
|
||||
}
|
||||
|
||||
def create_maintenance_request(self, maintenance_request_type):
|
||||
"""
|
||||
根据条件创建维保计划
|
||||
"""
|
||||
if maintenance_request_type == '保养':
|
||||
self._create_new_request(self.initial_action_date + timedelta(days=self.period))
|
||||
self.initial_action_date_old = self.initial_action_date
|
||||
elif maintenance_request_type == '检修':
|
||||
self._create_new_request1(self.initial_overhaul_date + timedelta(days=self.overhaul_period))
|
||||
self.initial_overhaul_date_old = self.initial_overhaul_date
|
||||
return {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'display_notification',
|
||||
'params': {
|
||||
'title': f'创建{maintenance_request_type}计划',
|
||||
'message': f'新{maintenance_request_type}维保计划创建成功',
|
||||
'type': 'success',
|
||||
'next': {'type': 'ir.actions.act_window_close'},
|
||||
}
|
||||
}
|
||||
|
||||
@api.onchange('eq_maintenance_id', 'overhaul_id')
|
||||
def _compute_equipment_maintenance_standards_ids(self):
|
||||
for record in self:
|
||||
@@ -179,7 +242,8 @@ class SfMaintenanceEquipment(models.Model):
|
||||
type_id = fields.Many2one('sf.machine_tool.type', '型号')
|
||||
|
||||
state = fields.Selection(
|
||||
[("正常", "正常"), ("故障停机", "故障停机"), ("计划维保", "计划维保"), ("空闲", "空闲"), ("封存(报废)", "封存(报废)")],
|
||||
[("正常", "正常"), ("故障停机", "故障停机"), ("计划维保", "计划维保"), ("空闲", "空闲"),
|
||||
("封存(报废)", "封存(报废)")],
|
||||
default='正常', string="机床状态")
|
||||
run_time = fields.Char('总运行时长')
|
||||
# 0606新增字段
|
||||
@@ -328,7 +392,7 @@ class SfMaintenanceEquipment(models.Model):
|
||||
item.tool_diameter_min = item.type_id.tool_diameter_min
|
||||
item.machine_tool_category = item.type_id.machine_tool_category.id
|
||||
item.brand_id = item.type_id.brand_id.id
|
||||
#新增修改字段
|
||||
# 新增修改字段
|
||||
item.taper_type_id = item.type_id.taper_type_id.id
|
||||
item.function_type = item.type_id.function_type
|
||||
item.a_axis = item.type_id.a_axis
|
||||
@@ -370,7 +434,6 @@ class SfMaintenanceEquipment(models.Model):
|
||||
item.image_id = item.type_id.jg_image_id.ids
|
||||
item.image_lq_id = item.type_id.lq_image_id.ids
|
||||
|
||||
|
||||
# AGV小车设备参数
|
||||
AGV_L = fields.Char('AGV尺寸(长)')
|
||||
AGV_W = fields.Char('AGV尺寸(宽)')
|
||||
@@ -461,18 +524,6 @@ class SfMaintenanceEquipment(models.Model):
|
||||
original_value = fields.Char('原值')
|
||||
incomplete_value = fields.Char('残值')
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# 注册同步机床
|
||||
def enroll_machine_tool(self):
|
||||
sf_sync_config = self.env['res.config.settings'].get_values()
|
||||
@@ -603,11 +654,13 @@ class SfMaintenanceEquipment(models.Model):
|
||||
('equipment_id', '=', equipment.id),
|
||||
('sf_maintenance_type', '=', '保养'),
|
||||
('stage_id.done', '!=', True),
|
||||
('active', '!=', False),
|
||||
('close_date', '=', False)], order="request_date asc", limit=1)
|
||||
last_maintenance_done = self.env['maintenance.request'].search([
|
||||
('equipment_id', '=', equipment.id),
|
||||
('sf_maintenance_type', '=', '保养'),
|
||||
('stage_id.done', '=', True),
|
||||
('active', '!=', False),
|
||||
('close_date', '!=', False)], order="close_date desc", limit=1)
|
||||
if next_maintenance_todo and last_maintenance_done:
|
||||
next_date = next_maintenance_todo.request_date
|
||||
@@ -636,7 +689,7 @@ class SfMaintenanceEquipment(models.Model):
|
||||
if next_date < date_now:
|
||||
next_date = date_now
|
||||
else:
|
||||
next_date = equipment.effective_date + timedelta(days=equipment.period)
|
||||
next_date = equipment.initial_action_date + timedelta(days=equipment.period)
|
||||
equipment.next_action_date = next_date
|
||||
else:
|
||||
self.next_action_date = False
|
||||
@@ -647,11 +700,13 @@ class SfMaintenanceEquipment(models.Model):
|
||||
('equipment_id', '=', equipment.id),
|
||||
('sf_maintenance_type', '=', '检修'),
|
||||
('stage_id.done', '!=', True),
|
||||
('active', '!=', False),
|
||||
('close_date', '=', False)], order="request_date asc", limit=1)
|
||||
last_maintenance_done = self.env['maintenance.request'].search([
|
||||
('equipment_id', '=', equipment.id),
|
||||
('sf_maintenance_type', '=', '检修'),
|
||||
('stage_id.done', '=', True),
|
||||
('active', '!=', False),
|
||||
('close_date', '!=', False)], order="close_date desc", limit=1)
|
||||
if next_maintenance_todo and last_maintenance_done:
|
||||
next_date = next_maintenance_todo.request_date
|
||||
@@ -680,7 +735,7 @@ class SfMaintenanceEquipment(models.Model):
|
||||
if next_date < date_now:
|
||||
next_date = date_now
|
||||
else:
|
||||
next_date = equipment.effective_date + timedelta(days=equipment.overhaul_period)
|
||||
next_date = equipment.initial_overhaul_date + timedelta(days=equipment.overhaul_period)
|
||||
equipment.overhaul_date = next_date
|
||||
else:
|
||||
self.overhaul_date = False
|
||||
@@ -747,6 +802,7 @@ class SfMaintenanceEquipment(models.Model):
|
||||
next_requests = self.env['maintenance.request'].search([('stage_id.done', '=', False),
|
||||
('equipment_id', '=', equipment.id),
|
||||
('maintenance_type', '=', 'preventive'),
|
||||
('active', '=', True),
|
||||
('request_date', '=', equipment.next_action_date),
|
||||
('sf_maintenance_type', '=', '保养')])
|
||||
if not next_requests:
|
||||
@@ -755,6 +811,7 @@ class SfMaintenanceEquipment(models.Model):
|
||||
next_requests = self.env['maintenance.request'].search([('stage_id.done', '=', False),
|
||||
('equipment_id', '=', equipment.id),
|
||||
('maintenance_type', '=', 'preventive'),
|
||||
('active', '=', True),
|
||||
('request_date', '=', equipment.overhaul_date),
|
||||
('sf_maintenance_type', '=', '检修')])
|
||||
if not next_requests:
|
||||
@@ -763,7 +820,7 @@ class SfMaintenanceEquipment(models.Model):
|
||||
image_id = fields.Many2many('maintenance.equipment.image', 'equipment_id',
|
||||
domain="[('type', '=', '加工能力')]")
|
||||
image_lq_id = fields.Many2many('maintenance.equipment.image', 'equipment_lq_id', string='冷却方式',
|
||||
domain="[('type', '=', '冷却方式')]")
|
||||
domain="[('type', '=', '冷却方式')]")
|
||||
|
||||
|
||||
class SfRobotAxisNum(models.Model):
|
||||
@@ -777,4 +834,5 @@ class SfRobotAxisNum(models.Model):
|
||||
weight = fields.Char('最大负载(kg)')
|
||||
permissible_load_torque = fields.Char('允许负载扭矩(N-m)')
|
||||
permissible_inertial_torque = fields.Char('允许惯性扭矩(kg-m²)')
|
||||
equipment_id = fields.Many2one('maintenance.equipment', string='机器人', domain="[('equipment_type', '=', '机器人')]")
|
||||
equipment_id = fields.Many2one('maintenance.equipment', string='机器人',
|
||||
domain="[('equipment_type', '=', '机器人')]")
|
||||
|
||||
@@ -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')
|
||||
|
||||
|
||||
@@ -14,6 +14,8 @@ class SfMaintenanceEquipmentCategory(models.Model):
|
||||
equipment_maintenance_id = fields.Many2one('equipment.maintenance.standards', string='设备维保标准',
|
||||
domain="[('maintenance_type','=',sf_maintenance_type)]")
|
||||
|
||||
active = fields.Boolean('有效', default=True)
|
||||
|
||||
@api.onchange('sf_maintenance_type')
|
||||
def _compute_equipment_maintenance_request_id(self):
|
||||
for record in self:
|
||||
|
||||
@@ -20,7 +20,8 @@ access_maintenance_equipment_agv_log,maintenance_equipment_agv_log,model_mainten
|
||||
|
||||
|
||||
|
||||
access_maintenance_system_user,equipment.request system user,maintenance.model_maintenance_request,base.group_user,1,0,0,0
|
||||
access_maintenance_system_user,equipment.request system user,maintenance.model_maintenance_request,base.group_user,1,1,1,0
|
||||
access_maintenance_wizard_system_user,maintenance.request.wizard system user,model_maintenance_request_wizard,base.group_user,1,1,1,0
|
||||
|
||||
access_maintenance_equipment_group_plan_dispatch,maintenance.equipment,maintenance.model_maintenance_equipment,sf_base.group_plan_dispatch,1,0,0,0
|
||||
access_maintenance_equipment_oee_group_plan_dispatch,maintenance_equipment_oee,model_maintenance_equipment_oee,sf_base.group_plan_dispatch,1,0,0,0
|
||||
@@ -67,6 +68,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 |
@@ -6,15 +6,16 @@
|
||||
<field name="name">equipment.maintenance.standards.form</field>
|
||||
<field name="model">equipment.maintenance.standards</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="设备维保标准">
|
||||
<form string="设备维保标准" delete="false" duplicate="false">
|
||||
<sheet>
|
||||
<group>
|
||||
<group>
|
||||
<field name="code" readonly="1" force_save="1"/>
|
||||
<field name="name" readonly="1" force_save="1"/>
|
||||
<field name="maintenance_equipment_category_id" required="1" />
|
||||
<field name="eq_maintenance_ids" invisible='1'/>
|
||||
<field name="overhaul_ids" invisible='1'/>
|
||||
<group>
|
||||
<field name="active" invisible="1"/>
|
||||
<field name="code" readonly="1" force_save="1"/>
|
||||
<field name="name" readonly="1" force_save="1"/>
|
||||
<field name="maintenance_equipment_category_id" required="1"/>
|
||||
<field name="eq_maintenance_ids" invisible='1'/>
|
||||
<field name="overhaul_ids" invisible='1'/>
|
||||
|
||||
|
||||
</group>
|
||||
@@ -50,7 +51,8 @@
|
||||
<field name="name">equipment.maintenance.standards.tree</field>
|
||||
<field name="model">equipment.maintenance.standards</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="设备维保标准">
|
||||
<tree string="设备维保标准" delete="false">
|
||||
<field name="active" invisible="1"/>
|
||||
<field name="code" readonly="1" force_save="1"/>
|
||||
<field name="maintenance_type" required="1"/>
|
||||
<field name="name" required="1"/>
|
||||
@@ -77,6 +79,7 @@
|
||||
<field name="name" string="日常机床保养"/>
|
||||
<field name="created_user_id" string="创建人"/>
|
||||
<field name="maintenance_equipment_category_id" string="设备类别"/>
|
||||
<filter name="filter_active" string="已归档" domain="[('active','=',False)]"/>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -60,9 +60,9 @@
|
||||
<field name="function_type"/>
|
||||
<field name="code" readonly="1"/>
|
||||
<field name="equipment_type" invisible="1"/>
|
||||
<field name="brand_id" force_save="1" />
|
||||
<field name="brand_id" force_save="1"/>
|
||||
<field name="type_id" attrs="{'required': [('equipment_type', '=', '机床')]}"
|
||||
domain="[('brand_id', '=', brand_id)]" />
|
||||
domain="[('brand_id', '=', brand_id)]"/>
|
||||
<field name="machine_tool_category" readonly="1" attrs="{'invisible': [('type_id', '=', False)]}"
|
||||
force_save="1"/>
|
||||
<field name="run_time" force_save="1"/>
|
||||
@@ -73,7 +73,7 @@
|
||||
<group>
|
||||
<group string="基础参数">
|
||||
<field name="control_system_id" attrs="{'required': [('equipment_type', '=', '机床')]}"
|
||||
options="{'no_create': True}" />
|
||||
options="{'no_create': True}"/>
|
||||
<label for="workbench_L" string="工作台尺寸(mm)"/>
|
||||
<div class="test_model">
|
||||
<label for="workbench_L" string="长"/>
|
||||
@@ -85,7 +85,7 @@
|
||||
<field name="workbench_W" class="o_address_zip"
|
||||
attrs="{'required': [('equipment_type', '=', '机床')]}"
|
||||
options="{'format': false}"/>
|
||||
<span>&nbsp;</span>
|
||||
<span>&nbsp;</span>
|
||||
<label for="workbench_H" string="高"/>
|
||||
<field name="workbench_H" class="o_address_zip"
|
||||
attrs="{'required': [('equipment_type', '=', '机床')]}"
|
||||
@@ -134,7 +134,7 @@
|
||||
<!-- <field name="guide_rail" required="1"/>-->
|
||||
<field name="number_of_axles" attrs="{'required': [('equipment_type', '=', '机床')]}"
|
||||
widget="radio"
|
||||
options="{'horizontal': true}" />
|
||||
options="{'horizontal': true}"/>
|
||||
<label for="x_axis" string="加工行程(mm)"
|
||||
attrs="{'invisible': [('number_of_axles', '=', False)]}"/>
|
||||
<div class="test_model"
|
||||
@@ -196,8 +196,8 @@
|
||||
<field name="T_tool_time"/>
|
||||
<field name="C_tool_time"/>
|
||||
</group>
|
||||
<group string="主轴">
|
||||
<field name="taper_type_id" attrs="{'required': [('equipment_type', '=', '机床')]}" />
|
||||
<group string="主轴">
|
||||
<field name="taper_type_id" attrs="{'required': [('equipment_type', '=', '机床')]}"/>
|
||||
<label for="distance_min" string="主轴端面-工作台距离(mm)"/>
|
||||
<div class="test_model">
|
||||
<label for="distance_min" string="最小(min)"/>
|
||||
@@ -237,7 +237,7 @@
|
||||
<field name="c_precision"/>
|
||||
<field name="c_precision_repeat"/>
|
||||
</group>
|
||||
<group string="进给参数">
|
||||
<group string="进给参数">
|
||||
<field name="X_axis_rapid_traverse_speed"/>
|
||||
<field name="Y_axis_rapid_traverse_speed"/>
|
||||
<field name="Z_axis_rapid_traverse_speed"/>
|
||||
@@ -252,21 +252,21 @@
|
||||
|
||||
|
||||
</page>
|
||||
<page string="AGV运行日志" name="sf_equipment"
|
||||
<page string="AGV运行日志" name="sf_equipment"
|
||||
attrs="{'invisible': [('equipment_type', '!=', 'AGV小车')]}">
|
||||
<field name="agv_logs">
|
||||
<tree create="1" edit="1" delete="1" editable="bottom">
|
||||
<field name = 'run_type'/>
|
||||
<field name = 'run_code'/>
|
||||
<field name = 'run_first'/>
|
||||
<field name = 'run_last'/>
|
||||
<field name = 'production_line'/>
|
||||
<field name = 'workorder'/>
|
||||
<field name = 'time'/>
|
||||
<field name = 'state'/>
|
||||
</tree>
|
||||
</field>
|
||||
</page>
|
||||
<field name="agv_logs">
|
||||
<tree create="1" edit="1" delete="1" editable="bottom">
|
||||
<field name='run_type'/>
|
||||
<field name='run_code'/>
|
||||
<field name='run_first'/>
|
||||
<field name='run_last'/>
|
||||
<field name='production_line'/>
|
||||
<field name='workorder'/>
|
||||
<field name='time'/>
|
||||
<field name='state'/>
|
||||
</tree>
|
||||
</field>
|
||||
</page>
|
||||
|
||||
<page string="设备参数" name="sf_equipment"
|
||||
attrs="{'invisible': [('equipment_type', '!=', 'AGV小车')]}">
|
||||
@@ -979,31 +979,58 @@
|
||||
</group>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//field[@name='next_action_date']" position="before">
|
||||
|
||||
<field name='eq_maintenance_id' force_save="1" widget="many2one"/>
|
||||
<xpath expr="//page[@name='maintenance']" position="replace">
|
||||
<page string="维保" name="maintenance">
|
||||
<group>
|
||||
<group string="保养">
|
||||
<field name='eq_maintenance_id' force_save="1" widget="many2one"/>
|
||||
<field name="initial_action_date"/>
|
||||
<field name="next_action_date" string="下次预防保养"/>
|
||||
<label for="period" string="预防保养频次"/>
|
||||
<div class="o_row">
|
||||
<field name="period"/>
|
||||
days
|
||||
</div>
|
||||
<label for="maintenance_duration" string="保养时长"/>
|
||||
<div class="o_row">
|
||||
<field name="maintenance_duration"/>
|
||||
hours
|
||||
</div>
|
||||
<div class="col-12 col-lg-6 o_setting_box" style="white-space: nowrap">
|
||||
<button name="confirm_maintenance" string="确认保养" type="object"
|
||||
class="oe_highlight" context="{'type': '保养'}"/>
|
||||
</div>
|
||||
</group>
|
||||
<group string="检修">
|
||||
<field name='overhaul_id'/>
|
||||
<field name="initial_overhaul_date"/>
|
||||
<field name="overhaul_date" string="下次预防检修"/>
|
||||
<label for="overhaul_period" string="预防检修频次"/>
|
||||
<div class="o_row">
|
||||
<field name="overhaul_period"/>
|
||||
days
|
||||
</div>
|
||||
|
||||
<label for="overhaul_duration" string="检修时长"/>
|
||||
<div class="o_row">
|
||||
<field name="overhaul_duration"/>
|
||||
hours
|
||||
</div>
|
||||
<field name='equipment_maintenance_standards_ids' widget="many2many_tags"
|
||||
invisible="1"/>
|
||||
|
||||
<div class="col-12 col-lg-6 o_setting_box" style="white-space: nowrap">
|
||||
<button name="confirm_maintenance" string="确认检修" type="object"
|
||||
class="oe_highlight" context="{'type': '检修'}"/>
|
||||
</div>
|
||||
</group>
|
||||
</group>
|
||||
|
||||
|
||||
</page>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//div[hasclass('o_row')][field[@name='maintenance_duration']]" position="after">
|
||||
|
||||
|
||||
<field name='overhaul_id' options="{'no_create':True}"/>
|
||||
<field name="overhaul_date" string="下次预防检修"/>
|
||||
<label for="overhaul_period" string="预防检修频次"/>
|
||||
<div class="o_row">
|
||||
<field name="overhaul_period"/>
|
||||
days
|
||||
</div>
|
||||
|
||||
<label for="overhaul_duration" string="检修时长"/>
|
||||
<div class="o_row">
|
||||
<field name="overhaul_duration"/>
|
||||
hours
|
||||
</div>
|
||||
<field name='equipment_maintenance_standards_ids' widget="many2many_tags" invisible="1"/>
|
||||
|
||||
</xpath>
|
||||
<xpath expr="//page[@name='description']" position="attributes">
|
||||
<attribute name="invisible">1</attribute>
|
||||
</xpath>
|
||||
@@ -1142,14 +1169,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>
|
||||
@@ -1187,7 +1216,7 @@
|
||||
<field name="name" readonly="1"/>
|
||||
<field name="type" readonly="1"/>
|
||||
<field name="image" widget="image" readonly="1"/>
|
||||
<!-- <field name="equipment_id"/>-->
|
||||
<!-- <field name="equipment_id"/>-->
|
||||
<field name="active" invisible="1"/>
|
||||
</tree>
|
||||
</field>
|
||||
@@ -1230,5 +1259,9 @@
|
||||
action="hr_equipment_action1"
|
||||
sequence="0"/>
|
||||
|
||||
<menuitem
|
||||
id="maintenance.menu_maintenance_title"
|
||||
web_icon="sf_maintenance,static/description/维护.png"/>
|
||||
|
||||
|
||||
</odoo>
|
||||
1
sf_maintenance/wizard/__init__.py
Normal file
1
sf_maintenance/wizard/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import maintenance_request_wizard
|
||||
26
sf_maintenance/wizard/maintenance_request_wizard.py
Normal file
26
sf_maintenance/wizard/maintenance_request_wizard.py
Normal file
@@ -0,0 +1,26 @@
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class MaintenanceRequestWizard(models.TransientModel):
|
||||
_name = 'maintenance.request.wizard'
|
||||
_description = '维保二次确认弹窗'
|
||||
|
||||
name = fields.Char('')
|
||||
|
||||
def submit(self):
|
||||
context = self.env.context
|
||||
equipment_id = self.env['maintenance.equipment'].sudo().search([('id', '=', context['equipment_id'])])
|
||||
request_ids = self.env['maintenance.request'].search([('stage_id.done', '=', False),
|
||||
('equipment_id', '=', equipment_id.id),
|
||||
('maintenance_type', '=', 'preventive'),
|
||||
('sf_maintenance_type', '=', context['type'])])
|
||||
request_ids.write({'active': False})
|
||||
return equipment_id.create_maintenance_request(context['type'])
|
||||
|
||||
def cancel(self):
|
||||
context = self.env.context
|
||||
equipment_id = self.env['maintenance.equipment'].sudo().search([('id', '=', context['equipment_id'])])
|
||||
if context['type'] == '保养':
|
||||
equipment_id.initial_action_date = equipment_id.initial_action_date_old
|
||||
elif context['type'] == '检修':
|
||||
equipment_id.initial_overhaul_date = equipment_id.initial_overhaul_date_old
|
||||
29
sf_maintenance/wizard/maintenance_request_wizard.xml
Normal file
29
sf_maintenance/wizard/maintenance_request_wizard.xml
Normal file
@@ -0,0 +1,29 @@
|
||||
<openerp>
|
||||
<data>
|
||||
<record id="action_maintenance_request_wizard" model="ir.actions.act_window">
|
||||
<field name="name">维保计划</field>
|
||||
<field name="res_model">maintenance.request.wizard</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="maintenance_request_wizard_form_view">
|
||||
<field name="name">maintenance.request.wizard.form.view</field>
|
||||
<field name="model">maintenance.request.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<div>
|
||||
<field name="name" invisible="1"/>
|
||||
有未执行的历史维保计划,是否创建新维保计划!!
|
||||
</div>
|
||||
<footer>
|
||||
<button string="确认" name="submit" type="object" class="oe_highlight"/>
|
||||
<button string="取消" name="cancel" type="object" class="oe_highlight"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
@@ -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}
|
||||
@@ -477,7 +478,7 @@ class Manufacturing_Connect(http.Controller):
|
||||
logging.info('LocationChange error:%s' % e)
|
||||
return json.JSONEncoder().encode(res)
|
||||
|
||||
@http.route('/AutoDeviceApi/AGVToProduct', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False,
|
||||
@http.route('/AutoDeviceApi/AGVToProduct', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
|
||||
cors="*")
|
||||
def AGVToProduct(self, **kw):
|
||||
"""
|
||||
@@ -549,7 +550,7 @@ class Manufacturing_Connect(http.Controller):
|
||||
logging.info('AGVToProduct error:%s' % e)
|
||||
return json.JSONEncoder().encode(res)
|
||||
|
||||
@http.route('/AutoDeviceApi/AGVDownProduct', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False,
|
||||
@http.route('/AutoDeviceApi/AGVDownProduct', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
|
||||
cors="*")
|
||||
def AGVDownProduct(self, **kw):
|
||||
"""
|
||||
@@ -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']}
|
||||
@@ -668,7 +661,7 @@ class Manufacturing_Connect(http.Controller):
|
||||
logging.info('AGVDownProduct error:%s' % e)
|
||||
return json.JSONEncoder().encode(res)
|
||||
|
||||
@http.route('/AutoDeviceApi/AgvStationState', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False,
|
||||
@http.route('/AutoDeviceApi/AgvStationState', type='json', auth='public', methods=['GET', 'POST'], csrf=False,
|
||||
cors="*")
|
||||
def AGVStationState(self, **kw):
|
||||
"""
|
||||
@@ -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)
|
||||
|
||||
@@ -8,7 +8,7 @@ from odoo.http import request
|
||||
|
||||
class Workpiece(http.Controller):
|
||||
|
||||
@http.route('/agvApi/backfeed', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False,
|
||||
@http.route('/agvApi/backfeed', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
|
||||
cors="*")
|
||||
def backfeed(self, **kw):
|
||||
"""
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
|
||||
import logging
|
||||
import requests
|
||||
|
||||
from odoo import models, fields, api, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
import logging
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ class AgvScheduling(models.Model):
|
||||
def web_search_read(self, domain=None, fields=None, offset=0, limit=None, order=None, count_limit=None):
|
||||
domain = domain or []
|
||||
new_domain = []
|
||||
for index, item in enumerate(domain):
|
||||
for item in domain:
|
||||
if isinstance(item, list):
|
||||
if item[0] == 'delivery_workpieces':
|
||||
new_domain.append('&')
|
||||
@@ -63,7 +63,7 @@ class AgvScheduling(models.Model):
|
||||
continue
|
||||
new_domain.append(item)
|
||||
|
||||
return super(AgvScheduling, self).web_search_read(new_domain, fields, limit=limit, offset=offset)
|
||||
return super(AgvScheduling, self).web_search_read(new_domain, fields, offset, limit, order, count_limit)
|
||||
|
||||
@api.depends('task_completion_time', 'task_delivery_time')
|
||||
def _compute_task_duration(self):
|
||||
@@ -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()
|
||||
@@ -265,3 +274,18 @@ class ResMrpWorkOrder(models.Model):
|
||||
'sf_agv_scheduling_mrp_workorder_ref',
|
||||
string='AGV调度',
|
||||
domain=[('state', '!=', '已取消')])
|
||||
|
||||
def get_down_product_agv_scheduling(self):
|
||||
"""
|
||||
获取关联的制造订单下产线的agv任务
|
||||
"""
|
||||
self.ensure_one()
|
||||
workorder_ids = self.production_id.workorder_ids
|
||||
cnc_workorder = workorder_ids.filtered(
|
||||
lambda w: w.routing_type == 'CNC加工' and w.state == 'done' and w.processing_panel == self.processing_panel
|
||||
)
|
||||
if cnc_workorder:
|
||||
agv_schedulingss = cnc_workorder.agv_scheduling_ids
|
||||
return agv_schedulingss.filtered(lambda a: a.state == '已配送' and a.agv_route_type == '下产线')
|
||||
else:
|
||||
return None
|
||||
|
||||
@@ -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,29 @@ 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:
|
||||
# 确保 procurement_group_id 和相关字段存在
|
||||
if production.procurement_group_id:
|
||||
# 获取相关的 sale_id
|
||||
sale_order_id = production.procurement_group_id.mrp_production_ids.mapped(
|
||||
'move_dest_ids.group_id.sale_id')
|
||||
|
||||
# 确保 sale_order_id 是有效的 ID 列表
|
||||
if sale_order_id:
|
||||
# 获取 sale.order 记录
|
||||
sale_id = self.env['sale.order'].sudo().browse(sale_order_id.ids) # 使用 mapped 返回的 ID 列表
|
||||
|
||||
# 处理 sale_id
|
||||
if sale_id:
|
||||
# 假设我们只需要第一个 sale_id
|
||||
production.deadline_of_delivery = sale_id[0].deadline_of_delivery if sale_id else False
|
||||
else:
|
||||
production.deadline_of_delivery = False
|
||||
else:
|
||||
production.deadline_of_delivery = False
|
||||
|
||||
@api.depends('workorder_ids.tool_state_remark')
|
||||
def _compute_tool_state_remark(self):
|
||||
for item in self:
|
||||
@@ -118,10 +141,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 +321,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'])
|
||||
@@ -318,8 +348,10 @@ class MrpProduction(models.Model):
|
||||
# cnc程序获取
|
||||
def fetchCNC(self, production_names):
|
||||
cnc = self.env['mrp.production'].search([('id', '=', self.id)])
|
||||
quick_order = self.env['quick.easy.order'].search(
|
||||
[('name', '=', cnc.product_id.default_code.rsplit('-', 1)[0])])
|
||||
quick_order = False
|
||||
if cnc.product_id.default_code:
|
||||
quick_order = self.env['quick.easy.order'].search(
|
||||
[('name', '=', cnc.product_id.default_code.rsplit('-', 1)[0])])
|
||||
programme_way = False
|
||||
if cnc.manual_quotation is True:
|
||||
programme_way = 'manual operation'
|
||||
@@ -779,7 +811,8 @@ class MrpProduction(models.Model):
|
||||
routing_workcenter = self.env['mrp.routing.workcenter'].sudo().search(
|
||||
[('name', '=', work.routing_type)])
|
||||
|
||||
work.write({'date_planned_start': date_planned_start, 'date_planned_finished': date_planned_end,'duration_expected':routing_workcenter.time_cycle})
|
||||
work.write({'date_planned_start': date_planned_start, 'date_planned_finished': date_planned_end,
|
||||
'duration_expected': routing_workcenter.time_cycle})
|
||||
|
||||
# 修改标记已完成方法
|
||||
def button_mark_done1(self):
|
||||
@@ -803,6 +836,10 @@ class MrpProduction(models.Model):
|
||||
backorders = backorders - productions_to_backorder
|
||||
|
||||
productions_not_to_backorder._post_inventory(cancel_backorder=True)
|
||||
# if self.workorder_ids.filtered(lambda w: w.routing_type in ['表面工艺']):
|
||||
# move_finish = self.env['stock.move'].search([('created_production_id', '=', self.id)])
|
||||
# if move_finish:
|
||||
# move_finish._action_assign()
|
||||
productions_to_backorder._post_inventory(cancel_backorder=True)
|
||||
|
||||
# if completed products make other confirmed/partially_available moves available, assign them
|
||||
@@ -936,6 +973,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:
|
||||
@@ -948,6 +987,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:
|
||||
@@ -983,8 +1029,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)
|
||||
@@ -1078,27 +1124,22 @@ class MrpProduction(models.Model):
|
||||
productions.write({'programming_no': self.programming_no, 'is_remanufacture': True})
|
||||
# productions.procurement_group_id.mrp_production_ids.move_dest_ids.write(
|
||||
# {'group_id': self.env['procurement.group'].search([('name', '=', sale_order.name)])})
|
||||
stock_picking = None
|
||||
pc_picking = self.env['stock.picking'].search(
|
||||
[('origin', '=', productions.name), ('name', 'ilike', 'WH/PC/')])
|
||||
stock_picking = pc_picking
|
||||
int_picking = self.env['stock.picking'].search(
|
||||
[('origin', '=', productions.name), ('name', 'ilike', 'WH/INT/')])
|
||||
stock_picking |= int_picking
|
||||
for pick in stock_picking:
|
||||
if pick.move_ids:
|
||||
product_type_id = pick.move_ids[0].product_id.categ_id
|
||||
if product_type_id.name == '坯料':
|
||||
location_id = self.env['stock.location'].search([('name', '=', '坯料存货区')])
|
||||
if not location_id:
|
||||
logging.info(f'没有搜索到【坯料存货区】: {location_id}')
|
||||
break
|
||||
if pick.picking_type_id.name == '内部调拨':
|
||||
if pick.location_dest_id.product_type != product_type_id:
|
||||
pick.location_dest_id = location_id.id
|
||||
elif pick.picking_type_id.name == '生产发料':
|
||||
if pick.location_id.product_type != product_type_id:
|
||||
pick.location_id = location_id.id
|
||||
stock_picking_remanufacture = self.env['stock.picking'].search([('origin', '=', productions.name)])
|
||||
for pick in stock_picking_remanufacture:
|
||||
if pick.name.startswith('WH/PC/') or pick.name.startswith('WH/INT/'):
|
||||
if pick.move_ids:
|
||||
product_type_id = pick.move_ids[0].product_id.categ_id
|
||||
if product_type_id.name == '坯料':
|
||||
location_id = self.env['stock.location'].search([('name', '=', '坯料存货区')])
|
||||
if not location_id:
|
||||
logging.info(f'没有搜索到【坯料存货区】: {location_id}')
|
||||
break
|
||||
if pick.picking_type_id.name == '内部调拨':
|
||||
if pick.location_dest_id.product_type != product_type_id:
|
||||
pick.location_dest_id = location_id.id
|
||||
elif pick.picking_type_id.name == '生产发料':
|
||||
if pick.location_id.product_type != product_type_id:
|
||||
pick.location_id = location_id.id
|
||||
scarp_process_parameter_workorder = self.env['mrp.workorder'].search(
|
||||
[('surface_technics_parameters_id', '!=', False), ('production_id', '=', self.id),
|
||||
('is_subcontract', '=', True)])
|
||||
@@ -1111,7 +1152,6 @@ class MrpProduction(models.Model):
|
||||
for process_item in scarp_process_parameter_workorder:
|
||||
if purchase_item.product_id.categ_type == '表面工艺':
|
||||
if purchase_item.product_id.server_product_process_parameters_id == process_item.surface_technics_parameters_id:
|
||||
print(purchase_orders.origin.find(productions.name))
|
||||
if purchase_orders.origin.find(productions.name) == -1:
|
||||
purchase_orders.origin += ',' + productions.name
|
||||
if item['is_reprogramming'] is False:
|
||||
|
||||
@@ -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,7 +1,13 @@
|
||||
import datetime
|
||||
import logging
|
||||
from datetime import timedelta, time
|
||||
from collections import defaultdict
|
||||
from odoo import fields, models
|
||||
from odoo import fields, models, api
|
||||
from odoo.addons.resource.models.resource import Intervals
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
import math
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ResWorkcenter(models.Model):
|
||||
@@ -41,14 +47,16 @@ class ResWorkcenter(models.Model):
|
||||
|
||||
oee_target = fields.Float(
|
||||
string='OEE Target', help="Overall Effective Efficiency Target in percentage", default=90, tracking=True)
|
||||
oee = fields.Float(compute='_compute_oee', help='Overall Equipment Effectiveness, based on the last month', store=True)
|
||||
oee = fields.Float(compute='_compute_oee', help='Overall Equipment Effectiveness, based on the last month',
|
||||
store=True)
|
||||
|
||||
time_start = fields.Float('Setup Time', tracking=True)
|
||||
time_stop = fields.Float('Cleanup Time', tracking=True)
|
||||
costs_hour = fields.Float(string='Cost per hour', help='Hourly processing cost.', default=0.0, tracking=True)
|
||||
|
||||
equipment_status = fields.Selection(
|
||||
[("正常", "正常"), ("故障停机", "故障停机"), ("计划维保", "计划维保"), ("空闲", "空闲"), ("封存(报废)", "封存(报废)")],
|
||||
[("正常", "正常"), ("故障停机", "故障停机"), ("计划维保", "计划维保"), ("空闲", "空闲"),
|
||||
("封存(报废)", "封存(报废)")],
|
||||
string="设备状态", related='equipment_id.state')
|
||||
|
||||
# @api.depends('equipment_id')
|
||||
@@ -127,6 +135,128 @@ class ResWorkcenter(models.Model):
|
||||
|
||||
# AGV是否可配送
|
||||
is_agv_scheduling = fields.Boolean(string="AGV所属区域", tracking=True)
|
||||
# 生产线优化
|
||||
available_machine_number = fields.Integer(string="可用机台数量")
|
||||
single_machine_capacity = fields.Float(string="单台小时产能")
|
||||
production_line_hour_capacity = fields.Float(string="生产线小时产能", readonly=True,
|
||||
compute='_compute_production_line_hour_capacity')
|
||||
effective_working_hours_day = fields.Float(string="日有效工作时长", default=0, readonly=True,
|
||||
compute='_compute_effective_working_hours_day')
|
||||
default_capacity = fields.Float(
|
||||
string='生产线日产能', compute='_compute_production_line_day_capacity', readonly=True)
|
||||
|
||||
# 计算生产线日产能
|
||||
@api.depends('production_line_hour_capacity', 'effective_working_hours_day')
|
||||
def _compute_production_line_day_capacity(self):
|
||||
for record in self:
|
||||
record.default_capacity = round(
|
||||
record.production_line_hour_capacity * record.effective_working_hours_day, 2)
|
||||
|
||||
# 计算日有效工作时长
|
||||
@api.depends('resource_calendar_id', 'resource_calendar_id.attendance_ids',
|
||||
'resource_calendar_id.attendance_ids.hour_to', 'resource_calendar_id.attendance_ids.hour_from')
|
||||
def _compute_effective_working_hours_day(self):
|
||||
for record in self:
|
||||
attendance_ids = [p for p in record.resource_calendar_id.attendance_ids if
|
||||
p.dayofweek == self.get_current_day_of_week(datetime.datetime.now())]
|
||||
if attendance_ids:
|
||||
for attendance_id in attendance_ids:
|
||||
if attendance_id.hour_from and attendance_id.hour_to:
|
||||
record.effective_working_hours_day += attendance_id.hour_to - attendance_id.hour_from
|
||||
else:
|
||||
record.effective_working_hours_day = 0
|
||||
|
||||
# 计算传入时间日有效工作时长
|
||||
def _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()
|
||||
return str(day_num)
|
||||
|
||||
# 计算生产线小时产能
|
||||
@api.depends('single_machine_capacity', 'available_machine_number')
|
||||
def _compute_production_line_hour_capacity(self):
|
||||
for record in self:
|
||||
record.production_line_hour_capacity = round(
|
||||
record.single_machine_capacity * record.available_machine_number, 2)
|
||||
|
||||
# 判断计划开始时间是否在配置的工作中心的工作日历内
|
||||
def deal_with_workcenter_calendar(self, start_date):
|
||||
start_date = start_date + timedelta(hours=8) # 转换为北京时间
|
||||
for record in self:
|
||||
attendance_ids = [p for p in record.resource_calendar_id.attendance_ids if
|
||||
p.dayofweek == record.get_current_day_of_week(start_date) and self.is_between_times(
|
||||
p.hour_from, p.hour_to, start_date)]
|
||||
return False if not attendance_ids else True
|
||||
|
||||
# 判断传入时间是否在配置的工作中心的工作日历内
|
||||
def is_between_times(self, hour_from, hour_to, start_date):
|
||||
integer_part, decimal_part = self.get_integer_and_decimal_parts(hour_from)
|
||||
start_time = time(integer_part, decimal_part)
|
||||
integer_part, decimal_part = self.get_integer_and_decimal_parts(hour_to)
|
||||
end_time = time(integer_part, decimal_part)
|
||||
return start_time <= start_date.time() <= end_time
|
||||
|
||||
# 获取整数部分和小数部分
|
||||
def get_integer_and_decimal_parts(self, value):
|
||||
integer_part = int(value)
|
||||
decimal_part = value - integer_part
|
||||
if decimal_part > 0:
|
||||
decimal_part = round(decimal_part, 2) * 60
|
||||
return int(integer_part), math.ceil(decimal_part)
|
||||
|
||||
# 处理排程是否超过日产能
|
||||
def deal_available_default_capacity(self, date_planned):
|
||||
date_planned_start = date_planned.strftime('%Y-%m-%d')
|
||||
date_planned_end = date_planned + timedelta(days=1)
|
||||
date_planned_end = date_planned_end.strftime('%Y-%m-%d')
|
||||
plan_ids = self.env['sf.production.plan'].sudo().search([('date_planned_start', '>=', date_planned_start),
|
||||
('date_planned_start', '<',
|
||||
date_planned_end),
|
||||
('state', 'not in', ['draft', 'cancel'])])
|
||||
if plan_ids:
|
||||
sum_qty = sum([p.product_qty for p in plan_ids])
|
||||
date_planned_working_hours = self._compute_effective_working_hours_day1(date_planned)
|
||||
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, count):
|
||||
|
||||
date_planned_start = date_planned.strftime('%Y-%m-%d %H:00:00')
|
||||
date_planned_end = date_planned + timedelta(hours=1)
|
||||
date_planned_end = date_planned_end.strftime('%Y-%m-%d %H:00:00')
|
||||
plan_ids = self.env['sf.production.plan'].sudo().search([('date_planned_start', '>=', date_planned_start),
|
||||
('date_planned_start', '<',
|
||||
date_planned_end),
|
||||
('state', 'not in', ['draft', 'cancel'])])
|
||||
|
||||
if plan_ids:
|
||||
sum_qty = sum([p.product_qty for p in plan_ids])
|
||||
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
|
||||
|
||||
|
||||
class ResWorkcenterProductivity(models.Model):
|
||||
_inherit = 'mrp.workcenter.productivity'
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user