Compare commits
729 Commits
release/re
...
feature/ne
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b23589b73d | ||
|
|
29864b8bb2 | ||
|
|
aa5294f110 | ||
|
|
4387d343b5 | ||
|
|
ff979aa34b | ||
|
|
21af368068 | ||
|
|
d6afd556d3 | ||
|
|
778d2c4ec5 | ||
|
|
fff29b36ee | ||
|
|
fedb79a142 | ||
|
|
d06816052f | ||
|
|
5739e167f4 | ||
|
|
a249548a20 | ||
|
|
7b6538d01e | ||
|
|
7025bde56e | ||
|
|
3585af0cea | ||
|
|
5406224c6f | ||
|
|
18ff0bba54 | ||
|
|
7c13e57ea2 | ||
|
|
b8605b9a97 | ||
|
|
4baa7a9b44 | ||
|
|
3af2f3e03b | ||
|
|
dc68ecb584 | ||
|
|
421f107728 | ||
|
|
1f1bdd08ed | ||
|
|
47d886ef6d | ||
|
|
1a7059aad1 | ||
|
|
8d461e61b3 | ||
|
|
fa0ca83709 | ||
|
|
a783f9c271 | ||
|
|
b4901e89d6 | ||
|
|
6690d3f2cb | ||
|
|
a7d97f137b | ||
|
|
21d6989182 | ||
|
|
97ad3567e3 | ||
|
|
22a69df31b | ||
|
|
c0ad9d8977 | ||
|
|
a927d0e9cb | ||
|
|
42fe08384a | ||
|
|
eff09e9b52 | ||
|
|
5759ca1ca5 | ||
|
|
9955157dd3 | ||
|
|
d42de42709 | ||
|
|
f6aa07fbe7 | ||
|
|
a7dc3a1227 | ||
|
|
9c5f525e90 | ||
|
|
db8bd659c3 | ||
|
|
0dab394d1b | ||
|
|
9a9e47b4ff | ||
|
|
deb2f6ca64 | ||
|
|
5bce9bb816 | ||
|
|
ccda6b627d | ||
|
|
20cfbf7230 | ||
|
|
805eabd07e | ||
|
|
649dfe90d6 | ||
|
|
c42c053d66 | ||
|
|
d8e51f44ac | ||
|
|
0b5645496b | ||
|
|
2c37dbf5c4 | ||
|
|
cd6dc7a47e | ||
|
|
7362f0cd30 | ||
|
|
de2924883c | ||
|
|
942aa469e3 | ||
|
|
d57cc59115 | ||
|
|
f8ff3a18a0 | ||
|
|
96171013d6 | ||
|
|
d741d6ccf5 | ||
|
|
8ff73d5647 | ||
|
|
5d3d04b5d3 | ||
|
|
75ed1a6889 | ||
|
|
9bfbea11db | ||
|
|
5eea260618 | ||
|
|
f1e70d2c66 | ||
|
|
da22cb301e | ||
|
|
2b93f2a253 | ||
|
|
8ab42ff7da | ||
|
|
9bf57005f0 | ||
|
|
a80d498eab | ||
|
|
4847596072 | ||
|
|
d4ba58b55a | ||
|
|
306d5074f2 | ||
|
|
745abaab2e | ||
|
|
c22b91f9d9 | ||
|
|
0bea93ea46 | ||
|
|
ecf683e663 | ||
|
|
0af7ef905d | ||
|
|
c169c0a80b | ||
|
|
878d62f483 | ||
|
|
8a33cea8bd | ||
|
|
d6c194eaa8 | ||
|
|
13d32c26b6 | ||
|
|
df146a4b58 | ||
|
|
4a02034ee3 | ||
|
|
acb24d69e3 | ||
|
|
56cfeac214 | ||
|
|
b86ad6e5bb | ||
|
|
f57c95496a | ||
|
|
826b2c3e94 | ||
|
|
29b43c6ab8 | ||
|
|
a434227807 | ||
|
|
3a7fb81276 | ||
|
|
12224fc892 | ||
|
|
64fdedf726 | ||
|
|
d3b61f320f | ||
|
|
6be7c05415 | ||
|
|
92b924754d | ||
|
|
40ddfdd48f | ||
|
|
afa5ad36f2 | ||
|
|
916a5c1054 | ||
|
|
ea914be4c7 | ||
|
|
e6eb41ad24 | ||
|
|
43ee2d1836 | ||
|
|
3bd4e6723e | ||
|
|
0e763c2ca6 | ||
|
|
34353e3614 | ||
|
|
d91c7b22d7 | ||
|
|
ac4861c76e | ||
|
|
6edd124556 | ||
|
|
f426183d22 | ||
|
|
33db91f799 | ||
|
|
8803380a30 | ||
|
|
b5beaad7bf | ||
|
|
304259c653 | ||
|
|
5ccc836b20 | ||
|
|
087bb0e8fe | ||
|
|
09a476ffa5 | ||
|
|
429ad2b77c | ||
|
|
c215e3dc27 | ||
|
|
0a1da79487 | ||
|
|
42e4e15cdb | ||
|
|
a3e7a23979 | ||
|
|
430a628e18 | ||
|
|
5f70690af5 | ||
|
|
b852933a9e | ||
|
|
074c45aa3d | ||
|
|
a52545d19a | ||
|
|
3e55bb4717 | ||
|
|
be408b98de | ||
|
|
12cc5fd0d2 | ||
|
|
589df289be | ||
|
|
7abc88f2c3 | ||
|
|
f318dc758b | ||
|
|
6628e22ac0 | ||
|
|
c4cf4e2392 | ||
|
|
56dfb98df5 | ||
|
|
4251e6dec7 | ||
|
|
bcc9f0301c | ||
|
|
17f2571be3 | ||
|
|
a9b24f7961 | ||
|
|
321125adf6 | ||
|
|
5c0db45223 | ||
|
|
76cc8845d2 | ||
|
|
38031a2537 | ||
|
|
1fe64e2b5d | ||
|
|
d572529a9d | ||
|
|
cb29baa698 | ||
|
|
696d0e2a40 | ||
|
|
75bbcd23a4 | ||
|
|
25002406b8 | ||
|
|
ed3ea32f45 | ||
|
|
63b1f732db | ||
|
|
84ebce0786 | ||
|
|
ed747ae4d9 | ||
|
|
8a103a7555 | ||
|
|
e8c166be31 | ||
|
|
d887a89988 | ||
|
|
cb02319453 | ||
|
|
7f5f65f07f | ||
|
|
c45200aaee | ||
|
|
d2351a67b0 | ||
|
|
52873021cb | ||
|
|
3f276f4177 | ||
|
|
f01a300aae | ||
|
|
8e11ef6e72 | ||
|
|
52c0df9b83 | ||
|
|
33b9c48e87 | ||
|
|
aa2f562ca8 | ||
|
|
77ce078e91 | ||
|
|
5450c9996f | ||
|
|
70f8aadd66 | ||
|
|
119083ac2b | ||
|
|
dd768e938c | ||
|
|
611aa0fe05 | ||
|
|
73564d574c | ||
|
|
ea5231adff | ||
|
|
2b3da48e9b | ||
|
|
2632c4a5a0 | ||
|
|
2420987f95 | ||
|
|
d80f3c4d5c | ||
|
|
5cb726bb2d | ||
|
|
f6b8fa5665 | ||
|
|
113d0abb4b | ||
|
|
3f3c494f7f | ||
|
|
3632a3f98f | ||
|
|
73402d1c5a | ||
|
|
4e41efd423 | ||
|
|
0b931f8255 | ||
|
|
b49135f5f9 | ||
|
|
ed6d28cc21 | ||
|
|
9857787d5a | ||
|
|
c6e62f65b6 | ||
|
|
6c0b034147 | ||
|
|
c0d520a47f | ||
|
|
de11c57800 | ||
|
|
d4ff4be32e | ||
|
|
48371b01cd | ||
|
|
310d8b33df | ||
|
|
6d61e45028 | ||
|
|
fff22a74b1 | ||
|
|
3588288434 | ||
|
|
7408cc00f9 | ||
|
|
9b3d1170b0 | ||
|
|
a774c74e6a | ||
|
|
1d53f47488 | ||
|
|
60cc49238c | ||
|
|
b415278997 | ||
|
|
ac32494944 | ||
|
|
8c81315c33 | ||
|
|
1d66c1dbea | ||
|
|
085d26669f | ||
|
|
bb7b72b540 | ||
|
|
ded0d06f86 | ||
|
|
fe08ee4173 | ||
|
|
25ee849796 | ||
|
|
bafcf35ac9 | ||
|
|
cc1455b22c | ||
|
|
1d38334c81 | ||
|
|
13d99b1bcc | ||
|
|
269be36d86 | ||
|
|
ebd76afb29 | ||
|
|
a1807e05db | ||
|
|
11866629c5 | ||
|
|
f8f5286568 | ||
|
|
36908f13d3 | ||
|
|
a7ed317b09 | ||
|
|
fe4346b802 | ||
|
|
23da273af1 | ||
|
|
68fdea4520 | ||
|
|
c5ac62060d | ||
|
|
350cf0a74b | ||
|
|
e204d693cf | ||
|
|
a6422330b6 | ||
|
|
230e87a9f1 | ||
|
|
e634e9726f | ||
|
|
e8d07abe05 | ||
|
|
808bbd7b22 | ||
|
|
fabcca1188 | ||
|
|
97c8b1dcc1 | ||
|
|
f99a35932f | ||
|
|
ce7dfdbd36 | ||
|
|
d53f3837c3 | ||
|
|
1cb646ec48 | ||
|
|
c73706555f | ||
|
|
21f961d893 | ||
|
|
08a682143f | ||
|
|
ebdeb95b0e | ||
|
|
0f26cac68d | ||
|
|
45c96276ff | ||
|
|
a2855f1f08 | ||
|
|
e8c006d495 | ||
|
|
9671efeced | ||
|
|
1e3fd6bc09 | ||
|
|
73aa4af118 | ||
|
|
38a8a298b5 | ||
|
|
bc0b8bdecb | ||
|
|
a8e9fe40ac | ||
|
|
5c5606749a | ||
|
|
8add4b5ad1 | ||
|
|
f579c8928c | ||
|
|
9410ca82db | ||
|
|
73c986cd50 | ||
|
|
f8a9a38778 | ||
|
|
9d723c5e00 | ||
|
|
722a044707 | ||
|
|
967effe8f1 | ||
|
|
5f5877699c | ||
|
|
4787cb451d | ||
|
|
811a79c7c1 | ||
|
|
153f599fe8 | ||
|
|
f6f9d14f0a | ||
|
|
018d51e25f | ||
|
|
ac03192119 | ||
|
|
b8d5d655ea | ||
|
|
00f986cdb5 | ||
|
|
5dc5c814c8 | ||
|
|
c64ede58fd | ||
|
|
6e17bb933f | ||
|
|
99aa161834 | ||
|
|
ed641c85b3 | ||
|
|
cf7756f51e | ||
|
|
a75269b3a3 | ||
|
|
6b184d7df3 | ||
|
|
daba7d5155 | ||
|
|
33a1837274 | ||
|
|
6b3e3aa49c | ||
|
|
a7b5fc898e | ||
|
|
9a9bfd198b | ||
|
|
2446065172 | ||
|
|
7565094077 | ||
|
|
ac2708404a | ||
|
|
63b9f495e9 | ||
|
|
012bb9fd54 | ||
|
|
7bcb54861b | ||
|
|
2f64a3a612 | ||
|
|
5365211f49 | ||
|
|
e38407c0a5 | ||
|
|
13ef106c0e | ||
|
|
433f3bd2dc | ||
|
|
ff072a32bb | ||
|
|
de7098a7d4 | ||
|
|
205bfd0538 | ||
|
|
37538141bf | ||
|
|
975ced08cb | ||
|
|
d646019c93 | ||
|
|
efa1e2b60f | ||
|
|
8bbc65ef88 | ||
|
|
e1121cc3b5 | ||
|
|
026bf17a68 | ||
|
|
fdcd825142 | ||
|
|
317f44d6c2 | ||
|
|
7d5c7bceb1 | ||
|
|
359fd40ed3 | ||
|
|
13a930b59c | ||
|
|
359a246793 | ||
|
|
2b658d7be0 | ||
|
|
cd9f485fc0 | ||
|
|
dda627f20a | ||
|
|
e8858559f6 | ||
|
|
d68a9249c6 | ||
|
|
eebb143778 | ||
|
|
a12eb12678 | ||
|
|
f5f67c52af | ||
|
|
3df2306e1b | ||
|
|
5dae0ea973 | ||
|
|
e7b18233f0 | ||
|
|
e55e42fe6b | ||
|
|
741f2ad8b2 | ||
|
|
1b87a81a5a | ||
|
|
7042eb0d7b | ||
|
|
28788e2a81 | ||
|
|
0f37b45b30 | ||
|
|
38c4aba45c | ||
|
|
3b686a8272 | ||
|
|
2798ce6183 | ||
|
|
e981d68805 | ||
|
|
cd3ab04728 | ||
|
|
d419efa3c3 | ||
|
|
8b4eb325ef | ||
|
|
330726007d | ||
|
|
4d7b1f557b | ||
|
|
1699f61d3f | ||
|
|
a0eec00749 | ||
|
|
f5365b4a3a | ||
|
|
8b9aa1c454 | ||
|
|
9a4d7e87ec | ||
|
|
6679c200e1 | ||
|
|
01df55ef35 | ||
|
|
d2b028ed4e | ||
|
|
de1b97bfb7 | ||
|
|
111c0f6dae | ||
|
|
11da893b17 | ||
|
|
b434329fc4 | ||
|
|
72998f04c6 | ||
|
|
23db164452 | ||
|
|
1f11d188c1 | ||
|
|
fa30e0c1da | ||
|
|
5106768330 | ||
|
|
6c0d9486fc | ||
|
|
211d74d9ff | ||
|
|
84db3a92be | ||
|
|
487a2936c3 | ||
|
|
aa9bdfe372 | ||
|
|
6134ac3b4c | ||
|
|
85c24e3d94 | ||
|
|
106945fba7 | ||
|
|
8ea72198ea | ||
|
|
41cd76ad14 | ||
|
|
e20510bc4f | ||
|
|
1d14ab27af | ||
|
|
7d6699fe7a | ||
|
|
fd55abec72 | ||
|
|
76597f6ccb | ||
|
|
d7779e969d | ||
|
|
9d28eccd1b | ||
|
|
27339afaae | ||
|
|
8dc3f2698b | ||
|
|
4fd7ad8d03 | ||
|
|
3bc33fe2fc | ||
|
|
c21319a127 | ||
|
|
02e21a8fde | ||
|
|
2cf578de65 | ||
|
|
9665cdfc84 | ||
|
|
bef7376827 | ||
|
|
e73e9d6960 | ||
|
|
892f5d0e70 | ||
|
|
61339da204 | ||
|
|
25db392864 | ||
|
|
745fd429c2 | ||
|
|
5c59a27a81 | ||
|
|
9bac3675f6 | ||
|
|
b4fbbd48fb | ||
|
|
3559d67c05 | ||
|
|
816623b1fe | ||
|
|
91b913a7bd | ||
|
|
ba8393a8c7 | ||
|
|
06d061165b | ||
|
|
18a7dbbff9 | ||
|
|
dc545e6d49 | ||
|
|
1467dbf88c | ||
|
|
e9caf78b81 | ||
|
|
aa1353cf99 | ||
|
|
14d49f2944 | ||
|
|
8560069b19 | ||
|
|
8dc23a638f | ||
|
|
537fbf7e14 | ||
|
|
5363557faf | ||
|
|
3e684a308c | ||
|
|
3c026004f3 | ||
|
|
651c8f266b | ||
|
|
a3bfbe66c4 | ||
|
|
563e40adc6 | ||
|
|
f88a784a87 | ||
|
|
7227e1e59c | ||
|
|
01df5e86cd | ||
|
|
41f853b6c9 | ||
|
|
906a0ea767 | ||
|
|
6162c731f2 | ||
|
|
4f1d518ef3 | ||
|
|
06eaebd098 | ||
|
|
8578a9a538 | ||
|
|
b9039ef466 | ||
|
|
a97f581c99 | ||
|
|
036179ec4d | ||
|
|
d33188dedf | ||
|
|
7033f95273 | ||
|
|
00689d76cc | ||
|
|
86d97ef331 | ||
|
|
05a183f46f | ||
|
|
50959d2e9c | ||
|
|
519ce0d5bb | ||
|
|
f06842cdcb | ||
|
|
d5a1caad10 | ||
|
|
e51fff4120 | ||
|
|
2fe8e0e356 | ||
|
|
bbe2e5792f | ||
|
|
a1a520ec39 | ||
|
|
1b714a9962 | ||
|
|
9d133096c7 | ||
|
|
69f3453134 | ||
|
|
ab10271b4d | ||
|
|
516a98e102 | ||
|
|
390213881a | ||
|
|
ac2eba4734 | ||
|
|
b64893436b | ||
|
|
ee148239d2 | ||
|
|
4cc03aee9b | ||
|
|
31a703952c | ||
|
|
69c63f708d | ||
|
|
ae08c5b56e | ||
|
|
dc76dcdc2f | ||
|
|
6a040d16ea | ||
|
|
30e8d04b70 | ||
|
|
2b3375ad41 | ||
|
|
8a059a5520 | ||
|
|
49a2f02b54 | ||
|
|
ef92f24d35 | ||
|
|
0207914592 | ||
|
|
5776c97744 | ||
|
|
180cdf2a08 | ||
|
|
47720948ff | ||
|
|
7814a2b622 | ||
|
|
06981fb894 | ||
|
|
19dbd9901d | ||
|
|
4985b0a85d | ||
|
|
39f99d6ce7 | ||
|
|
c8f25ea4b3 | ||
|
|
cab9e08066 | ||
|
|
2b83f82989 | ||
|
|
598e8fee9c | ||
|
|
6f3a788101 | ||
|
|
30ff349b37 | ||
|
|
e264306fed | ||
|
|
4018797384 | ||
|
|
f557cb8d10 | ||
|
|
a1efde6ef9 | ||
|
|
cf34e28e35 | ||
|
|
b7595fe4ec | ||
|
|
2551d82a99 | ||
|
|
0cedba8ea4 | ||
|
|
3f84972bfd | ||
|
|
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 | ||
|
|
02467d5220 | ||
|
|
f18c0e3eaf | ||
|
|
c55f0b4c39 | ||
|
|
2639a6ac6b | ||
|
|
52c1992814 | ||
|
|
b1fbf4344e | ||
|
|
a1b9f2dd3b | ||
|
|
441cfaffce | ||
|
|
75e1f5387d | ||
|
|
3f0fda7aac | ||
|
|
cc5452d14c | ||
|
|
5aff8c1737 | ||
|
|
a00f4af923 | ||
|
|
62a2aed7d9 | ||
|
|
e99f01104e | ||
|
|
1de4704046 | ||
|
|
0d65e67140 | ||
|
|
ccd01c9c75 | ||
|
|
0b127ff419 | ||
|
|
d4a01d9b8a | ||
|
|
c6065653f0 | ||
|
|
fd0de5ae09 | ||
|
|
83ca18d7d5 | ||
|
|
11666b9ae1 | ||
|
|
7235f242b2 | ||
|
|
7fef8f4c77 | ||
|
|
2e3a9a8b80 |
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
.grid-item {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.many2many_flex>div {
|
||||
margin-right: 15px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.many2many_flex>div>:nth-child(2) {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.close {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
position: absolute;
|
||||
top: -8.8px;
|
||||
right: -8.8px;
|
||||
color: #fff;
|
||||
background-color: #000;
|
||||
opacity: 0;
|
||||
.item-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
line-height: 20px;
|
||||
font-size: 18px;
|
||||
}
|
||||
/*控制图片大小*/
|
||||
.item-icon {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.img_close {
|
||||
.item-label {
|
||||
font-size: 12px;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.processing-capabilities-grid {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@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;
|
||||
transform: scale(0.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';
|
||||
|
||||
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();
|
||||
}
|
||||
});
|
||||
|
||||
onCloseClick(event) {
|
||||
const close = event.target;
|
||||
const img = close.previousSibling;
|
||||
img.parentElement.classList.remove('zoomed');
|
||||
close.classList.remove('img_close');
|
||||
// 使用 setTimeout 来触发过渡效果
|
||||
setTimeout(() => {
|
||||
previewContainer.classList.add('show');
|
||||
}, 10);
|
||||
}
|
||||
}
|
||||
|
||||
MyCustomWidget.template = "jikimo_frontend.MyCustomWidget";
|
||||
// MyCustomWidget.supportedTypes = ['many2many'];
|
||||
|
||||
registry.category("fields").add("custom_many2many_checkboxes", MyCustomWidget);
|
||||
|
||||
|
||||
@@ -2,25 +2,20 @@
|
||||
<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>
|
||||
|
||||
@@ -108,6 +108,10 @@ td.o_required_modifier {
|
||||
}
|
||||
|
||||
.color_3 {
|
||||
background-color: #808080;
|
||||
}
|
||||
|
||||
.color_4 {
|
||||
background-color: rgb(255, 150, 0);
|
||||
}
|
||||
|
||||
@@ -532,3 +536,7 @@ div:has(.o_required_modifier) > label::before {
|
||||
position: unset;
|
||||
}
|
||||
|
||||
// 修复搜索面板checkbox样式
|
||||
.o_search_panel .form-check .form-check-label span {
|
||||
position: relative;
|
||||
}
|
||||
@@ -190,7 +190,7 @@ def _create(self, data_list):
|
||||
# 如果该用户组被限制创建或更新操作
|
||||
if rec['is_create_or_update']:
|
||||
raise UserError(
|
||||
_("You are restricted from performing this operation. Please contact the administrator."))
|
||||
_("您没有执行此操作的权限。请联系管理员"))
|
||||
else:
|
||||
# 如果 'access.right' 模型不存在,可以在这里定义备选逻辑
|
||||
# 例如,记录日志、发送通知或者简单地跳过这部分逻辑
|
||||
|
||||
139
jikimo_sale_multiple_supply_methods/__init__.py
Normal file
139
jikimo_sale_multiple_supply_methods/__init__.py
Normal file
@@ -0,0 +1,139 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from . import models
|
||||
from . import controllers
|
||||
|
||||
from odoo import api, SUPERUSER_ID
|
||||
|
||||
def _data_install(cr, registry):
|
||||
env = api.Environment(cr, SUPERUSER_ID, {})
|
||||
# 获取所有需要设置的产品模板
|
||||
env.ref('jikimo_sale_multiple_supply_methods.product_template_purchase').product_variant_id.write({'active': False, 'is_bfm': True})
|
||||
env.ref('jikimo_sale_multiple_supply_methods.product_template_manual_processing').product_variant_id.write({'active': False, 'single_manufacturing': True, 'is_bfm': True})
|
||||
env.ref('jikimo_sale_multiple_supply_methods.product_template_default').product_variant_id.write({'active': False, 'is_bfm': True})
|
||||
env.ref('jikimo_sale_multiple_supply_methods.product_template_embryo_customer_provided').product_variant_id.write({'active': False})
|
||||
env.ref('jikimo_sale_multiple_supply_methods.product_template_outsourcing').product_variant_id.write({'active': False, 'is_bfm': True})
|
||||
|
||||
# 为三步制造增加规则
|
||||
warehouse = env['stock.warehouse'].search([('company_id', '=', env.company.id)], limit=1)
|
||||
product_route_id = warehouse.pbm_route_id
|
||||
# 创建规则:原料存货区 -> 制造前, 坯料存货区 -> 制造前, 制造后 -> 坯料存货区, 制造后 -> 成品存货区
|
||||
raw_material_location_id = env['stock.location'].search([('name', '=', '坯料存货区')], limit=1)
|
||||
picking_type_production = warehouse.pbm_type_id
|
||||
picking_type_stock = warehouse.sam_type_id
|
||||
material_location_id = env['stock.location'].search([('name', '=', '原料存货区')], limit=1)
|
||||
# 为mto增加规则
|
||||
mto_route_id = env.ref('stock.route_warehouse0_mto')
|
||||
# 创建规则:原料存货区 -> 外包位置, 坯料存货区 -> 外包位置
|
||||
subcontracting_location_id = env.company.subcontracting_location_id
|
||||
picking_type_subcontracting = warehouse.subcontracting_resupply_type_id
|
||||
# 为补给外包商增加规则
|
||||
resupply_route_id = warehouse.subcontracting_route_id
|
||||
|
||||
rules_data = [
|
||||
{
|
||||
'name': 'WH: 原料存货区 → 制造前',
|
||||
'location_src_id': material_location_id.id,
|
||||
'location_dest_id': warehouse.pbm_loc_id.id,
|
||||
'route_id': product_route_id.id,
|
||||
'picking_type_id': picking_type_production.id,
|
||||
'action': 'pull',
|
||||
'sequence': 20,
|
||||
'warehouse_id': warehouse.id,
|
||||
'procure_method': 'mts_else_mto',
|
||||
},
|
||||
{
|
||||
'name': 'WH: 坯料存货区 → 制造前',
|
||||
'location_src_id': raw_material_location_id.id,
|
||||
'location_dest_id': warehouse.pbm_loc_id.id,
|
||||
'route_id': product_route_id.id,
|
||||
'picking_type_id': picking_type_production.id,
|
||||
'action': 'pull',
|
||||
'sequence': 21,
|
||||
'warehouse_id': warehouse.id,
|
||||
'procure_method': 'mts_else_mto',
|
||||
},
|
||||
{
|
||||
'name': 'WH: 制造后 → 坯料存货区',
|
||||
'location_src_id': warehouse.sam_loc_id.id,
|
||||
'location_dest_id': raw_material_location_id.id,
|
||||
'route_id': product_route_id.id,
|
||||
'picking_type_id': picking_type_stock.id,
|
||||
'action': 'push',
|
||||
'sequence': 23,
|
||||
},
|
||||
{
|
||||
'name': 'WH: 制造后 → 成品存货区',
|
||||
'location_src_id': warehouse.sam_loc_id.id,
|
||||
'location_dest_id': env['stock.location'].search([('name', '=', '成品存货区')], limit=1).id,
|
||||
'route_id': product_route_id.id,
|
||||
'picking_type_id': picking_type_stock.id,
|
||||
'action': 'push',
|
||||
'sequence': 24,
|
||||
},
|
||||
{
|
||||
'name': 'WH: 原料存货区 → 外包位置 (MTO)',
|
||||
'location_src_id': material_location_id.id,
|
||||
'location_dest_id': subcontracting_location_id.id,
|
||||
'route_id': mto_route_id.id,
|
||||
'picking_type_id': picking_type_subcontracting.id,
|
||||
'action': 'pull',
|
||||
'sequence': 24,
|
||||
'warehouse_id': warehouse.id,
|
||||
'procure_method': 'mts_else_mto',
|
||||
},
|
||||
{
|
||||
'name': 'WH: 坯料存货区 → 外包位置 (MTO)',
|
||||
'location_src_id': raw_material_location_id.id,
|
||||
'location_dest_id': subcontracting_location_id.id,
|
||||
'route_id': mto_route_id.id,
|
||||
'picking_type_id': picking_type_subcontracting.id,
|
||||
'action': 'pull',
|
||||
'sequence': 25,
|
||||
'warehouse_id': warehouse.id,
|
||||
'procure_method': 'mts_else_mto',
|
||||
},
|
||||
{
|
||||
'name': 'WH: 坯料存货区 → 外包位置',
|
||||
'location_src_id': raw_material_location_id.id,
|
||||
'location_dest_id': subcontracting_location_id.id,
|
||||
'route_id': resupply_route_id.id,
|
||||
'picking_type_id': picking_type_subcontracting.id,
|
||||
'action': 'pull',
|
||||
'sequence': 26,
|
||||
'warehouse_id': warehouse.id,
|
||||
'procure_method': 'make_to_stock',
|
||||
}
|
||||
]
|
||||
# 遍历每个规则数据,执行创建或更新操作
|
||||
for rule_data in rules_data:
|
||||
_create_or_update_stock_rule(env, rule_data)
|
||||
|
||||
def _create_or_update_stock_rule(env, rule_data):
|
||||
# 尝试查找现有的 stock.rule
|
||||
existing_rule = env['stock.rule'].search([
|
||||
('name', '=', rule_data['name']),
|
||||
('route_id', '=', rule_data.get('route_id'))
|
||||
], limit=1)
|
||||
|
||||
if existing_rule:
|
||||
# 如果存在,更新现有记录
|
||||
existing_rule.write(rule_data)
|
||||
else:
|
||||
# 如果不存在,创建新记录
|
||||
env['stock.rule'].create(rule_data)
|
||||
|
||||
def _data_uninstall(cr, registry):
|
||||
env = api.Environment(cr, SUPERUSER_ID, {})
|
||||
warehouse = env['stock.warehouse'].search([('company_id', '=', env.company.id)], limit=1)
|
||||
product_route_id = warehouse.pbm_route_id
|
||||
resupply_route_id = warehouse.subcontracting_route_id
|
||||
mto_route_id = env.ref('stock.route_warehouse0_mto')
|
||||
# Fail unlink means that the route is used somewhere (e.g. route_id on stock.rule). In this case
|
||||
# we don't try to do anything.
|
||||
try:
|
||||
with env.cr.savepoint():
|
||||
env['stock.rule'].search([('name', 'in', ('WH: 原料存货区 → 制造前', 'WH: 坯料存货区 → 制造前', 'WH: 制造后 → 坯料存货区', 'WH: 制造后 → 成品存货区')), ('route_id', '=', product_route_id.id)]).unlink()
|
||||
env['stock.rule'].search([('name', 'in', ('WH: 原料存货区 → 外包位置 (MTO)', 'WH: 坯料存货区 → 外包位置 (MTO)')), ('route_id', '=', mto_route_id.id)]).unlink()
|
||||
env['stock.rule'].search([('name', '=', 'WH: 坯料存货区 → 外包位置'), ('route_id', '=', resupply_route_id.id)]).unlink()
|
||||
except:
|
||||
pass
|
||||
26
jikimo_sale_multiple_supply_methods/__manifest__.py
Normal file
26
jikimo_sale_multiple_supply_methods/__manifest__.py
Normal file
@@ -0,0 +1,26 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
{
|
||||
'name': '机企猫 多供货方式',
|
||||
'version': '16.0.1.0.0',
|
||||
'summary': """ 报价单提供(自动化产线加工/人工线下加工/外购/委外加工)多种供货方式选择 """,
|
||||
'author': 'fox',
|
||||
'website': '',
|
||||
'category': '',
|
||||
'depends': ['sf_dlm', 'sale_stock', 'sf_sale', 'sale'],
|
||||
"data": [
|
||||
'security/ir.model.access.csv',
|
||||
'data/stock_routes.xml',
|
||||
'data/product_data.xml',
|
||||
# 'views/product_product_views.xml',
|
||||
],'assets': {
|
||||
# 'web.assets_backend': [
|
||||
# 'jikimo_sale_multiple_supply_methods/static/src/**/*'
|
||||
# ],
|
||||
},
|
||||
'post_init_hook': '_data_install',
|
||||
'uninstall_hook': '_data_uninstall',
|
||||
'application': True,
|
||||
'installable': True,
|
||||
'auto_install': False,
|
||||
'license': 'LGPL-3',
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
98
jikimo_sale_multiple_supply_methods/data/product_data.xml
Normal file
98
jikimo_sale_multiple_supply_methods/data/product_data.xml
Normal file
@@ -0,0 +1,98 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<record id="product_template_manual_processing" model="product.template">
|
||||
<field name="name">人工线下加工模板</field>
|
||||
<field name="active" eval="False"/>
|
||||
<field name="categ_id" ref="sf_dlm.product_category_finished_sf"/>
|
||||
<field name="route_ids"
|
||||
eval="[ref('stock.route_warehouse0_mto'), ref('mrp.route_warehouse0_manufacture')]"/>
|
||||
<field name="invoice_policy">delivery</field>
|
||||
<field name="detailed_type">product</field>
|
||||
<field name="purchase_ok">false</field>
|
||||
<field name="uom_id" ref="uom.product_uom_unit"/>
|
||||
<field name="uom_po_id" ref="uom.product_uom_unit"/>
|
||||
<field name="company_id" ref="base.main_company"/>
|
||||
<field name="single_manufacturing">true</field>
|
||||
<field name="tracking">serial</field>
|
||||
<!-- <field name="categ_type">成品</field> -->
|
||||
<field name="is_manual_processing">true</field>
|
||||
</record>
|
||||
|
||||
<record id="product_template_purchase" model="product.template">
|
||||
<field name="name">成品外购模板</field>
|
||||
<field name="active" eval="False"/>
|
||||
<field name="categ_id" ref="sf_dlm.product_category_finished_sf"/>
|
||||
<field name="route_ids"
|
||||
eval="[ref('stock.route_warehouse0_mto'), ref('purchase_stock.route_warehouse0_buy')]"/>
|
||||
<field name="tracking">serial</field>
|
||||
<field name="detailed_type">product</field>
|
||||
<field name="uom_id" ref="uom.product_uom_unit"/>
|
||||
<field name="uom_po_id" ref="uom.product_uom_unit"/>
|
||||
<field name="company_id" ref="base.main_company"/>
|
||||
<!-- <field name="categ_type">成品</field> -->
|
||||
</record>
|
||||
|
||||
<record id="product_template_outsourcing" model="product.template">
|
||||
<field name="name">成品委外加工模板</field>
|
||||
<field name="active" eval="False"/>
|
||||
<field name="categ_id" ref="sf_dlm.product_category_finished_sf"/>
|
||||
<field name="route_ids"
|
||||
eval="[ref('stock.route_warehouse0_mto'), ref('purchase_stock.route_warehouse0_buy'), ref('mrp_subcontracting.route_resupply_subcontractor_mto')]"/>
|
||||
<field name="tracking">serial</field>
|
||||
<field name="detailed_type">product</field>
|
||||
<field name="uom_id" ref="uom.product_uom_unit"/>
|
||||
<field name="uom_po_id" ref="uom.product_uom_unit"/>
|
||||
<field name="company_id" ref="base.main_company"/>
|
||||
<!-- <field name="categ_type">成品</field> -->
|
||||
</record>
|
||||
|
||||
<record id="product_template_default" model="product.template">
|
||||
<field name="name">成品初始化模板</field>
|
||||
<field name="active" eval="False"/>
|
||||
<field name="categ_id" ref="sf_dlm.product_category_finished_sf"/>
|
||||
<field name="route_ids" eval="[]"/>
|
||||
<field name="tracking">serial</field>
|
||||
<field name="detailed_type">product</field>
|
||||
<field name="uom_id" ref="uom.product_uom_unit"/>
|
||||
<field name="uom_po_id" ref="uom.product_uom_unit"/>
|
||||
<field name="company_id" ref="base.main_company"/>
|
||||
<!-- <field name="categ_type">成品</field> -->
|
||||
</record>
|
||||
|
||||
<!-- 供应商信息(业务平台),由于数据是python创建,只能指定ID -->
|
||||
<record id="product_supplierinfo_bfm" model="product.supplierinfo">
|
||||
<field name="partner_id" eval="91"/>
|
||||
</record>
|
||||
|
||||
<record id="product_template_embryo_customer_provided" model="product.template">
|
||||
<field name="name">坯料客供料模板</field>
|
||||
<field name="active" eval="False"/>
|
||||
<field name="categ_id" ref="sf_dlm.product_category_embryo_sf"/>
|
||||
<field name="route_ids" eval="[
|
||||
ref('stock.route_warehouse0_mto'),
|
||||
ref('mrp_subcontracting.route_resupply_subcontractor_mto'),
|
||||
ref('jikimo_sale_multiple_supply_methods.route_material_processing')]"/>
|
||||
<field name="sale_ok">false</field>
|
||||
<field name="tracking">serial</field>
|
||||
<field name="detailed_type">product</field>
|
||||
<field name="uom_id" ref="uom.product_uom_unit"/>
|
||||
<field name="uom_po_id" ref="uom.product_uom_unit"/>
|
||||
<field name="company_id" ref="base.main_company"/>
|
||||
<!-- <field name="categ_type">坯料</field> -->
|
||||
<field name="seller_ids" eval="[ref('jikimo_sale_multiple_supply_methods.product_supplierinfo_bfm')]"/>
|
||||
</record>
|
||||
|
||||
<record id="sf_dlm.product_embryo_sf_self_machining" model="product.product">
|
||||
<field name="is_manual_processing">true</field>
|
||||
</record>
|
||||
|
||||
<record id="sf_dlm.product_embryo_sf_self_machining" model="product.product">
|
||||
<field name="route_ids" eval="[(4, ref('mrp_subcontracting.route_resupply_subcontractor_mto'))]"/>
|
||||
</record>
|
||||
|
||||
<record id="sf_dlm.product_embryo_sf_purchase" model="product.product">
|
||||
<field name="route_ids" eval="[(4, ref('mrp_subcontracting.route_resupply_subcontractor_mto'))]"/>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
32
jikimo_sale_multiple_supply_methods/data/stock_routes.xml
Normal file
32
jikimo_sale_multiple_supply_methods/data/stock_routes.xml
Normal file
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<record id="route_material_processing" model="stock.route">
|
||||
<field name="name">带料加工</field>
|
||||
<field name="product_selectable">true</field>
|
||||
<field name="warehouse_selectable">true</field>
|
||||
<field name="warehouse_ids" eval="[ref('stock.warehouse0')]"/>
|
||||
<field name="sequence">16</field>
|
||||
</record>
|
||||
|
||||
<record id="material_picking_in" model="stock.picking.type">
|
||||
<field name="name">客供料入库</field>
|
||||
<field name="code">incoming</field>
|
||||
<field name="active">true</field>
|
||||
<field name="company_id" ref="base.main_company"/>
|
||||
<field name="sequence_code">DL</field>
|
||||
<field name="warehouse_id" ref="stock.warehouse0"/>
|
||||
<field name="default_location_src_id" ref="stock.stock_location_customers"/>
|
||||
<field name="default_location_dest_id" eval="25"/>
|
||||
</record>
|
||||
|
||||
<record id="rule_material_receiving" model="stock.rule">
|
||||
<field name="name">带料收货</field>
|
||||
<field name="route_id" ref="route_material_processing"/>
|
||||
<field name="location_dest_id" ref="stock.stock_location_company"/>
|
||||
<field name="location_src_id" ref="stock.stock_location_customers"/>
|
||||
<field name="picking_type_id" ref="material_picking_in"/>
|
||||
<field name="action">pull</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
3
jikimo_sale_multiple_supply_methods/models/__init__.py
Normal file
3
jikimo_sale_multiple_supply_methods/models/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from . import product_template
|
||||
from . import mrp_bom
|
||||
13
jikimo_sale_multiple_supply_methods/models/mrp_bom.py
Normal file
13
jikimo_sale_multiple_supply_methods/models/mrp_bom.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from odoo import models, fields
|
||||
|
||||
class MrpBom(models.Model):
|
||||
_inherit = 'mrp.bom'
|
||||
|
||||
# 业务平台分配工厂后在智能工厂先创建销售订单再创建该产品后再次进行创建bom
|
||||
def bom_create(self, product, bom_type, product_type):
|
||||
bom_id = super(MrpBom, self).bom_create(product, bom_type, product_type)
|
||||
|
||||
# 成品的供应商从模板中获取
|
||||
if product_type == 'product':
|
||||
bom_id.subcontractor_id = product.product_tmpl_id.seller_ids.partner_id.id
|
||||
return bom_id
|
||||
@@ -0,0 +1,26 @@
|
||||
from odoo import models, fields, api
|
||||
|
||||
class ProductTemplate(models.Model):
|
||||
_inherit = 'product.template'
|
||||
|
||||
is_manual_processing = fields.Boolean(string='人工线下加工')
|
||||
is_customer_provided = fields.Boolean(string='客供料')
|
||||
|
||||
def copy_template(self, product_template_id):
|
||||
if not isinstance(product_template_id, ProductTemplate):
|
||||
raise ValueError('%s必须是ProductTemplate类型' % product_template_id)
|
||||
|
||||
self.route_ids = product_template_id.route_ids
|
||||
self.categ_id = product_template_id.categ_id
|
||||
self.invoice_policy = product_template_id.invoice_policy
|
||||
self.detailed_type = product_template_id.detailed_type
|
||||
self.purchase_ok = product_template_id.purchase_ok
|
||||
self.uom_id = product_template_id.uom_id
|
||||
self.uom_po_id = product_template_id.uom_po_id
|
||||
self.company_id = product_template_id.company_id
|
||||
self.single_manufacturing = product_template_id.single_manufacturing
|
||||
self.tracking = product_template_id.tracking
|
||||
self.is_bfm = product_template_id.is_bfm
|
||||
self.is_manual_processing = product_template_id.is_manual_processing
|
||||
# 复制 seller_ids
|
||||
self.seller_ids = [(0, 0, {'partner_id': seller.partner_id.id, 'delay': 1.0}) for seller in product_template_id.seller_ids]
|
||||
@@ -0,0 +1,8 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_sale_order_group_production_engineer,sale.order_group_production_engineer,sale.model_sale_order,sf_base.group_production_engineer,1,1,0,0
|
||||
access_sale_order_line_group_production_engineer,sale_order_line_group_production_engineer,sale.model_sale_order_line,sf_base.group_production_engineer,1,1,0,0
|
||||
access_product_product_group_production_engineer,product_product_group_production_engineer,product.model_product_product,sf_base.group_production_engineer,1,0,0,0
|
||||
access_product_template_group_production_engineer,product_template_group_production_engineer,product.model_product_template,sf_base.group_production_engineer,1,0,0,0
|
||||
access_stock_picking_group_production_engineer,stock_picking_group_production_engineer,stock.model_stock_picking,sf_base.group_production_engineer,1,0,0,0
|
||||
access_stock_move_group_production_engineer,stock_move_group_production_engineer,stock.model_stock_move,sf_base.group_production_engineer,1,0,0,0
|
||||
access_mrp_bom_group_production_engineer,mrp_bom_group_production_engineer,mrp.model_mrp_bom,sf_base.group_production_engineer,1,0,0,0
|
||||
|
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<!-- 由于该模块不能依赖sf_dlm_management, 该功能只能在sf_dlm_management中实现,并且依赖该模块-->
|
||||
<record id="view_product_product_form_inherit_sf" model="ir.ui.view">
|
||||
<field name="name">view.product.template.form.inherit.sf</field>
|
||||
<field name="model">product.template</field>
|
||||
<field name="inherit_id" ref="sf_dlm_management.view_sale_product_template_form_inherit_sf"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='manual_quotation']" position="after">
|
||||
<field name="is_customer_provided" attrs="{'invisible': [('categ_type', 'not in', ['成品', '坯料'])], 'readonly': True}" />
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
||||
|
||||
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'
|
||||
})
|
||||
5
jikimo_workorder_exception/security/ir.model.access.csv
Normal file
5
jikimo_workorder_exception/security/ir.model.access.csv
Normal file
@@ -0,0 +1,5 @@
|
||||
"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
|
||||
"access_jikimo_workorder_exception_group_quality","access.jikimo.workorder.exception.group_quality","model_jikimo_workorder_exception","sf_base.group_quality",1,1,1,0
|
||||
"access_jikimo_workorder_exception_group_quality_director","access.jikimo.workorder.exception.group_quality_director","model_jikimo_workorder_exception","sf_base.group_quality_director",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>
|
||||
4
jikimo_workorder_exception_notify/__init__.py
Normal file
4
jikimo_workorder_exception_notify/__init__.py
Normal file
@@ -0,0 +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 "打印标签"
|
||||
@@ -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)
|
||||
@@ -203,7 +203,7 @@
|
||||
<record id="quality_alert_action_check" model="ir.actions.act_window">
|
||||
<field name="name">Quality Alerts</field>
|
||||
<field name="res_model">quality.alert</field>
|
||||
<field name="view_mode">kanban,tree,form,pivot,graph,calendar</field>
|
||||
<field name="view_mode">tree,kanban,form,pivot,graph,calendar</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Create a new quality alert
|
||||
@@ -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"
|
||||
|
||||
@@ -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,30 @@ 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')
|
||||
|
||||
|
||||
class ReSaleOrder(models.Model):
|
||||
_inherit = 'sale.order'
|
||||
|
||||
person_of_delivery = fields.Char('收货人')
|
||||
telephone_of_delivery = fields.Char('电话号码')
|
||||
address_of_delivery = fields.Char('联系地址')
|
||||
|
||||
|
||||
class EmbryoRedundancy(models.Model):
|
||||
_name = "sf.embryo.redundancy"
|
||||
|
||||
code = fields.Char('编码', required=True)
|
||||
name = fields.Char('名称', required=True)
|
||||
long = fields.Float('长度(mm)', required=True)
|
||||
width = fields.Float('宽度(mm)', required=True)
|
||||
height = fields.Float('高度(mm)', required=True)
|
||||
active = fields.Boolean('有效', default=True)
|
||||
|
||||
@@ -56,7 +56,7 @@ class MrsMaterialModel(models.Model):
|
||||
finish_machining = fields.Float("精加工Vc(m/min)")
|
||||
remark = fields.Text("备注")
|
||||
gain_way = fields.Selection(
|
||||
[("自加工", "自加工"), ("外协", "外协"), ("采购", "采购")],
|
||||
[("自加工", "自加工"), ("外协", "委外加工"), ("采购", "采购")],
|
||||
default="", string="获取方式")
|
||||
supplier_ids = fields.One2many('sf.supplier.sort', 'materials_model_id', string='供应商')
|
||||
active = fields.Boolean('有效', default=True)
|
||||
@@ -100,6 +100,7 @@ class MrsProductionProcess(models.Model):
|
||||
travel_day = fields.Float('路途天数/d')
|
||||
sequence = fields.Integer('排序')
|
||||
|
||||
|
||||
# class MrsProcessingTechnology(models.Model):
|
||||
# _name = 'sf.processing.technology'
|
||||
# _description = '加工工艺'
|
||||
@@ -157,6 +158,8 @@ class MrsProductionProcessParameter(models.Model):
|
||||
for parameter in self:
|
||||
if parameter.process_id:
|
||||
name = parameter.process_id.name + '-' + parameter.name
|
||||
else:
|
||||
name = parameter.name
|
||||
result.append((parameter.id, name))
|
||||
return result
|
||||
|
||||
|
||||
@@ -46,6 +46,11 @@
|
||||
<field name="category_id" ref="base.module_category_manufacturing_manufacturing"/>
|
||||
</record>
|
||||
|
||||
<record id="group_production_engineer" model="res.groups">
|
||||
<field name="name">工艺工程师</field>
|
||||
<field name="implied_ids" eval="[(4, ref('group_sf_mrp_user'))]"/>
|
||||
<field name="category_id" ref="base.module_category_manufacturing_manufacturing"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.module.category" id="module_category_plan">
|
||||
<field name="name">计划</field>
|
||||
@@ -65,7 +70,7 @@
|
||||
<record id="group_plan_dispatch" model="res.groups">
|
||||
<field name="name">计划调度岗</field>
|
||||
<field name="category_id" ref="module_category_plan"/>
|
||||
<!-- <field name="implied_ids" eval="[(4, ref('base.group_user'))]"/> -->
|
||||
<!-- <field name="implied_ids" eval="[(4, ref('base.group_user'))]"/> -->
|
||||
</record>
|
||||
|
||||
<record id="group_plan_director" model="res.groups">
|
||||
|
||||
@@ -247,3 +247,10 @@ access_sf_cutting_tool_type_group_sf_stock_manager,sf_cutting_tool_type_group_sf
|
||||
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
|
||||
|
||||
access_sf_embryo_redundancy,sf_embryo_redundancy,model_sf_embryo_redundancy,base.group_user,1,0,0,0
|
||||
access_sf_embryo_redundancy_admin,sf_embryo_redundancy_admin,model_sf_embryo_redundancy,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()
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
.img-fluid {
|
||||
max-width: unset !important;
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.o_inner_group .img-fluid {
|
||||
|
||||
@@ -132,11 +132,9 @@
|
||||
<group string="加工能力">
|
||||
<div>
|
||||
<field name='jg_image_id' widget="custom_many2many_checkboxes">
|
||||
|
||||
<tree>
|
||||
<field name="name"/>
|
||||
<field name="image" widget="image"
|
||||
options="{'size': [100, 100], 'click enlarge': True}"/>
|
||||
<field name="image" widget="image"/>
|
||||
|
||||
</tree>
|
||||
</field>
|
||||
@@ -149,8 +147,7 @@
|
||||
|
||||
<tree>
|
||||
<field name="name"/>
|
||||
<field name="image" widget="image"
|
||||
options="{'size': [100, 100], 'click enlarge': True}"/>
|
||||
<field name="image" widget="image"/>
|
||||
|
||||
</tree>
|
||||
</field>
|
||||
@@ -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)"
|
||||
@@ -314,22 +311,22 @@
|
||||
<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,45 @@
|
||||
<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>
|
||||
|
||||
#------------------坯料冗余量------------------
|
||||
<record model="ir.ui.view" id="tree_sf_embryo_redundancy_view">
|
||||
<field name="name">tree.sf.embryo.redundancy</field>
|
||||
<field name="model">sf.embryo.redundancy</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="坯料冗余量" create="0" edit="0" delete="0">
|
||||
<field name="name"/>
|
||||
<field name="code"/>
|
||||
<field name="long"/>
|
||||
<field name="width"/>
|
||||
<field name="height"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_sf_embryo_redundancy" model="ir.actions.act_window">
|
||||
<field name="name">坯料冗余量</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">sf.embryo.redundancy</field>
|
||||
<field name="view_mode">tree</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -141,12 +141,25 @@
|
||||
sequence="1"
|
||||
action="action_sf_machine_brand"/>
|
||||
|
||||
<menuitem
|
||||
id="menu_sf_embryo_redundancy"
|
||||
parent="menu_sf_base"
|
||||
name="坯料冗余"
|
||||
sequence="2"
|
||||
action="action_sf_embryo_redundancy"/>
|
||||
|
||||
<menuitem
|
||||
id="menu_sf_machining_accuracy"
|
||||
parent="menu_sf_base"
|
||||
name="加工精度"
|
||||
sequence="3"
|
||||
action="action_sf_machining_accuracy"/>
|
||||
|
||||
<menuitem
|
||||
id="menu_sf_machine_control_system"
|
||||
parent="menu_sf_base"
|
||||
name="数控系统"
|
||||
sequence="1"
|
||||
sequence="4"
|
||||
action="action_sf_machine_control_system"/>
|
||||
|
||||
|
||||
|
||||
@@ -360,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', ('整体式刀具','刀片'))]}">
|
||||
@@ -380,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', ('整体式刀具','刀片'))]}">
|
||||
@@ -392,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">
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import json
|
||||
import logging
|
||||
import traceback
|
||||
|
||||
from odoo import http
|
||||
from odoo.http import request
|
||||
|
||||
@@ -123,7 +125,8 @@ class Sf_Bf_Connect(http.Controller):
|
||||
res['factory_order_no'] = order_id.name
|
||||
return json.JSONEncoder().encode(res)
|
||||
except Exception as e:
|
||||
logging.info('get_bfm_process_order_list error:%s' % e)
|
||||
traceback_error = traceback.format_exc()
|
||||
logging.error('get_bfm_process_order_list error: %s' % traceback_error)
|
||||
res['status'] = -1
|
||||
res['message'] = '工厂创建销售订单和产品失败,请联系管理员'
|
||||
request.cr.rollback()
|
||||
|
||||
@@ -36,6 +36,7 @@ class StatusChange(models.Model):
|
||||
# 使用super()来调用原始方法(在本例中为'sale.order'模型的'action_confirm'方法)
|
||||
try:
|
||||
res = super(StatusChange, self).action_confirm()
|
||||
logging.info('原生方法返回结果:%s' % res)
|
||||
# 原有方法执行后,进行额外的操作(如调用外部API)
|
||||
process_start_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
config = self.env['res.config.settings'].get_values()
|
||||
@@ -61,6 +62,7 @@ class StatusChange(models.Model):
|
||||
traceback_error = traceback.format_exc()
|
||||
logging.error("工厂加工同步订单状态失败:%s " % traceback_error)
|
||||
raise UserError(e)
|
||||
logging.info('最终返回值:%s' % res)
|
||||
return res
|
||||
|
||||
def action_cancel(self):
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
<field name="inherit_id" ref="stock.view_picking_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//form//sheet//notebook//page[@name='operations']" position="after">
|
||||
<page string="发货信息" name="tracking">
|
||||
<page string="发货信息" name="tracking" attrs="{'invisible': [('picking_type_code', '!=', 'outgoing')]}">
|
||||
<group>
|
||||
<group>
|
||||
<field name="senderNickName" attrs="{'invisible': [('check_out', '!=', 'OUT')]}"/>
|
||||
|
||||
@@ -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,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<data>
|
||||
<data noupdate="1">
|
||||
<record id="product_category_embryo_sf" model="product.category">
|
||||
<field name="name">坯料</field>
|
||||
<field name="type">坯料</field>
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
from . import product_supplierinfo
|
||||
from . import stock_rule_inherit
|
||||
|
||||
|
||||
87
sf_dlm/models/stock_rule_inherit.py
Normal file
87
sf_dlm/models/stock_rule_inherit.py
Normal file
@@ -0,0 +1,87 @@
|
||||
import logging
|
||||
from itertools import groupby
|
||||
from odoo import models, fields, api, _
|
||||
|
||||
|
||||
class StockRuleInherit(models.Model):
|
||||
_inherit = 'stock.rule'
|
||||
|
||||
@api.model
|
||||
def _run_buy(self, procurements):
|
||||
# 判断补货组的采购类型
|
||||
procurements_group = {'standard': [], 'consignment': []}
|
||||
for procurement, rule in procurements:
|
||||
is_consignment = False
|
||||
product = procurement.product_id
|
||||
# 获取主 BOM
|
||||
bom = self.env['mrp.bom'].search([('product_tmpl_id', '=', product.product_tmpl_id.id)], limit=1)
|
||||
|
||||
if bom:
|
||||
# 遍历 BOM 中的组件(即坯料等)
|
||||
for line in bom.bom_line_ids:
|
||||
raw_material = line.product_id
|
||||
# 检查路线
|
||||
for route in raw_material.route_ids:
|
||||
# print('route.name:', route.name)
|
||||
if route.name == '按订单补给外包商':
|
||||
is_consignment = True
|
||||
|
||||
if is_consignment:
|
||||
procurements_group['consignment'].append((procurement, rule))
|
||||
else:
|
||||
procurements_group['standard'].append((procurement, rule))
|
||||
|
||||
for key, value in procurements_group.items():
|
||||
super(StockRuleInherit, self)._run_buy(value)
|
||||
|
||||
if key == 'consignment':
|
||||
for procurement, rule in value:
|
||||
supplier = procurement.values.get('supplier')
|
||||
if supplier:
|
||||
domain = rule._make_po_get_domain(procurement.company_id, procurement.values,
|
||||
supplier.partner_id)
|
||||
logging.info("domain=============: %s", domain)
|
||||
po = self.env['purchase.order'].sudo().search([
|
||||
('partner_id', '=', supplier.partner_id.id),
|
||||
('company_id', '=', procurement.company_id.id), # 保证公司一致
|
||||
('origin', '=', procurement.origin), # 根据来源匹配
|
||||
('state', '=', 'draft') # 状态为草稿
|
||||
], limit=1)
|
||||
logging.info("po=: %s", po)
|
||||
if po:
|
||||
po.write({'purchase_type': 'consignment'})
|
||||
|
||||
# # 首先调用父类的 _run_buy 方法,以保留原有逻辑
|
||||
# super(StockRuleInherit, self)._run_buy(procurements)
|
||||
|
||||
# 然后在这里添加自定义的逻辑
|
||||
# for procurement, rule in procurements:
|
||||
# product = procurement.product_id
|
||||
# # 获取主 BOM
|
||||
# bom = self.env['mrp.bom'].search([('product_tmpl_id', '=', product.product_tmpl_id.id)], limit=1)
|
||||
|
||||
# if bom:
|
||||
# # 遍历 BOM 中的组件(即坯料等)
|
||||
# for line in bom.bom_line_ids:
|
||||
# raw_material = line.product_id
|
||||
# # 检查路线
|
||||
# for route in raw_material.route_ids:
|
||||
# # print('route.name:', route.name)
|
||||
# if route.name == '按订单补给外包商': # 或者用 route.id 检查精确的路线
|
||||
# print("按订单补给外包商============是")
|
||||
# # 使用 procurement.values['supplier'] 获取供应商
|
||||
# supplier = procurement.values.get('supplier')
|
||||
# if supplier:
|
||||
# domain = rule._make_po_get_domain(procurement.company_id, procurement.values,
|
||||
# supplier.partner_id)
|
||||
# logging.info("domain=============: %s", domain)
|
||||
# po = self.env['purchase.order'].sudo().search([
|
||||
# ('partner_id', '=', supplier.partner_id.id),
|
||||
# ('company_id', '=', procurement.company_id.id), # 保证公司一致
|
||||
# ('origin', '=', procurement.origin), # 根据来源匹配
|
||||
# ('state', '=', 'draft') # 状态为草稿
|
||||
# ], limit=1)
|
||||
# logging.info("po=: %s", po)
|
||||
# if po:
|
||||
# po.write({'purchase_type': 'consignment'})
|
||||
# break
|
||||
@@ -9,7 +9,7 @@
|
||||
""",
|
||||
'category': 'sf',
|
||||
'website': 'https://www.sf.jikimo.com',
|
||||
'depends': ['sf_sale', 'sf_dlm', 'sf_manufacturing'],
|
||||
'depends': ['sf_sale', 'sf_dlm', 'sf_manufacturing','jikimo_attachment_viewer'],
|
||||
'data': [
|
||||
'data/stock_data.xml',
|
||||
'views/product_template_management_view.xml',
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
<odoo>
|
||||
<data>
|
||||
<record id="mrp.product_template_action" model="ir.actions.act_window">
|
||||
<field name="view_mode">tree,kanban,form,activity</field>
|
||||
<field name="context">
|
||||
{"search_default_categ_id":1,"search_default_consumable": 1, 'default_detailed_type': 'product'}
|
||||
</field>
|
||||
@@ -15,8 +16,10 @@
|
||||
<field name='categ_id' class="custom_required" attrs="{'readonly': [('id', '!=', False)]}"/>
|
||||
<field name='is_bfm' invisible="1"/>
|
||||
<field name='categ_type' invisible="1"/>
|
||||
<field name='part_name' attrs="{'invisible': [('categ_type', '!=', '成品')]}"/>
|
||||
<field name='part_number' attrs="{'invisible': [('categ_type', '!=', '成品')]}"/>
|
||||
<field name='manual_quotation' attrs="{'invisible':[('upload_model_file', '=', [])]}"/>
|
||||
<field name="is_customer_provided" attrs="{'invisible': [('categ_type', 'not in', ['成品', '坯料'])], 'readonly': True}" />
|
||||
<field name="upload_model_file"
|
||||
widget="many2many_binary"
|
||||
attrs="{'invisible': ['|', '|',('categ_type', '!=', '成品'),('categ_type', '=', False),('is_bfm','=', True)]}"/>
|
||||
@@ -63,6 +66,9 @@
|
||||
attrs="{'invisible': [('categ_type', '!=', '夹具')],'required': [('categ_type', '=', '夹具')]}"
|
||||
domain="[('fixture_model_id','=',fixture_model_id)]"/>
|
||||
</field>
|
||||
<xpath expr="//field[@name='uom_id']" position="before">
|
||||
<field name="is_manual_processing" attrs="{'invisible': [('categ_type', 'not in', ['成品', '坯料'])], 'readonly': True}" />
|
||||
</xpath>
|
||||
<xpath expr="//label[@for='volume']" position="before">
|
||||
<label for="length" string="尺寸"
|
||||
attrs="{'invisible':[('product_variant_count', '>', 1), ('is_product_variant', '=', False)]}"/>
|
||||
@@ -109,6 +115,17 @@
|
||||
'刀具')], 'required': True}
|
||||
</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//sheet//notebook" position="inside">
|
||||
<page string="2D加工图纸" attrs="{'invisible': [('categ_type', 'not in', ['成品', '坯料'])]}">
|
||||
<field name='machining_drawings' widget="adaptive_viewer"/>
|
||||
</page>
|
||||
</xpath>
|
||||
<xpath expr="//sheet//notebook" position="inside">
|
||||
<page string="质检标准" attrs="{'invisible': [('categ_type', 'not in', ['成品', '坯料'])]}">
|
||||
<field name='quality_standard' widget="adaptive_viewer"/>
|
||||
|
||||
</page>
|
||||
</xpath>
|
||||
<!-- <xpath expr="//field[@name='default_code']" position="attributes">-->
|
||||
<!-- <attribute name="attrs">{'readonly': [('categ_type', '=', '刀具')], 'invisible':-->
|
||||
<!-- [('product_variant_count', '>' , 1)]}-->
|
||||
|
||||
@@ -2,3 +2,4 @@
|
||||
|
||||
from . import hr_employee
|
||||
from . import res_config_setting
|
||||
from . import res_users
|
||||
|
||||
@@ -11,6 +11,42 @@ class JkmPracticeEmployee(models.Model):
|
||||
|
||||
we_id = fields.Char(string='企微ID', index=True)
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
for val in vals_list:
|
||||
if 'work_email' in val:
|
||||
val["we_id"] = self._get_we_id(val.get('work_email'))
|
||||
return super(JkmPracticeEmployee, self).create(vals_list)
|
||||
|
||||
def write(self, vals):
|
||||
if 'work_email' in vals:
|
||||
vals["we_id"] = self._get_we_id(vals.get('work_email'))
|
||||
return super(JkmPracticeEmployee, self).write(vals)
|
||||
|
||||
@api.depends('work_contact_id', 'work_contact_id.mobile', 'work_contact_id.email')
|
||||
def _compute_work_contact_details(self):
|
||||
for employee in self:
|
||||
if employee.work_contact_id:
|
||||
employee.mobile_phone = employee.work_contact_id.mobile
|
||||
employee.work_email = employee.work_contact_id.email
|
||||
if employee.work_contact_id.email:
|
||||
employee.we_id = self._get_we_id(employee.work_contact_id.email)
|
||||
|
||||
def _get_we_id(self, work_email):
|
||||
json1 = {
|
||||
'params': {
|
||||
'work_email': work_email
|
||||
}
|
||||
}
|
||||
url = '/api/get/we_id/info'
|
||||
config = self.env['res.config.settings'].get_values()
|
||||
ret = requests.post((config['ims_url'] + url), json=json1, data={})
|
||||
result = ret.json()['result']
|
||||
if result['code'] == 200:
|
||||
if result['we_id']:
|
||||
return result['we_id']
|
||||
return None
|
||||
|
||||
def _employee_info_sync(self):
|
||||
url = '/api/get/organization'
|
||||
config = self.env['res.config.settings'].get_values()
|
||||
|
||||
15
sf_hr/models/res_users.py
Normal file
15
sf_hr/models/res_users.py
Normal file
@@ -0,0 +1,15 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import random
|
||||
from odoo import models, fields, api
|
||||
from odoo.http import request
|
||||
from odoo.exceptions import AccessDenied
|
||||
|
||||
import logging
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ResUsers(models.Model):
|
||||
_inherit = 'res.users'
|
||||
|
||||
we_employee_id = fields.Char(string=u'企业微信账号', related='employee_id.we_id', default="")
|
||||
@@ -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': [
|
||||
# 定义权限组放在最上面
|
||||
# 权限组
|
||||
|
||||
@@ -15,7 +15,7 @@ db_config = {
|
||||
"user": "postgres",
|
||||
"password": "postgres",
|
||||
"port": "5432",
|
||||
"host": "172.16.10.113"
|
||||
"host": "172.16.10.131"
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@ def convert_to_seconds(time_str):
|
||||
|
||||
if time_str is None:
|
||||
return 0
|
||||
if time_str == 0:
|
||||
return 0
|
||||
|
||||
pattern = r"(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?"
|
||||
|
||||
@@ -400,20 +402,45 @@ class Sf_Dashboard_Connect(http.Controller):
|
||||
try:
|
||||
plan_obj = request.env['sf.production.plan'].sudo()
|
||||
production_obj = request.env['mrp.production'].sudo()
|
||||
work_order_obj = request.env['mrp.workorder'].sudo()
|
||||
line_list = ast.literal_eval(kw['line_list'])
|
||||
# print('line_list: %s' % line_list)
|
||||
for line in line_list:
|
||||
|
||||
# 工单计划量
|
||||
plan_data_total_counts = production_obj.search_count(
|
||||
[('production_line_id.name', '=', line), ('state', 'not in', ['cancel']),
|
||||
('active', '=', True)])
|
||||
# 工单完成量
|
||||
plan_data_finish_counts = plan_obj.search_count(
|
||||
[('production_line_id.name', '=', line), ('state', 'in', ['finished'])])
|
||||
# # 工单计划量
|
||||
# plan_data_plan_counts = plan_obj.search_count(
|
||||
# [('production_line_id.name', '=', line), ('state', 'not in', ['finished'])])
|
||||
# plan_data_total_counts = production_obj.search_count(
|
||||
# [('production_line_id.name', '=', line), ('state', 'not in', ['cancel']),
|
||||
# ('active', '=', True)])
|
||||
|
||||
# 工单计划量切换为CNC工单
|
||||
plan_data_total_counts = work_order_obj.search_count(
|
||||
[('production_id.production_line_id.name', '=', line),
|
||||
('state', 'in', ['ready', 'progress', 'done']), ('routing_type', '=', 'CNC加工')])
|
||||
|
||||
# # 工单完成量
|
||||
# plan_data_finish_counts = plan_obj.search_count(
|
||||
# [('production_line_id.name', '=', line), ('state', 'in', ['finished'])])
|
||||
|
||||
# 工单完成量切换为CNC工单
|
||||
plan_data_finish_counts = work_order_obj.search_count(
|
||||
[('production_id.production_line_id.name', '=', line),
|
||||
('state', 'in', ['done']), ('routing_type', '=', 'CNC加工')])
|
||||
|
||||
# 超期完成量
|
||||
# 搜索所有已经完成的工单
|
||||
plan_data_overtime = work_order_obj.search([
|
||||
('production_id.production_line_id.name', '=', line),
|
||||
('state', 'in', ['done']),
|
||||
('routing_type', '=', 'CNC加工')
|
||||
])
|
||||
|
||||
# 使用 filtered 进行字段比较
|
||||
plan_data_overtime_counts = plan_data_overtime.filtered(
|
||||
lambda order: order.date_finished > order.date_planned_finished
|
||||
)
|
||||
|
||||
# 获取数量
|
||||
plan_data_overtime_counts = len(plan_data_overtime_counts)
|
||||
|
||||
# 查找符合条件的生产计划记录
|
||||
plan_data = plan_obj.search([
|
||||
@@ -515,7 +542,10 @@ class Sf_Dashboard_Connect(http.Controller):
|
||||
'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
|
||||
'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
|
||||
|
||||
@@ -1234,6 +1264,7 @@ class Sf_Dashboard_Connect(http.Controller):
|
||||
"""
|
||||
获取
|
||||
"""
|
||||
logging.info("kw=============:%s" % kw)
|
||||
res = {'status': 1, 'message': '成功', 'data': {}}
|
||||
# 连接数据库
|
||||
conn = psycopg2.connect(**db_config)
|
||||
@@ -1242,6 +1273,7 @@ class Sf_Dashboard_Connect(http.Controller):
|
||||
time_threshold = datetime.now() - timedelta(days=1)
|
||||
|
||||
alarm_last_24_time = 0.0
|
||||
alarm_all_time = 0.0
|
||||
|
||||
def fetch_result_as_dict(cursor):
|
||||
"""辅助函数:将查询结果转为字典"""
|
||||
@@ -1302,17 +1334,49 @@ class Sf_Dashboard_Connect(http.Controller):
|
||||
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['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['power_on_time'] is not None else 0,
|
||||
'power_on_24_time': last_24_time['power_on_time'] if last_24_time else 0,
|
||||
'alarm_last_24_time': alarm_last_24_time,
|
||||
'alarm_last_24_nums': len(list(set(alarm_last_24_nums))),
|
||||
'idle_count': idle_count,
|
||||
'first_online_time': first_online_duration,
|
||||
'alarm_all_time': alarm_all_time,
|
||||
'alarm_all_nums': len(list(set(alarm_all_nums)))
|
||||
# 'total_count': total_count['total_count'] if total_count else 0
|
||||
}
|
||||
|
||||
conn.close()
|
||||
|
||||
@@ -217,7 +217,7 @@ class Machine_ftp(models.Model):
|
||||
status = fields.Boolean('机床在线状态', readonly=True)
|
||||
# run_status = fields.Selection([('0', '空闲中'), ('1', '加工中'), ('2', '加工中'), ('3', '加工中')], string='机床运行状态',
|
||||
# readonly=True, default='0')
|
||||
run_status = fields.Char('机床运行状态', readonly=True)
|
||||
# run_status = fields.Char('机床运行状态', readonly=True)
|
||||
run_time = fields.Char('机床累计运行时长', readonly=True)
|
||||
# 机床系统日期
|
||||
system_date = fields.Char('机床系统日期', readonly=True)
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
<searchpanel>
|
||||
<field name="routing_type" select="multi" string="工序类型" icon="fa-building" enable_counters="1"/>
|
||||
<field name="state" select="multi" string="状态" icon="fa-building" enable_counters="1"/>
|
||||
|
||||
<field name="construction_period_status" select="multi" icon="fa-building" enable_counters="1"/>
|
||||
<field name="tag_type" select="multi" icon="fa-building" enable_counters="1"/>
|
||||
<!-- <field name="manual_quotation" select="multi" string="" icon="fa-building" enable_counters="1"/>-->
|
||||
</searchpanel>
|
||||
|
||||
|
||||
@@ -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
@@ -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,6 +39,8 @@ class SfMaintenanceEquipment(models.Model):
|
||||
|
||||
crea_url = "/api/machine_tool/create"
|
||||
|
||||
run_status = fields.Char('机床运行状态', readonly=True)
|
||||
|
||||
# AGV运行日志
|
||||
agv_logs = fields.One2many('maintenance.equipment.agv.log', 'equipment_id', string='AGV运行日志')
|
||||
# 1212修改后的字段
|
||||
@@ -120,6 +123,13 @@ class SfMaintenanceEquipment(models.Model):
|
||||
'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='检修时长')
|
||||
@@ -127,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:
|
||||
@@ -589,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
|
||||
@@ -622,7 +689,9 @@ class SfMaintenanceEquipment(models.Model):
|
||||
if next_date < date_now:
|
||||
next_date = date_now
|
||||
else:
|
||||
next_date = equipment.effective_date + timedelta(days=equipment.period)
|
||||
if not equipment.initial_action_date:
|
||||
raise ValidationError('重置保养日期不能为空!!!')
|
||||
next_date = equipment.initial_action_date + timedelta(days=equipment.period)
|
||||
equipment.next_action_date = next_date
|
||||
else:
|
||||
self.next_action_date = False
|
||||
@@ -633,11 +702,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
|
||||
@@ -666,7 +737,9 @@ class SfMaintenanceEquipment(models.Model):
|
||||
if next_date < date_now:
|
||||
next_date = date_now
|
||||
else:
|
||||
next_date = equipment.effective_date + timedelta(days=equipment.overhaul_period)
|
||||
if not equipment.initial_overhaul_date:
|
||||
raise ValidationError('重置维修日期不能为空')
|
||||
next_date = equipment.initial_overhaul_date + timedelta(days=equipment.overhaul_period)
|
||||
equipment.overhaul_date = next_date
|
||||
else:
|
||||
self.overhaul_date = False
|
||||
@@ -733,6 +806,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:
|
||||
@@ -741,6 +815,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:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import json
|
||||
import logging
|
||||
import datetime
|
||||
import requests
|
||||
from odoo import api, fields, models, _
|
||||
@@ -12,6 +13,8 @@ def convert_to_seconds(time_str):
|
||||
|
||||
if time_str is None:
|
||||
return 0
|
||||
if time_str == 0:
|
||||
return 0
|
||||
|
||||
pattern = r"(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?"
|
||||
|
||||
@@ -60,22 +63,22 @@ class SfMaintenanceEquipmentOEE(models.Model):
|
||||
("封存(报废)", "封存(报废)")],
|
||||
default='正常', string="机床状态", related='equipment_id.state')
|
||||
|
||||
online_time = fields.Char('开机时长(小时)', reaonly='True')
|
||||
online_time = fields.Char('开机时长(小时)', readonly='True')
|
||||
|
||||
offline_time = fields.Char('关机时长(小时)', reaonly='True')
|
||||
idle_nums = fields.Integer('待机次数', reaonly='True')
|
||||
offline_time = fields.Char('关机时长(小时)', readonly='True')
|
||||
idle_nums = fields.Integer('待机次数', readonly='True')
|
||||
# 待机时长
|
||||
|
||||
idle_time = fields.Char('待机时长(小时)', reaonly='True')
|
||||
idle_time = fields.Char('待机时长(小时)', readonly='True')
|
||||
|
||||
# 待机率
|
||||
idle_rate = fields.Char('待机率(%)', reaonly='True')
|
||||
idle_rate = fields.Char('待机率(%)', readonly='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')
|
||||
work_time = fields.Char('加工时长(小时)', readonly='True')
|
||||
work_rate = fields.Char('可用率(%)', readonly='True')
|
||||
fault_time = fields.Char('故障时长(小时)', readonly='True')
|
||||
fault_rate = fields.Char('故障率(%)', readonly='True')
|
||||
fault_nums = fields.Integer('故障次数', readonly='True')
|
||||
|
||||
# 设备故障日志
|
||||
sf_maintenance_logs_ids = fields.One2many('sf.maintenance.logs', 'maintenance_equipment_oee_id', '设备故障日志',
|
||||
@@ -86,6 +89,76 @@ class SfMaintenanceEquipmentOEE(models.Model):
|
||||
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')
|
||||
logging.info("base_url=============:%s" % base_url)
|
||||
# 只有当原始 URL 使用 http 时才替换为 https
|
||||
if base_url.startswith("http://"):
|
||||
secure_base_url = base_url.replace("http://", "https://")
|
||||
else:
|
||||
secure_base_url = base_url
|
||||
url_time = secure_base_url + '/api/RunningTimeDetail'
|
||||
logging.info("url_time=============:%s" % url_time)
|
||||
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))
|
||||
logging.info("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')
|
||||
@@ -122,12 +195,19 @@ class SfMaintenanceEquipmentOEE(models.Model):
|
||||
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.work_time = round(
|
||||
(convert_to_seconds(real_dict['cut_time']) - convert_to_seconds(real_dict['cut_24_time'])) / 3600,
|
||||
2)
|
||||
self.offline_time = 24 - (float(self.online_time) if self.online_time else 0)
|
||||
self.idle_time = float(self.online_time) - float(
|
||||
self.work_time) if self.online_time and self.work_time else 0
|
||||
self.idle_rate = round(
|
||||
@@ -140,6 +220,11 @@ class SfMaintenanceEquipmentOEE(models.Model):
|
||||
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()
|
||||
@@ -290,25 +375,25 @@ class SfMaintenanceEquipmentOEELog(models.Model):
|
||||
[("ZXJGZX", "钻铣加工中心"), ("CXJGZX", "车削加工中心"), ("FHJGZX", "复合加工中心")],
|
||||
default="", string="功能类型")
|
||||
machine_tool_picture = fields.Binary('设备图片')
|
||||
type_id = fields.Many2one('sf.machine_tool.type', '品牌型号', reaonly='True')
|
||||
type_id = fields.Many2one('sf.machine_tool.type', '品牌型号', readonly='True')
|
||||
state = fields.Selection([("加工", "加工"), ("关机", "关机"), ("待机", "待机"), ("故障", "故障"),
|
||||
("检修", "检修"), ("保养", "保养")], default="", string="实时状态")
|
||||
online_time = fields.Char('开机时长', reaonly='True')
|
||||
online_time = fields.Char('开机时长', readonly='True')
|
||||
|
||||
offline_time = fields.Char('关机时长', reaonly='True')
|
||||
offline_nums = fields.Integer('关机次数', reaonly='True')
|
||||
offline_time = fields.Char('关机时长', readonly='True')
|
||||
offline_nums = fields.Integer('关机次数', readonly='True')
|
||||
# 待机时长
|
||||
|
||||
idle_time = fields.Char('待机时长', reaonly='True')
|
||||
idle_time = fields.Char('待机时长', readonly='True')
|
||||
|
||||
# 待机率
|
||||
idle_rate = fields.Char('待机率', reaonly='True')
|
||||
idle_rate = fields.Char('待机率', readonly='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')
|
||||
work_time = fields.Char('加工时长', readonly='True')
|
||||
work_rate = fields.Char('可用率', readonly='True')
|
||||
fault_time = fields.Char('故障时长', readonly='True')
|
||||
fault_rate = fields.Char('故障率', readonly='True')
|
||||
fault_nums = fields.Integer('故障次数', readonly='True')
|
||||
|
||||
detail_ids = fields.One2many('maintenance.equipment.oee.log.detail', 'log_id', string='日志详情')
|
||||
|
||||
|
||||
@@ -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,12 @@ 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_sf_group_equipment_user,equipment.request_sf_group_equipment_user,maintenance.model_maintenance_request,sf_group_equipment_user,1,1,1,0
|
||||
access_maintenance_wizard_sf_group_equipment_user,maintenance_wizard_sf_group_equipment_user,model_maintenance_request_wizard,sf_group_equipment_user,1,1,1,0
|
||||
access_maintenance_sf_group_equipment_manager,equipment.request_sf_group_equipment_manager,maintenance.model_maintenance_request,sf_group_equipment_manager,1,1,1,0
|
||||
access_maintenance_wizard_sf_group_equipment_manager,maintenance_wizard_sf_group_equipment_manager,model_maintenance_request_wizard,sf_group_equipment_manager,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
|
||||
|
||||
|
@@ -79,12 +79,12 @@
|
||||
</group>
|
||||
|
||||
</group>
|
||||
<notebook>
|
||||
<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"/>
|
||||
<!-- <notebook> -->
|
||||
<!-- <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> -->
|
||||
@@ -109,7 +109,7 @@
|
||||
<!-- </div> -->
|
||||
<!-- </div> -->
|
||||
<!-- </group> -->
|
||||
</page>
|
||||
<!-- </page> -->
|
||||
<!-- <page string="历史日志详情"> -->
|
||||
<!-- <group> -->
|
||||
<!-- <group> -->
|
||||
@@ -132,7 +132,7 @@
|
||||
<!-- </group> -->
|
||||
<!-- <field name="history_logs_detail"/> -->
|
||||
<!-- </page> -->
|
||||
</notebook>
|
||||
<!-- </notebook> -->
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
|
||||
@@ -80,13 +80,19 @@
|
||||
<field name="user_id" string="维保人"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='close_date']" position="replace">
|
||||
<field name="close_date" attrs="{'invisible': [('done', '!=', True)]}" readonly="True" string="维保日期"/>
|
||||
<field name="close_date" attrs="{'invisible': [('done', '!=', True)]}" readonly="True"
|
||||
string="维保日期"/>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//field[@name='request_date']" position="attributes">
|
||||
<attribute name="string">计划维保日期</attribute>
|
||||
</xpath>
|
||||
|
||||
<sheet>
|
||||
|
||||
<notebook>
|
||||
<page string="维保标准" attrs="{'invisible': [('equipment_maintenance_id', '=', False)]}" context="{'default_standard_id': 'id'}">
|
||||
<page string="维保标准" attrs="{'invisible': [('equipment_maintenance_id', '=', False)]}"
|
||||
context="{'default_standard_id': 'id'}">
|
||||
<field name="maintenance_standards" widget="one2many_list">
|
||||
<tree multi_edit="" editable="">
|
||||
<field name="name" class="table_custom_required"/>
|
||||
@@ -98,11 +104,33 @@
|
||||
</field>
|
||||
</page>
|
||||
</notebook>
|
||||
|
||||
|
||||
</sheet>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="maintenance_request_view_tree_sf" model="ir.ui.view">
|
||||
<field name="name">maintenance.request.view.tree.sf</field>
|
||||
<field name="model">maintenance.request</field>
|
||||
<field name="inherit_id" ref="maintenance.hr_equipment_request_view_tree"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='request_date']" position="replace">
|
||||
<field name="request_date" string="计划维保日期"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='user_id']" position="after">
|
||||
<field name="sf_maintenance_type"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="equipment_request_view_search_sf" model="ir.ui.view">
|
||||
<field name="name">maintenance.request.view.search.sf</field>
|
||||
<field name="model">maintenance.request</field>
|
||||
<field name="inherit_id" ref="maintenance.hr_equipment_request_view_search"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//filter[@name='inactive']" position="replace">
|
||||
<filter string="不活跃的" name="inactive" domain="[('archive', '=', True)]"/>
|
||||
<filter string="已归档" name="in_active" domain="[('active', '=', False)]"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -110,8 +138,8 @@
|
||||
<record id="hr_equipment_request_action1" model="ir.actions.act_window">
|
||||
<field name="name">维保计划</field>
|
||||
<field name="res_model">maintenance.request</field>
|
||||
<field name="view_mode">kanban,tree,form,pivot,graph,calendar</field>
|
||||
<field name="view_id" ref="maintenance.hr_equipment_request_view_kanban"/>
|
||||
<field name="view_mode">tree,kanban,form,pivot,graph,calendar</field>
|
||||
<field name="view_id" ref="sf_maintenance.maintenance_request_view_tree_sf"/>
|
||||
<field name="context">{'default_user_id': uid}</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
|
||||
@@ -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="长"/>
|
||||
@@ -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"
|
||||
@@ -197,7 +197,7 @@
|
||||
<field name="C_tool_time"/>
|
||||
</group>
|
||||
<group string="主轴">
|
||||
<field name="taper_type_id" attrs="{'required': [('equipment_type', '=', '机床')]}" />
|
||||
<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)"/>
|
||||
@@ -256,14 +256,14 @@
|
||||
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'/>
|
||||
<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>
|
||||
@@ -979,16 +979,32 @@
|
||||
</group>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//field[@name='next_action_date']" position="before">
|
||||
|
||||
<xpath expr="//page[@name='maintenance']" position="replace">
|
||||
<page string="维保" name="maintenance">
|
||||
<group>
|
||||
<group string="保养">
|
||||
<field name='eq_maintenance_id' force_save="1" widget="many2one"/>
|
||||
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//div[hasclass('o_row')][field[@name='maintenance_duration']]" position="after">
|
||||
|
||||
|
||||
<field name='overhaul_id' options="{'no_create':True}"/>
|
||||
<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">
|
||||
@@ -1001,9 +1017,20 @@
|
||||
<field name="overhaul_duration"/>
|
||||
hours
|
||||
</div>
|
||||
<field name='equipment_maintenance_standards_ids' widget="many2many_tags" invisible="1"/>
|
||||
<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="//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>
|
||||
|
||||
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>
|
||||
@@ -2,7 +2,7 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
{
|
||||
'name': '机企猫智能工厂 制造管理',
|
||||
'version': '1.0',
|
||||
'version': '1.1',
|
||||
'summary': '智能工厂制造模块',
|
||||
'sequence': 1,
|
||||
'description': """
|
||||
@@ -10,8 +10,9 @@
|
||||
""",
|
||||
'category': 'sf',
|
||||
'website': 'https://www.sf.jikimo.com',
|
||||
'depends': ['sf_base', 'sf_maintenance', 'web_widget_model_viewer', 'sf_warehouse'],
|
||||
'depends': ['sf_base', 'sf_maintenance', 'web_widget_model_viewer', 'sf_warehouse','jikimo_attachment_viewer', 'jikimo_sale_multiple_supply_methods'],
|
||||
'data': [
|
||||
'data/cron_data.xml',
|
||||
'data/stock_data.xml',
|
||||
'data/empty_racks_data.xml',
|
||||
'data/panel_data.xml',
|
||||
@@ -21,6 +22,8 @@
|
||||
'wizard/workpiece_delivery_views.xml',
|
||||
'wizard/rework_wizard_views.xml',
|
||||
'wizard/production_wizard_views.xml',
|
||||
'wizard/production_technology_wizard_views.xml',
|
||||
'wizard/production_technology_re_adjust_wizard_views.xml',
|
||||
'views/mrp_views_menus.xml',
|
||||
'views/agv_scheduling_views.xml',
|
||||
'views/stock_lot_views.xml',
|
||||
@@ -29,10 +32,12 @@
|
||||
'views/production_line_view.xml',
|
||||
'views/mrp_workcenter_views.xml',
|
||||
'views/mrp_workorder_view.xml',
|
||||
'views/stock_picking_view.xml',
|
||||
'views/model_type_view.xml',
|
||||
'views/agv_setting_views.xml',
|
||||
'views/sf_maintenance_equipment.xml',
|
||||
'views/res_config_settings_views.xml',
|
||||
'views/sale_order_views.xml',
|
||||
],
|
||||
'assets': {
|
||||
|
||||
@@ -45,6 +50,8 @@
|
||||
'sf_manufacturing/static/src/scss/kanban_change.scss',
|
||||
'sf_manufacturing/static/src/xml/button_show_on_tree.xml',
|
||||
'sf_manufacturing/static/src/js/workpiece_delivery_wizard_confirm.js',
|
||||
'sf_manufacturing/static/src/js/qr.js',
|
||||
'sf_manufacturing/static/src/xml/qr.xml',
|
||||
]
|
||||
|
||||
},
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
from . import controllers
|
||||
from . import workpiece
|
||||
from . import main
|
||||
|
||||
@@ -189,6 +189,7 @@ class Manufacturing_Connect(http.Controller):
|
||||
request.env['sf.production.plan'].sudo().search([('production_id', '=', production_id)]).write(
|
||||
{'actual_start_time': workorder.date_start,
|
||||
'state': 'processing'})
|
||||
res.update({'workorder_id': workorder.id})
|
||||
|
||||
except Exception as e:
|
||||
res = {'Succeed': False, 'ErrorCode': 202, 'Error': e}
|
||||
@@ -595,14 +596,6 @@ class Manufacturing_Connect(http.Controller):
|
||||
if panel_workorder:
|
||||
panel_workorder.write({'production_line_state': '已下产线'})
|
||||
workorder.write({'state': 'to be detected'})
|
||||
# workpiece_delivery = request.env['sf.workpiece.delivery'].sudo().search(
|
||||
# [
|
||||
# ('rfid_code', '=', rfid_code), ('type', '=', '下产线'),
|
||||
# ('production_id', '=', order.production_id.id),
|
||||
# ('workorder_id', '=', order.id),
|
||||
# ('workorder_state', '=', 'done')])
|
||||
# if workpiece_delivery:
|
||||
# delivery_Arr.append(workpiece_delivery.id)
|
||||
else:
|
||||
res = {'Succeed': False, 'ErrorCode': 204,
|
||||
'Error': 'DeviceId为%s没有对应的已配送工件数据' % ret['DeviceId']}
|
||||
|
||||
54
sf_manufacturing/controllers/main.py
Normal file
54
sf_manufacturing/controllers/main.py
Normal file
@@ -0,0 +1,54 @@
|
||||
import logging
|
||||
import json
|
||||
import traceback
|
||||
|
||||
from odoo import http
|
||||
from odoo.http import request
|
||||
from odoo.addons.sf_bf_connect.controllers.controllers import Sf_Bf_Connect
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class JikimoSaleRoutePicking(Sf_Bf_Connect):
|
||||
|
||||
@http.route('/api/bfm_process_order/list', type='http', auth='sf_token', methods=['GET', 'POST'], csrf=False,
|
||||
cors="*")
|
||||
def get_bfm_process_order_list(self, **kw):
|
||||
"""
|
||||
接收业务平台加工订单分配工厂时传送来的订单数据并生成销售订单和产品及坯料
|
||||
:param kw:
|
||||
:return:
|
||||
"""
|
||||
res = {'status': 1, 'factory_order_no': ''}
|
||||
# _logger.info('get_bfm_process_order_list:%s' % kw['order_number'])
|
||||
try:
|
||||
product_id = request.env.ref('jikimo_sale_multiple_supply_methods.product_template_default').with_context(active_test=False).sudo().product_variant_id
|
||||
_logger.info('product_id:%s' % product_id)
|
||||
company_id = request.env.ref('base.main_company').sudo()
|
||||
bfm_process_order_list = json.loads(kw['bfm_process_order_list'])
|
||||
order_id = request.env['sale.order'].with_user(request.env.ref("base.user_admin")).sale_order_create(
|
||||
company_id, kw['delivery_name'], kw['delivery_telephone'], kw['delivery_address'],
|
||||
kw['delivery_end_date'], kw['payments_way'], kw['pay_way'], state='draft')
|
||||
i = 1
|
||||
# 给sale_order的default_code字段赋值
|
||||
# aa = request.env['sale.order'].sudo().search([('name', '=', order_id.name)])
|
||||
# _logger.info('get_bfm_process_or===================================:%s' % order_id.name)
|
||||
order_id.default_code = kw['order_number']
|
||||
if kw.get('logistics_way'):
|
||||
order_id.logistics_way = kw['logistics_way']
|
||||
for item in bfm_process_order_list:
|
||||
if item.get('embryo_redundancy_id'):
|
||||
item['embryo_redundancy'] = request.env['sf.embryo.redundancy'].sudo().search([('code', '=', item['embryo_redundancy_id'])], limit=1)
|
||||
item['embryo_redundancy_id'] = item['embryo_redundancy'].id
|
||||
product = request.env['product.template'].sudo().product_create(product_id, item, order_id,
|
||||
kw['order_number'], i)
|
||||
product.product_tmpl_id.is_customer_provided = True if item['embryo_redundancy_id'] else False
|
||||
order_id.with_user(request.env.ref("base.user_admin")).sale_order_create_line(product, item)
|
||||
i += 1
|
||||
res['factory_order_no'] = order_id.name
|
||||
except Exception as e:
|
||||
traceback_error = traceback.format_exc()
|
||||
logging.error('get_bfm_process_order_list error: %s' % traceback_error)
|
||||
res['status'] = -1
|
||||
res['message'] = '工厂创建销售订单和产品失败,请联系管理员'
|
||||
request.cr.rollback()
|
||||
return json.JSONEncoder().encode(res)
|
||||
29
sf_manufacturing/data/cron_data.xml
Normal file
29
sf_manufacturing/data/cron_data.xml
Normal file
@@ -0,0 +1,29 @@
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<record model="ir.cron" id="ir_cron_update_construction_period_status">
|
||||
<field name="name">工期状态变更</field>
|
||||
<field name="model_id" ref="model_mrp_workorder"/>
|
||||
<field name="state">code</field>
|
||||
<field name="code">model._corn_update_construction_period_status()</field>
|
||||
<field name="interval_number">12</field>
|
||||
<field name="interval_type">hours</field>
|
||||
<field name="numbercall">-1</field>
|
||||
<field name="doall" eval="False"/>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<field name="active" eval="True"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.cron" id="ir_cron_update_delivery_status">
|
||||
<field name="name">交期状态变更</field>
|
||||
<field name="model_id" ref="model_mrp_production"/>
|
||||
<field name="state">code</field>
|
||||
<field name="code">model._corn_update_delivery_status()</field>
|
||||
<field name="interval_number">12</field>
|
||||
<field name="interval_type">hours</field>
|
||||
<field name="numbercall">-1</field>
|
||||
<field name="doall" eval="False"/>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<field name="active" eval="True"/>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
12
sf_manufacturing/migrations/1.1/post-migrate.py
Normal file
12
sf_manufacturing/migrations/1.1/post-migrate.py
Normal file
@@ -0,0 +1,12 @@
|
||||
# migrations/1.1.0/post-migrate.py
|
||||
from odoo import api, SUPERUSER_ID
|
||||
|
||||
def migrate(cr, version):
|
||||
# 获取环境
|
||||
env = api.Environment(cr, SUPERUSER_ID, {})
|
||||
|
||||
# 示例:添加新字段
|
||||
env.ref('sf_dlm.product_embryo_sf_self_machining').product_tmpl_id.write({'categ_type': '坯料'})
|
||||
env.ref('sf_dlm.product_template_sf').product_tmpl_id.write({'categ_type': '成品'})
|
||||
env.ref('sf_dlm.product_embryo_sf_outsource').product_tmpl_id.write({'categ_type': '坯料'})
|
||||
env.ref('sf_dlm.product_embryo_sf_purchase').product_tmpl_id.write({'categ_type': '坯料'})
|
||||
@@ -11,3 +11,7 @@ from . import production_line_base
|
||||
from . import agv_setting
|
||||
from . import agv_scheduling
|
||||
from . import res_config_setting
|
||||
from . import sf_technology_design
|
||||
from . import sf_production_common
|
||||
from . import sale_order
|
||||
from . import quick_easy_order
|
||||
|
||||
@@ -6,42 +6,29 @@ class ModelType(models.Model):
|
||||
_description = '模型类型'
|
||||
|
||||
name = fields.Char('名称')
|
||||
embryo_tolerance = fields.Integer('坯料容余')
|
||||
# embryo_tolerance = fields.Char('坯料容余')
|
||||
embryo_tolerance_id = fields.Many2one('sf.embryo.redundancy', string='坯料冗余')
|
||||
product_routing_tmpl_ids = fields.One2many('sf.product.model.type.routing.sort', 'product_model_type_id',
|
||||
'成品工序模板')
|
||||
'成品工序模板(自动化产线加工')
|
||||
embryo_routing_tmpl_ids = fields.One2many('sf.embryo.model.type.routing.sort', 'embryo_model_type_id',
|
||||
'坯料工序模板')
|
||||
'坯料工序模板(人工线下加工)')
|
||||
surface_technics_routing_tmpl_ids = fields.One2many('sf.surface_technics.model.type.routing.sort',
|
||||
'surface_technics_model_type_id',
|
||||
'表面工艺工序模板')
|
||||
manual_product_routing_tmpl_ids = fields.One2many('sf.manual.product.model.type.routing.sort',
|
||||
'manual_product_model_type_id',
|
||||
'成品工序模板(人工线下加工)')
|
||||
|
||||
|
||||
class ProductModelTypeRoutingSort(models.Model):
|
||||
_name = 'sf.product.model.type.routing.sort'
|
||||
_description = '成品工序排序'
|
||||
_description = '成品工序排序(自动化产线加工)'
|
||||
|
||||
sequence = fields.Integer('Sequence')
|
||||
route_workcenter_id = fields.Many2one('mrp.routing.workcenter',
|
||||
domain=[('routing_type', 'in', ['装夹预调', 'CNC加工', '解除装夹'])])
|
||||
is_repeat = fields.Boolean('重复', related='route_workcenter_id.is_repeat')
|
||||
|
||||
# routing_type = fields.Selection([
|
||||
# ('获取CNC加工程序', '获取CNC加工程序'),
|
||||
# ('装夹', '装夹'),
|
||||
# ('前置三元定位检测', '前置三元定位检测'),
|
||||
# ('CNC加工', 'CNC加工'),
|
||||
# ('后置三元质量检测', '后置三元质量检测'),
|
||||
# ('解除装夹', '解除装夹'), ('切割', '切割'), ('表面工艺', '表面工艺')
|
||||
# ], string="工序类型", compute='_compute_route_workcenter_id')
|
||||
#
|
||||
# @api.depends('route_workcenter_id')
|
||||
# def _compute_route_workcenter_id(self):
|
||||
# for record in self:
|
||||
# if record:
|
||||
# record.routing_type = record.route_workcenter_id.routing_type
|
||||
|
||||
routing_type = fields.Selection(string="工序类型", related='route_workcenter_id.routing_type')
|
||||
|
||||
workcenter_ids = fields.Many2many('mrp.workcenter', required=False, related='route_workcenter_id.workcenter_ids')
|
||||
product_model_type_id = fields.Many2one('sf.model.type')
|
||||
|
||||
@@ -57,24 +44,7 @@ class EmbryoModelTypeRoutingSort(models.Model):
|
||||
sequence = fields.Integer('Sequence')
|
||||
route_workcenter_id = fields.Many2one('mrp.routing.workcenter', domain=[('routing_type', 'in', ['切割'])])
|
||||
is_repeat = fields.Boolean('重复', related='route_workcenter_id.is_repeat')
|
||||
|
||||
# routing_type = fields.Selection([
|
||||
# ('获取CNC加工程序', '获取CNC加工程序'),
|
||||
# ('装夹', '装夹'),
|
||||
# ('前置三元定位检测', '前置三元定位检测'),
|
||||
# ('CNC加工', 'CNC加工'),
|
||||
# ('后置三元质量检测', '后置三元质量检测'),
|
||||
# ('解除装夹', '解除装夹'), ('切割', '切割'), ('表面工艺', '表面工艺')
|
||||
# ], string="工序类型", compute='_compute_route_workcenter_id')
|
||||
#
|
||||
# @api.depends('route_workcenter_id')
|
||||
# def _compute_route_workcenter_id(self):
|
||||
# for record in self:
|
||||
# if record:
|
||||
# record.routing_type = record.route_workcenter_id.routing_type
|
||||
|
||||
routing_type = fields.Selection(string="工序类型", related='route_workcenter_id.routing_type')
|
||||
|
||||
workcenter_ids = fields.Many2many('mrp.workcenter', required=False, related='route_workcenter_id.workcenter_ids')
|
||||
embryo_model_type_id = fields.Many2one('sf.model.type')
|
||||
|
||||
@@ -90,24 +60,7 @@ class SurfaceTechnicsModelTypeRoutingSort(models.Model):
|
||||
sequence = fields.Integer('Sequence')
|
||||
route_workcenter_id = fields.Many2one('mrp.routing.workcenter', domain=[('routing_type', 'in', ['表面工艺'])])
|
||||
is_repeat = fields.Boolean('重复', related='route_workcenter_id.is_repeat')
|
||||
|
||||
# routing_type = fields.Selection([
|
||||
# ('获取CNC加工程序', '获取CNC加工程序'),
|
||||
# ('装夹', '装夹'),
|
||||
# ('前置三元定位检测', '前置三元定位检测'),
|
||||
# ('CNC加工', 'CNC加工'),
|
||||
# ('后置三元质量检测', '后置三元质量检测'),
|
||||
# ('解除装夹', '解除装夹'), ('切割', '切割'), ('表面工艺', '表面工艺')
|
||||
# ], string="工序类型", compute='_compute_route_workcenter_id')
|
||||
#
|
||||
# @api.depends('route_workcenter_id')
|
||||
# def _compute_route_workcenter_id(self):
|
||||
# for record in self:
|
||||
# if record:
|
||||
# record.routing_type = record.route_workcenter_id.routing_type
|
||||
|
||||
routing_type = fields.Selection(string="工序类型", related='route_workcenter_id.routing_type')
|
||||
|
||||
workcenter_ids = fields.Many2many('mrp.workcenter', required=False, related='route_workcenter_id.workcenter_ids')
|
||||
surface_technics_model_type_id = fields.Many2one('sf.model.type')
|
||||
|
||||
@@ -116,3 +69,18 @@ class SurfaceTechnicsModelTypeRoutingSort(models.Model):
|
||||
'route_model_type_uniq', 'unique (route_workcenter_id,surface_technics_model_type_id)',
|
||||
'表面工艺工序不能重复!')
|
||||
]
|
||||
|
||||
class ManualProductModelTypeRoutingSort(models.Model):
|
||||
_name = 'sf.manual.product.model.type.routing.sort'
|
||||
_description = '成品工序排序(人工线下加工)'
|
||||
|
||||
sequence = fields.Integer('Sequence')
|
||||
route_workcenter_id = fields.Many2one('mrp.routing.workcenter')
|
||||
is_repeat = fields.Boolean('重复', related='route_workcenter_id.is_repeat')
|
||||
routing_type = fields.Selection(string="工序类型", related='route_workcenter_id.routing_type')
|
||||
workcenter_ids = fields.Many2many('mrp.workcenter', required=False, related='route_workcenter_id.workcenter_ids')
|
||||
manual_product_model_type_id = fields.Many2one('sf.model.type')
|
||||
|
||||
_sql_constraints = [
|
||||
('route_model_type_uniq', 'unique (route_workcenter_id,manual_product_model_type_id)', '成品工序不能重复!')
|
||||
]
|
||||
@@ -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,118 @@ 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
|
||||
|
||||
def _compute_default_delivery_status(self):
|
||||
try:
|
||||
if self.state == 'cancel':
|
||||
return False
|
||||
if not self.deadline_of_delivery:
|
||||
return False
|
||||
hours = self.get_hours_diff()
|
||||
if hours >= 48:
|
||||
return '正常'
|
||||
elif hours > 0 and hours < 48 and self.state != 'done':
|
||||
return '预警'
|
||||
elif hours > 0 and hours < 48 and self.state == 'done':
|
||||
return '正常'
|
||||
else:
|
||||
return '已逾期'
|
||||
except Exception as e:
|
||||
logging.error("Error processing production ID {}: {}".format(self.id, e))
|
||||
raise e
|
||||
|
||||
@api.depends('state', 'deadline_of_delivery')
|
||||
def _compute_delivery_status(self):
|
||||
for production in self:
|
||||
delivery_status = production._compute_default_delivery_status()
|
||||
if delivery_status and production.delivery_status != delivery_status:
|
||||
production.delivery_status = delivery_status
|
||||
|
||||
delivery_status = fields.Selection([('正常', '正常'), ('预警', '预警'), ('已逾期', '已逾期')], string='交期状态',
|
||||
store=True,
|
||||
compute='_compute_delivery_status',
|
||||
default=lambda self: self._compute_default_delivery_status())
|
||||
|
||||
def get_page_all_records(self, model_name, func, domain, page_size=100):
|
||||
# 获取模型对象
|
||||
model = self.env[model_name].sudo()
|
||||
|
||||
# 初始化分页参数
|
||||
page_number = 1
|
||||
while True:
|
||||
# 计算偏移量
|
||||
offset = (page_number - 1) * page_size
|
||||
|
||||
# 获取当前页的数据
|
||||
records = model.search(domain, limit=page_size, offset=offset)
|
||||
|
||||
# 如果没有更多记录,退出循环
|
||||
if not records:
|
||||
break
|
||||
|
||||
# 将当前页的数据添加到结果列表
|
||||
func(records)
|
||||
# 增加页码
|
||||
page_number += 1
|
||||
|
||||
def run_compute_delivery_status(self, records):
|
||||
records._compute_delivery_status()
|
||||
|
||||
def _corn_update_delivery_status(self):
|
||||
need_list = [
|
||||
'draft',
|
||||
'technology_to_confirmed',
|
||||
'confirmed',
|
||||
'pending_cam',
|
||||
'progress',
|
||||
'rework',
|
||||
'scrap',
|
||||
'to_close',
|
||||
]
|
||||
# previous_workorder = self.env['mrp.production'].search([('state', 'in', need_list)])
|
||||
self.get_page_all_records('mrp.production', self.run_compute_delivery_status,
|
||||
[('state', 'in', need_list)], 100)
|
||||
|
||||
def get_hours_diff(self):
|
||||
# 获取当前日期和时间
|
||||
current_datetime = fields.Datetime.now()
|
||||
|
||||
# 将 date_field 转换为 datetime 对象
|
||||
if self.deadline_of_delivery:
|
||||
date_obj = fields.Date.from_string(self.deadline_of_delivery)
|
||||
# 将 date 对象转换为 datetime 对象,设置时间为 00:00:00
|
||||
date_obj = datetime.datetime.combine(date_obj, datetime.time.min)
|
||||
|
||||
# 计算两个日期之间的差值
|
||||
delta = date_obj - current_datetime
|
||||
|
||||
# 返回差值的小时数
|
||||
return int(delta.total_seconds() / 3600)
|
||||
else:
|
||||
return 0.0
|
||||
|
||||
@api.depends('workorder_ids.tool_state_remark')
|
||||
def _compute_tool_state_remark(self):
|
||||
for item in self:
|
||||
@@ -75,6 +187,7 @@ class MrpProduction(models.Model):
|
||||
# ])
|
||||
state = fields.Selection([
|
||||
('draft', '草稿'),
|
||||
('technology_to_confirmed', '待工艺确认'),
|
||||
('confirmed', '待排程'),
|
||||
('pending_cam', '待加工'),
|
||||
('progress', '加工中'),
|
||||
@@ -118,10 +231,14 @@ class MrpProduction(models.Model):
|
||||
], string='工序状态', default='待装夹')
|
||||
|
||||
# 零件图号
|
||||
part_number = fields.Char('零件图号')
|
||||
part_number = fields.Char('零件图号', related='product_id.part_number', readonly=True)
|
||||
|
||||
# 上传零件图纸
|
||||
part_drawing = fields.Binary('零件图纸')
|
||||
part_drawing = fields.Binary('零件图纸', related='product_id.machining_drawings', readonly=True)
|
||||
|
||||
quality_standard = fields.Binary('质检标准', related='product_id.quality_standard', readonly=True)
|
||||
|
||||
part_name = fields.Char(string='零件名称', related='product_id.part_name', readonly=True)
|
||||
|
||||
@api.depends('product_id.manual_quotation')
|
||||
def _compute_manual_quotation(self):
|
||||
@@ -133,6 +250,8 @@ class MrpProduction(models.Model):
|
||||
is_remanufacture = fields.Boolean('是否重新制造', default=False)
|
||||
remanufacture_count = fields.Integer("重新制造订单数量", compute='_compute_remanufacture_production_ids')
|
||||
remanufacture_production_id = fields.Many2one('mrp.production', string='')
|
||||
technology_design_ids = fields.One2many('sf.technology.design', 'production_id', string='工艺设计')
|
||||
is_adjust = fields.Boolean('是否退回调整', default=False)
|
||||
|
||||
@api.depends('remanufacture_production_id')
|
||||
def _compute_remanufacture_production_ids(self):
|
||||
@@ -159,7 +278,7 @@ class MrpProduction(models.Model):
|
||||
|
||||
@api.depends(
|
||||
'move_raw_ids.state', 'move_raw_ids.quantity_done', 'move_finished_ids.state', 'tool_state',
|
||||
'workorder_ids.state', 'product_qty', 'qty_producing', 'schedule_state')
|
||||
'workorder_ids.state', 'product_qty', 'qty_producing', 'schedule_state', 'programming_state', 'is_adjust')
|
||||
def _compute_state(self):
|
||||
for production in self:
|
||||
if not production.state or not production.product_uom_id:
|
||||
@@ -189,22 +308,33 @@ class MrpProduction(models.Model):
|
||||
precision_rounding=move.product_uom.rounding or move.product_id.uom_id.rounding)
|
||||
for move in production.move_raw_ids if move.product_id):
|
||||
production.state = 'progress'
|
||||
|
||||
# # 新添加的状态逻辑
|
||||
if (
|
||||
production.state == 'to_close' or production.state == 'progress') and production.schedule_state == '未排':
|
||||
# 新添加的状态逻辑
|
||||
if production.state in ['to_close', 'progress',
|
||||
'technology_to_confirmed'] and production.schedule_state == '未排':
|
||||
if not production.workorder_ids or production.is_adjust is True:
|
||||
production.state = 'technology_to_confirmed'
|
||||
else:
|
||||
if production.is_adjust is True:
|
||||
production.state = 'technology_to_confirmed'
|
||||
else:
|
||||
production.state = 'confirmed'
|
||||
elif production.state == 'pending_cam' and production.schedule_state == '未排':
|
||||
production.state = 'confirmed'
|
||||
elif production.state == 'to_close' and production.schedule_state == '已排':
|
||||
production.state = 'pending_cam'
|
||||
|
||||
elif production.state == 'confirmed' and production.is_adjust is True:
|
||||
production.state = 'technology_to_confirmed'
|
||||
if production.state == 'confirmed' and production.schedule_state == '已排':
|
||||
production.state = 'pending_cam'
|
||||
if production.state == 'progress':
|
||||
if all(wo_state not in ('progress', 'done', 'rework', 'scrap') for wo_state in
|
||||
production.workorder_ids.mapped('state')):
|
||||
production.state = 'pending_cam'
|
||||
if production.is_rework is True:
|
||||
production.state = 'rework'
|
||||
if (production.state == 'rework' and production.tool_state == '0'
|
||||
and production.schedule_state == '已排' and production.is_rework is False):
|
||||
production.state = 'pending_cam'
|
||||
# if production.state == 'pending_cam':
|
||||
# if all(wo_state in 'done' for wo_state in production.workorder_ids.mapped('state')):
|
||||
# production.state = 'done'
|
||||
@@ -227,6 +357,104 @@ class MrpProduction(models.Model):
|
||||
if production.tool_state == '2':
|
||||
production.state = 'rework'
|
||||
|
||||
# 退回调整
|
||||
def technology_back_adjust(self):
|
||||
process_parameters = []
|
||||
domain = [('state', '=', 'confirmed'), ('origin', '=', self.origin)]
|
||||
if self.production_type == '自动化产线加工':
|
||||
cloud_programming = self._cron_get_programming_state()
|
||||
if cloud_programming['send_state'] == 'sending':
|
||||
raise UserError(_("编程文件正在下发中,请稍后重试"))
|
||||
domain += [('programming_no', '=', self.programming_no)]
|
||||
# 带排程的制造订单
|
||||
production_confirmed = self.env['mrp.production'].search(domain)
|
||||
for special in production_confirmed.technology_design_ids:
|
||||
if special.process_parameters_id:
|
||||
product_production_process = self.env['product.template'].search(
|
||||
[('server_product_process_parameters_id', '=', special.process_parameters_id.id)])
|
||||
if not product_production_process:
|
||||
if special.process_parameters_id not in process_parameters:
|
||||
process_parameters.append(special.process_parameters_id.display_name)
|
||||
if process_parameters:
|
||||
raise UserError(_("【工艺设计】-【参数】为%s的在【产品】中不存在,请先创建", ", ".join(process_parameters)))
|
||||
if production_confirmed:
|
||||
return {
|
||||
'name': _('退回调整'),
|
||||
'type': 'ir.actions.act_window',
|
||||
'view_mode': 'form',
|
||||
'res_model': 'sf.production.technology.re_adjust.wizard',
|
||||
'target': 'new',
|
||||
'context': {
|
||||
'default_production_id': self.id,
|
||||
'default_origin': self.origin,
|
||||
}}
|
||||
|
||||
# 工艺确认
|
||||
def technology_confirm(self):
|
||||
process_parameters = []
|
||||
account_moves = []
|
||||
parameters_not = []
|
||||
special_design = self.technology_design_ids.filtered(
|
||||
lambda a: a.routing_tag == 'special' and a.is_auto is False)
|
||||
for special in special_design:
|
||||
if special.route_id.routing_type == '表面工艺' and not special.process_parameters_id:
|
||||
parameters_not.append(special.route_id.name)
|
||||
if special.process_parameters_id:
|
||||
product_production_process = self.env['product.template'].search(
|
||||
[('server_product_process_parameters_id', '=', special.process_parameters_id.id)])
|
||||
if not product_production_process:
|
||||
if special.process_parameters_id not in process_parameters:
|
||||
process_parameters.append(special.process_parameters_id.display_name)
|
||||
purchase = self.env['purchase.order'].search([('origin', '=', special.production_id.name)])
|
||||
account = self.env['account.move'].search([('id', 'in', purchase.invoice_ids)])
|
||||
if account.state not in ['cancel', False]:
|
||||
if purchase.name not in account_moves:
|
||||
account_moves.append(purchase.name)
|
||||
if account_moves:
|
||||
raise UserError(_("请联系工厂生产经理对采购订单为%s生成的账单进行取消", ", ".join(account_moves)))
|
||||
if parameters_not:
|
||||
raise UserError(_("【工艺设计】-【工序】为%s未选择参数,请选择", ", ".join(parameters_not)))
|
||||
if process_parameters:
|
||||
raise UserError(_("【工艺设计】-【参数】为%s的在【产品】中不存在,请先创建", ", ".join(process_parameters)))
|
||||
# 判断同一个加工面的标准工序的顺序是否依次排序
|
||||
error_panel = []
|
||||
technology_design = self.technology_design_ids.filtered(lambda a: a.routing_tag == 'standard').sorted(
|
||||
key=lambda m: m.sequence)
|
||||
for index, design in enumerate(technology_design):
|
||||
routing_type = design.route_id.routing_type
|
||||
if index < len(technology_design) - 1:
|
||||
next_index = index + 1
|
||||
next_design = technology_design[next_index]
|
||||
next_design_routing_type = next_design.route_id.routing_type
|
||||
# logging.info('当前工序和加工面: %s-%s' % (design.route_id.name, design.panel))
|
||||
# logging.info('下一个工序和加工面: %s-%s' % (next_design.route_id.name, next_design.panel))
|
||||
if design.panel is not False:
|
||||
if design.panel != next_design.panel:
|
||||
if index == 0:
|
||||
raise UserError('【加工面】为%s的标准工序里含有其他加工面的工序,请调整后重试' % design.panel)
|
||||
if routing_type not in ['解除装夹']:
|
||||
raise UserError('【加工面】为%s的标准工序顺序有误,请调整后重试' % design.panel)
|
||||
if design.panel == next_design.panel:
|
||||
if (routing_type == '装夹预调' and next_design_routing_type == '解除装夹') or (
|
||||
routing_type == 'CNC加工' and next_design_routing_type == '装夹预调'):
|
||||
if design.panel not in error_panel:
|
||||
error_panel.append(design.panel)
|
||||
else:
|
||||
if not error_panel and not process_parameters:
|
||||
return {
|
||||
'name': _('工艺确认'),
|
||||
'type': 'ir.actions.act_window',
|
||||
'view_mode': 'form',
|
||||
'res_model': 'sf.production.technology.wizard',
|
||||
'target': 'new',
|
||||
'context': {
|
||||
'default_production_id': self.id,
|
||||
'default_origin': self.origin,
|
||||
}}
|
||||
if error_panel:
|
||||
raise UserError(_("【加工面】为%s的标准工序顺序有误,请调整后重试", ", ".join(error_panel)))
|
||||
return True
|
||||
|
||||
def action_check(self):
|
||||
"""
|
||||
审核启用
|
||||
@@ -296,8 +524,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'])
|
||||
@@ -422,8 +655,8 @@ class MrpProduction(models.Model):
|
||||
if self.move_finished_ids.filtered(lambda m: m.product_id == self.product_id).move_line_ids:
|
||||
self.move_finished_ids.filtered(
|
||||
lambda m: m.product_id == self.product_id).move_line_ids.lot_id = self.lot_producing_id
|
||||
if self.product_id.tracking == 'serial':
|
||||
self._set_qty_producing()
|
||||
# if self.product_id.tracking == 'serial':
|
||||
# self._set_qty_producing()
|
||||
|
||||
# 重载根据工序生成工单的程序:如果产品BOM中没有工序时,
|
||||
# 根据产品对应的模板类型中工序,去生成工单;
|
||||
@@ -459,122 +692,77 @@ class MrpProduction(models.Model):
|
||||
'operation_id': operation.id,
|
||||
'state': 'pending',
|
||||
}]
|
||||
if production.product_id.categ_id.type == '成品':
|
||||
# # 根据加工面板的面数及对应的工序模板生成工单
|
||||
i = 0
|
||||
processing_panel_len = len(production.product_id.model_processing_panel.split(','))
|
||||
for k in (production.product_id.model_processing_panel.split(',')):
|
||||
product_routing_workcenter = self.env['sf.product.model.type.routing.sort'].search(
|
||||
[('product_model_type_id', '=', production.product_id.product_model_type_id.id)],
|
||||
order='sequence asc'
|
||||
)
|
||||
i += 1
|
||||
for route in product_routing_workcenter:
|
||||
if route.is_repeat is True:
|
||||
if production.product_id.categ_id.type in ['成品', '坯料']:
|
||||
# # 根据工序设计生成工单
|
||||
for route in production.technology_design_ids:
|
||||
workorder_has = self.env['mrp.workorder'].search(
|
||||
[('technology_design_id', '=', route.id), ('production_id', '=', production.id)])
|
||||
if not workorder_has:
|
||||
if route.route_id.routing_type not in ['表面工艺']:
|
||||
workorders_values.append(
|
||||
self.env['mrp.workorder'].json_workorder_str(k, production, route, item))
|
||||
# if i == processing_panel_len and route.routing_type == '解除装夹':
|
||||
# workorders_values.append(
|
||||
# self.env['mrp.workorder'].json_workorder_str(k, production, route))
|
||||
# 表面工艺工序
|
||||
# 获取表面工艺id
|
||||
# 工序id
|
||||
surface_technics_arr = []
|
||||
route_workcenter_arr = []
|
||||
for item in production.product_id.product_model_type_id.surface_technics_routing_tmpl_ids:
|
||||
if item.route_workcenter_id.surface_technics_id.id:
|
||||
for process_param in production.product_id.model_process_parameters_ids:
|
||||
logging.info('process_param:%s%s' % (process_param.id, process_param.name))
|
||||
if item.route_workcenter_id.surface_technics_id == process_param.process_id:
|
||||
logging.info(
|
||||
'surface_technics_id:%s%s' % (item.route_workcenter_id.surface_technics_id.id,
|
||||
item.route_workcenter_id.surface_technics_id.name))
|
||||
surface_technics_arr.append(item.route_workcenter_id.surface_technics_id.id)
|
||||
route_workcenter_arr.append(item.route_workcenter_id.id)
|
||||
if surface_technics_arr:
|
||||
production_process = self.env['sf.production.process'].search(
|
||||
[('id', 'in', surface_technics_arr)],
|
||||
order='sequence asc'
|
||||
)
|
||||
for p in production_process:
|
||||
logging.info('production_process:%s' % p.name)
|
||||
# if production_process:
|
||||
process_parameter = production.product_id.model_process_parameters_ids.filtered(
|
||||
lambda pm: pm.process_id.id == p.id)
|
||||
if process_parameter:
|
||||
# 产品为表面工艺服务的供应商
|
||||
self.env['mrp.workorder'].json_workorder_str(production, route))
|
||||
else:
|
||||
product_production_process = self.env['product.template'].search(
|
||||
[('server_product_process_parameters_id', '=', process_parameter.id)])
|
||||
if product_production_process:
|
||||
route_production_process = self.env[
|
||||
'mrp.routing.workcenter'].search(
|
||||
[('surface_technics_id', '=', p.id),
|
||||
('id', 'in', route_workcenter_arr)])
|
||||
if route_production_process:
|
||||
[('server_product_process_parameters_id', '=', route.process_parameters_id.id)])
|
||||
workorders_values.append(
|
||||
self.env[
|
||||
'mrp.workorder']._json_workorder_surface_process_str(
|
||||
production, route_production_process,
|
||||
process_parameter,
|
||||
product_production_process.seller_ids[0].partner_id.id))
|
||||
elif production.product_id.categ_id.type == '坯料':
|
||||
embryo_routing_workcenter = self.env['sf.embryo.model.type.routing.sort'].search(
|
||||
[('embryo_model_type_id', '=', production.product_id.embryo_model_type_id.id)],
|
||||
order='sequence asc'
|
||||
)
|
||||
for route in embryo_routing_workcenter:
|
||||
workorders_values.append(
|
||||
self.env['mrp.workorder'].json_workorder_str('', production, route))
|
||||
production, route, product_production_process.seller_ids[0].partner_id.id))
|
||||
production.workorder_ids = workorders_values
|
||||
# for production_item in productions:
|
||||
process_parameter_workorder = self.env['mrp.workorder'].search(
|
||||
[('surface_technics_parameters_id', '!=', False), ('production_id', '=', production.id),
|
||||
('is_subcontract', '=', True)])
|
||||
if process_parameter_workorder:
|
||||
is_pick = False
|
||||
consecutive_workorders = []
|
||||
m = 0
|
||||
sorted_workorders = sorted(process_parameter_workorder, key=lambda w: w.id)
|
||||
for i in range(len(sorted_workorders) - 1):
|
||||
if m == 0:
|
||||
is_pick = False
|
||||
if sorted_workorders[i].supplier_id.id == sorted_workorders[i + 1].supplier_id.id and \
|
||||
sorted_workorders[i].is_subcontract == sorted_workorders[i + 1].is_subcontract and \
|
||||
sorted_workorders[i].id == sorted_workorders[i + 1].id - 1:
|
||||
if sorted_workorders[i] not in consecutive_workorders:
|
||||
consecutive_workorders.append(sorted_workorders[i])
|
||||
consecutive_workorders.append(sorted_workorders[i + 1])
|
||||
m += 1
|
||||
continue
|
||||
else:
|
||||
if m == len(consecutive_workorders) - 1 and m != 0:
|
||||
self.env['stock.picking'].create_outcontract_picking(consecutive_workorders,
|
||||
production)
|
||||
if sorted_workorders[i] in consecutive_workorders:
|
||||
is_pick = True
|
||||
consecutive_workorders = []
|
||||
m = 0
|
||||
# 当前面的连续工序生成对应的外协出入库单再生成当前工序的外协出入库单
|
||||
if is_pick is False:
|
||||
self.env['stock.picking'].create_outcontract_picking(sorted_workorders[i],
|
||||
production)
|
||||
if m == len(consecutive_workorders) - 1 and m != 0:
|
||||
self.env['stock.picking'].create_outcontract_picking(consecutive_workorders,
|
||||
production)
|
||||
if sorted_workorders[i] in consecutive_workorders:
|
||||
is_pick = True
|
||||
consecutive_workorders = []
|
||||
m = 0
|
||||
if m == len(consecutive_workorders) - 1 and m != 0:
|
||||
self.env['stock.picking'].create_outcontract_picking(consecutive_workorders, production)
|
||||
if is_pick is False and m == 0:
|
||||
if len(sorted_workorders) == 1:
|
||||
self.env['stock.picking'].create_outcontract_picking(sorted_workorders, production)
|
||||
else:
|
||||
self.env['stock.picking'].create_outcontract_picking(sorted_workorders[i], production)
|
||||
for workorder in production.workorder_ids:
|
||||
workorder.duration_expected = workorder._get_duration_expected()
|
||||
|
||||
# 外协出入库单处理
|
||||
def get_subcontract_pick_purchase(self):
|
||||
production_all = self.sorted(lambda x: x.id)
|
||||
product_id_to_production_names = {}
|
||||
grouped_product_ids = {k: list(g) for k, g in
|
||||
groupby(production_all, key=lambda x: x.product_id.id)}
|
||||
for product_id, pd in grouped_product_ids.items():
|
||||
product_id_to_production_names[product_id] = [p.name for p in pd]
|
||||
for production in production_all:
|
||||
process_parameter_workorder = self.env['mrp.workorder'].search(
|
||||
[('surface_technics_parameters_id', '!=', False), ('production_id', '=', production.id),
|
||||
('is_subcontract', '=', True)], order='sequence asc')
|
||||
if process_parameter_workorder:
|
||||
consecutive_workorders = []
|
||||
sorted_workorders = sorted(process_parameter_workorder, key=lambda w: w.sequence)
|
||||
for i, workorder in enumerate(sorted_workorders):
|
||||
# 检查当前工作订单和下一个工作订单是否连续,并且供应商相同
|
||||
if i == 0:
|
||||
consecutive_workorders.append(workorder)
|
||||
elif workorder.sequence == sorted_workorders[
|
||||
i - 1].sequence + 1 and workorder.supplier_id.id == sorted_workorders[i - 1].supplier_id.id:
|
||||
consecutive_workorders.append(workorder)
|
||||
else:
|
||||
# 处理连续组,如果它不为空
|
||||
if consecutive_workorders:
|
||||
# 创建外协出入库单和采购订单
|
||||
self.env['stock.picking'].create_outcontract_picking(consecutive_workorders, production)
|
||||
self.env['purchase.order'].get_purchase_order(consecutive_workorders, production,
|
||||
product_id_to_production_names)
|
||||
if i < len(sorted_workorders) - 1:
|
||||
# 重置连续组,并添加当前工作订单
|
||||
consecutive_workorders = [workorder]
|
||||
else:
|
||||
# 判断最后一笔:
|
||||
if workorder.sequence == sorted_workorders[
|
||||
i - 1].sequence and workorder.supplier_id.id == sorted_workorders[
|
||||
i - 1].supplier_id.id:
|
||||
consecutive_workorders = [workorder]
|
||||
else:
|
||||
# 立即创建外协出入库单和采购订单
|
||||
self.env['stock.picking'].create_outcontract_picking(workorder, production)
|
||||
self.env['purchase.order'].get_purchase_order(workorder, production,
|
||||
product_id_to_production_names)
|
||||
consecutive_workorders = []
|
||||
|
||||
# 处理最后一个组,即使它可能只有一个工作订单
|
||||
if consecutive_workorders:
|
||||
self.env['stock.picking'].create_outcontract_picking(consecutive_workorders, production)
|
||||
self.env['purchase.order'].get_purchase_order(consecutive_workorders, production,
|
||||
product_id_to_production_names)
|
||||
|
||||
# 工单排序
|
||||
def _reset_work_order_sequence1(self, k):
|
||||
for rec in self:
|
||||
@@ -641,7 +829,84 @@ class MrpProduction(models.Model):
|
||||
self._reset_work_order_sequence1(k)
|
||||
return True
|
||||
|
||||
# 需对不连续工单对应的采购单和外协出入库单做处理
|
||||
def _reset_subcontract_pick_purchase(self):
|
||||
production_all = self.sorted(lambda x: x.id)
|
||||
product_id_to_production_names = {}
|
||||
grouped_product_ids = {k: list(g) for k, g in
|
||||
groupby(production_all, key=lambda x: x.product_id.id)}
|
||||
for product_id, pd in grouped_product_ids.items():
|
||||
product_id_to_production_names[product_id] = [p.name for p in pd]
|
||||
for item in production_all:
|
||||
production_process = product_id_to_production_names.get(item.product_id.id)
|
||||
workorder_sf = item.workorder_ids.filtered(lambda sf: sf.routing_type == '表面工艺')
|
||||
for i, workorder in enumerate(workorder_sf):
|
||||
if i == 0:
|
||||
continue
|
||||
elif workorder.sequence != workorder_sf[i - 1].sequence + 1:
|
||||
# workorder.picking_ids.move_ids = False
|
||||
workorder.picking_ids = False
|
||||
purchase_order = self.env['purchase.order'].search(
|
||||
[('state', '=', 'draft'), ('origin', '=', item.name),
|
||||
('purchase_type', '=', 'consignment')])
|
||||
server_template = self.env['product.template'].search(
|
||||
[('server_product_process_parameters_id', '=',
|
||||
workorder.surface_technics_parameters_id.id),
|
||||
('detailed_type', '=', 'service')])
|
||||
for po in purchase_order:
|
||||
for line in po.order_line:
|
||||
if line.product_id == server_template.product_variant_id:
|
||||
continue
|
||||
if server_template.server_product_process_parameters_id != line.product_id.server_product_process_parameters_id:
|
||||
purchase_order_line = self.env['purchase.order.line'].search(
|
||||
[('product_id', '=', server_template.product_variant_id.id), ('id', '=', line.id),
|
||||
('product_qty', '=', 1)], limit=1, order='id desc')
|
||||
if purchase_order_line:
|
||||
line.unlink()
|
||||
|
||||
def _reset_work_order_sequence(self):
|
||||
"""
|
||||
工单工序排序方法(新)
|
||||
"""
|
||||
for rec in self:
|
||||
workorder_ids = rec.workorder_ids
|
||||
technology_design_ids = rec.technology_design_ids
|
||||
if workorder_ids.filtered(lambda item: item.state in ('返工', 'rework')):
|
||||
# 获取返工后新生成的工单
|
||||
work_ids = workorder_ids.filtered(lambda item: item.sequence == 0)
|
||||
# 对工单进行逐个插入
|
||||
for work_id in work_ids:
|
||||
for order_id in rec.workorder_ids.filtered(lambda item: item.sequence > 0):
|
||||
if work_id.name == order_id.name:
|
||||
work_id.sequence = order_id.sequence + 1
|
||||
break
|
||||
# 对该工单之后的工单工序进行加一
|
||||
work_order_ids = rec.workorder_ids.filtered(
|
||||
lambda item: item.sequence >= work_id.sequence and item.id != work_id.id)
|
||||
for work in work_order_ids:
|
||||
work.sequence = work.sequence + 1
|
||||
|
||||
else:
|
||||
# 将工艺设计生成的工单序号赋值给工单的序号
|
||||
for work in workorder_ids:
|
||||
td_ids = technology_design_ids.filtered(
|
||||
lambda item: (item.route_id.name in work.name and item.process_parameters_id
|
||||
and item.process_parameters_id == work.surface_technics_parameters_id) or
|
||||
(item.route_id.name == work.name and item.panel
|
||||
and item.panel == work.processing_panel))
|
||||
if td_ids:
|
||||
work.sequence = td_ids[0].sequence
|
||||
cancel_work_ids = workorder_ids.filtered(lambda item: item.state in ('已取消', 'cancel'))
|
||||
if cancel_work_ids:
|
||||
sequence = max(workorder_ids.filtered(lambda item: item.state not in ('已取消', 'cancel')),
|
||||
key=lambda w: w.sequence).sequence
|
||||
for cw in cancel_work_ids:
|
||||
cw.sequence = sequence + 1
|
||||
|
||||
def _reset_work_order_sequence_1(self):
|
||||
"""
|
||||
工单工序排序方法(旧)
|
||||
"""
|
||||
for rec in self:
|
||||
workorder_ids = rec.workorder_ids.filtered(lambda item: item.state in ('返工', 'rework'))
|
||||
# 产品模型类型
|
||||
@@ -737,52 +1002,32 @@ class MrpProduction(models.Model):
|
||||
self._reset_work_order_sequence()
|
||||
return True
|
||||
|
||||
def production_process(self, pro_plan):
|
||||
type_map = {'装夹预调': False, 'CNC加工': False, '解除装夹': False}
|
||||
# 最后一次加工结束时间
|
||||
last_time = pro_plan.date_planned_start
|
||||
# 预置时间
|
||||
works = self.workorder_ids
|
||||
for index, work in enumerate(works):
|
||||
count = type_map.get(work.routing_type)
|
||||
date_planned_end = None
|
||||
date_planned_start = None
|
||||
if self.production_type == '自动化产线加工':
|
||||
date_planned_start, date_planned_end, last_time = work.auto_production_process(last_time, count,
|
||||
type_map)
|
||||
elif self.production_type == '':
|
||||
date_planned_start, date_planned_end, last_time = work.manual_offline_process(last_time, index)
|
||||
work.update_work_start_end(date_planned_start, date_planned_end)
|
||||
|
||||
# def
|
||||
def process_range_time(self):
|
||||
for production in self:
|
||||
works = production.workorder_ids
|
||||
pro_plan = self.env['sf.production.plan'].search([('production_id', '=', production.id)], limit=1)
|
||||
if not pro_plan:
|
||||
continue
|
||||
type_map = {'装夹预调': False, 'CNC加工': False, '解除装夹': False}
|
||||
# 最后一次加工结束时间
|
||||
last_time = pro_plan.date_planned_start
|
||||
# 预置时间
|
||||
for work in works:
|
||||
count = type_map.get(work.routing_type)
|
||||
date_planned_end = None
|
||||
date_planned_start = None
|
||||
duration_expected = datetime.timedelta(minutes=work.duration_expected)
|
||||
reserve_time = datetime.timedelta(minutes=work.reserved_duration)
|
||||
if not count:
|
||||
# 第一轮加工
|
||||
if work.routing_type == '装夹预调':
|
||||
date_planned_end = last_time - reserve_time
|
||||
date_planned_start = date_planned_end - duration_expected
|
||||
elif work.routing_type == 'CNC加工':
|
||||
date_planned_start = last_time
|
||||
date_planned_end = last_time + duration_expected
|
||||
last_time = date_planned_end
|
||||
else:
|
||||
date_planned_start = last_time + reserve_time
|
||||
date_planned_end = date_planned_start + duration_expected
|
||||
last_time = date_planned_end
|
||||
type_map.update({work.routing_type: True})
|
||||
else:
|
||||
date_planned_start = last_time + reserve_time
|
||||
date_planned_end = date_planned_start + duration_expected
|
||||
last_time = date_planned_end
|
||||
work.leave_id.write({
|
||||
'date_from': date_planned_start,
|
||||
'date_to': date_planned_end,
|
||||
})
|
||||
# work.write({'date_planned_start': date_planned_start, 'date_planned_finished': date_planned_end})
|
||||
work.date_planned_start = date_planned_start
|
||||
work.date_planned_finished = date_planned_end
|
||||
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})
|
||||
if production.production_type:
|
||||
production.production_process(pro_plan)
|
||||
|
||||
# 修改标记已完成方法
|
||||
def button_mark_done1(self):
|
||||
@@ -806,6 +1051,7 @@ 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:
|
||||
@@ -906,6 +1152,14 @@ class MrpProduction(models.Model):
|
||||
cloud_programming = None
|
||||
if self.programming_state in ['已编程']:
|
||||
cloud_programming = self._cron_get_programming_state()
|
||||
result_ids = self.detection_result_ids.filtered(lambda dr: dr.handle_result == '待处理')
|
||||
work_id_list = []
|
||||
if result_ids:
|
||||
work_id_list = [self.workorder_ids.filtered(
|
||||
lambda wk: (wk.name == result_id.routing_type and wk.processing_panel == result_id.processing_panel
|
||||
and wk.state == 'done')).id
|
||||
for result_id in result_ids]
|
||||
|
||||
return {
|
||||
'name': _('返工'),
|
||||
'type': 'ir.actions.act_window',
|
||||
@@ -914,6 +1168,8 @@ class MrpProduction(models.Model):
|
||||
'target': 'new',
|
||||
'context': {
|
||||
'default_production_id': self.id,
|
||||
'default_workorder_ids': self.workorder_ids.ids,
|
||||
'default_hidden_workorder_ids': ','.join(map(str, work_id_list)) if work_id_list != [] else '',
|
||||
'default_reprogramming_num': cloud_programming['reprogramming_num'],
|
||||
'default_programming_state': cloud_programming['programming_state'],
|
||||
'default_is_reprogramming': True if cloud_programming['programming_state'] in ['已下发'] else False
|
||||
@@ -943,6 +1199,8 @@ class MrpProduction(models.Model):
|
||||
if production.programming_no in program_to_production_names:
|
||||
productions_not_delivered = self.env['mrp.production'].search(
|
||||
[('programming_no', '=', production.programming_no), ('programming_state', '=', '已编程未下发')])
|
||||
productions = self.env['mrp.production'].search(
|
||||
[('programming_no', '=', production.programming_no), ('state', 'not in', ('cancel', 'done'))])
|
||||
rework_workorder = production.workorder_ids.filtered(lambda m: m.state == 'rework')
|
||||
if rework_workorder:
|
||||
for rework_item in rework_workorder:
|
||||
@@ -955,6 +1213,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:
|
||||
@@ -1058,13 +1323,15 @@ class MrpProduction(models.Model):
|
||||
raise_user_error=not self.env.context.get('from_orderpoint'))
|
||||
productions = self.env['mrp.production'].sudo().search(
|
||||
[('origin', '=', self.origin)], order='id desc', limit=1)
|
||||
productions.write({'programming_no': self.programming_no, 'is_remanufacture': True})
|
||||
move = self.env['stock.move'].search([('origin', '=', productions.name)], order='id desc')
|
||||
for mo in move:
|
||||
if mo.procure_method == 'make_to_order' and mo.name != productions.name:
|
||||
if mo.name == '/':
|
||||
domain = []
|
||||
if mo.location_id.barcode == 'WH-POSTPRODUCTION' and mo.rule_id.picking_type_id.barcode == 'PC':
|
||||
domain = [('barcode', '=', 'WH-PC'), ('sequence_code', '=', 'PC')]
|
||||
elif mo.name == '拉':
|
||||
elif mo.location_id.barcode == 'PL' and mo.rule_id.picking_type_id.barcode == 'INT':
|
||||
domain = [('barcode', '=', 'WH-INTERNAL'), ('sequence_code', '=', 'INT')]
|
||||
if domain:
|
||||
picking_type = self.env['stock.picking.type'].search(domain)
|
||||
mo.write({'picking_type_id': picking_type.id})
|
||||
mo._assign_picking()
|
||||
@@ -1082,7 +1349,6 @@ class MrpProduction(models.Model):
|
||||
mo_move.write({'reference': sfp_move.reference, 'partner_id': sfp_move.partner_id.id,
|
||||
'picking_id': sfp_move.picking_id.id, 'picking_type_id': sfp_move.picking_type_id.id,
|
||||
'production_id': False})
|
||||
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_remanufacture = self.env['stock.picking'].search([('origin', '=', productions.name)])
|
||||
@@ -1116,7 +1382,6 @@ class MrpProduction(models.Model):
|
||||
if purchase_orders.origin.find(productions.name) == -1:
|
||||
purchase_orders.origin += ',' + productions.name
|
||||
if item['is_reprogramming'] is False:
|
||||
productions._create_workorder(item)
|
||||
productions.programming_state = '已编程'
|
||||
else:
|
||||
productions.programming_state = '编程中'
|
||||
@@ -1146,6 +1411,103 @@ class MrpProduction(models.Model):
|
||||
'user_id': production.user_id.id}
|
||||
return production_values_str
|
||||
|
||||
# 增加制造订单类型
|
||||
production_type = fields.Selection(
|
||||
[('自动化产线加工', '自动化产线加工'), ('人工线下加工', '人工线下加工')],
|
||||
string='制造类型',
|
||||
compute='_compute_production_type',
|
||||
store=True
|
||||
)
|
||||
|
||||
@api.depends('product_id.is_manual_processing')
|
||||
def _compute_production_type(self):
|
||||
for production in self:
|
||||
production.production_type = '自动化产线加工' if not production.product_id.is_manual_processing else '人工线下加工'
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
"""
|
||||
重载创建制造订单的方法,单个制造订单,同一成品只创建一个采购组,用于后续单据的创建
|
||||
"""
|
||||
product_group_id = {}
|
||||
for vals in vals_list:
|
||||
if not vals.get('name', False) or vals['name'] == _('New'):
|
||||
picking_type_id = vals.get('picking_type_id')
|
||||
if not picking_type_id:
|
||||
picking_type_id = self._get_default_picking_type_id(vals.get('company_id', self.env.company.id))
|
||||
vals['picking_type_id'] = picking_type_id
|
||||
vals['name'] = self.env['stock.picking.type'].browse(picking_type_id).sequence_id.next_by_id()
|
||||
if not vals.get('procurement_group_id'):
|
||||
product_id = self.env['product.product'].browse(vals['product_id'])
|
||||
if product_id.product_tmpl_id.single_manufacturing:
|
||||
if product_id.id not in product_group_id.keys():
|
||||
procurement_group_vals = self._prepare_procurement_group_vals(vals)
|
||||
group_id = self.env["procurement.group"].create(procurement_group_vals).id
|
||||
vals['procurement_group_id'] = group_id
|
||||
product_group_id[product_id.id] = group_id
|
||||
else:
|
||||
vals['procurement_group_id'] = product_group_id[product_id.id]
|
||||
return super(MrpProduction, self).create(vals_list)
|
||||
|
||||
@api.depends('procurement_group_id.stock_move_ids.created_purchase_line_id.order_id',
|
||||
'procurement_group_id.stock_move_ids.move_orig_ids.purchase_line_id.order_id')
|
||||
def _compute_purchase_order_count(self):
|
||||
for production in self:
|
||||
# 找到来源的第一张制造订单的采购组
|
||||
if production.product_id.product_tmpl_id.single_manufacturing == True:
|
||||
first_production = self.env['mrp.production'].search(
|
||||
[('origin', '=', production.origin), ('product_id', '=', production.product_id.id)], limit=1,
|
||||
order='id asc')
|
||||
production.purchase_order_count = len(
|
||||
first_production.procurement_group_id.stock_move_ids.created_purchase_line_id.order_id |
|
||||
first_production.procurement_group_id.stock_move_ids.move_orig_ids.purchase_line_id.order_id)
|
||||
else:
|
||||
production.purchase_order_count = len(
|
||||
production.procurement_group_id.stock_move_ids.created_purchase_line_id.order_id |
|
||||
production.procurement_group_id.stock_move_ids.move_orig_ids.purchase_line_id.order_id)
|
||||
|
||||
@api.depends('procurement_group_id', 'procurement_group_id.stock_move_ids.group_id')
|
||||
def _compute_picking_ids(self):
|
||||
for order in self:
|
||||
if order.product_id.product_tmpl_id.single_manufacturing == True:
|
||||
first_order = self.env['mrp.production'].search(
|
||||
[('origin', '=', order.origin), ('product_id', '=', order.product_id.id)], limit=1, order='id asc')
|
||||
order.picking_ids = self.env['stock.picking'].search([
|
||||
('group_id', '=', first_order.procurement_group_id.id), ('group_id', '!=', False),
|
||||
])
|
||||
order.delivery_count = len(first_order.picking_ids)
|
||||
else:
|
||||
order.picking_ids = self.env['stock.picking'].search([
|
||||
('group_id', '=', order.procurement_group_id.id), ('group_id', '!=', False),
|
||||
])
|
||||
order.delivery_count = len(order.picking_ids)
|
||||
|
||||
def action_view_purchase_orders(self):
|
||||
self.ensure_one()
|
||||
if self.product_id.product_tmpl_id.single_manufacturing == True:
|
||||
production = self.env['mrp.production'].search(
|
||||
[('origin', '=', self.origin), ('product_id', '=', self.product_id.id)], limit=1, order='id asc')
|
||||
else:
|
||||
production = self
|
||||
purchase_order_ids = (
|
||||
production.procurement_group_id.stock_move_ids.created_purchase_line_id.order_id | production.procurement_group_id.stock_move_ids.move_orig_ids.purchase_line_id.order_id).ids
|
||||
action = {
|
||||
'res_model': 'purchase.order',
|
||||
'type': 'ir.actions.act_window',
|
||||
}
|
||||
if len(purchase_order_ids) == 1:
|
||||
action.update({
|
||||
'view_mode': 'form',
|
||||
'res_id': purchase_order_ids[0],
|
||||
})
|
||||
else:
|
||||
action.update({
|
||||
'name': _("Purchase Order generated from %s", self.name),
|
||||
'domain': [('id', 'in', purchase_order_ids)],
|
||||
'view_mode': 'tree,form',
|
||||
})
|
||||
return action
|
||||
|
||||
|
||||
class sf_detection_result(models.Model):
|
||||
_name = 'sf.detection.result'
|
||||
@@ -1175,10 +1537,6 @@ class sf_detection_result(models.Model):
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_id': self.id,
|
||||
'views': [(self.env.ref('sf_manufacturing.sf_test_report_form').id, 'form')],
|
||||
# 'view_mode': 'form',
|
||||
# 'context': {
|
||||
# 'default_id': self.id
|
||||
# },
|
||||
'target': 'new'
|
||||
}
|
||||
|
||||
|
||||
@@ -7,21 +7,25 @@ class ResMrpRoutingWorkcenter(models.Model):
|
||||
_inherit = 'mrp.routing.workcenter'
|
||||
|
||||
routing_type = fields.Selection([
|
||||
# ('获取CNC加工程序', '获取CNC加工程序'),
|
||||
('装夹预调', '装夹预调'),
|
||||
# ('前置三元定位检测', '前置三元定位检测'),
|
||||
('CNC加工', 'CNC加工'),
|
||||
# ('后置三元质量检测', '后置三元质量检测'),
|
||||
('解除装夹', '解除装夹'),
|
||||
('切割', '切割'),
|
||||
('表面工艺', '表面工艺')
|
||||
('表面工艺', '表面工艺'),
|
||||
('线切割', '线切割'),
|
||||
('人工线下加工', '人工线下加工')
|
||||
], string="工序类型")
|
||||
routing_tag = fields.Selection([
|
||||
('standard', '标准'),
|
||||
('special', '特殊')
|
||||
], string="标签")
|
||||
is_repeat = fields.Boolean('重复', default=False)
|
||||
workcenter_id = fields.Many2one('mrp.workcenter', required=False)
|
||||
workcenter_ids = fields.Many2many('mrp.workcenter', 'rel_workcenter_route', required=True)
|
||||
bom_id = fields.Many2one('mrp.bom', required=False)
|
||||
surface_technics_id = fields.Many2one('sf.production.process', string="表面工艺")
|
||||
reserved_duration = fields.Float('预留时长', default=30, tracking=True)
|
||||
|
||||
def get_no(self):
|
||||
international_standards = self.search(
|
||||
[('code', '!=', ''), ('active', 'in', [True, False])],
|
||||
@@ -40,7 +44,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)
|
||||
|
||||
# 排产的时候, 根据坯料的长宽高比对一下机床的最大加工尺寸.不符合就不要分配给这个加工中心(机床).
|
||||
# 工单对应的工作中心,根据工序中的工作中心去匹配,
|
||||
@@ -78,3 +82,16 @@ class ResMrpRoutingWorkcenter(models.Model):
|
||||
else:
|
||||
workcenter_id = workcenter_ids[0]
|
||||
return workcenter_id
|
||||
|
||||
@api.model
|
||||
def _name_search(self, name, args=None, operator='ilike', limit=100, name_get_uid=None):
|
||||
if self._context.get('production_id'):
|
||||
route_ids = []
|
||||
technology_design = self.env['sf.technology.design'].search(
|
||||
[('production_id', '=', self._context.get('production_id'))])
|
||||
for t in technology_design.filtered(lambda a: a.routing_tag == 'special'):
|
||||
if not t.process_parameters_id:
|
||||
route_ids.append(t.route_id.surface_technics_id.id)
|
||||
domain = [('id', 'not in', route_ids), ('routing_tag', '=', 'special')]
|
||||
return self._search(domain, limit=limit, access_rights_uid=name_get_uid)
|
||||
return super()._name_search(name, args, operator, limit, name_get_uid)
|
||||
|
||||
@@ -237,7 +237,7 @@ class ResWorkcenter(models.Model):
|
||||
return True
|
||||
|
||||
# 处理排程是否超过小时产能
|
||||
def deal_available_single_machine_capacity(self, date_planned):
|
||||
def deal_available_single_machine_capacity(self, date_planned, count):
|
||||
|
||||
date_planned_start = date_planned.strftime('%Y-%m-%d %H:00:00')
|
||||
date_planned_end = date_planned + timedelta(hours=1)
|
||||
@@ -249,7 +249,11 @@ class ResWorkcenter(models.Model):
|
||||
|
||||
if plan_ids:
|
||||
sum_qty = sum([p.product_qty for p in plan_ids])
|
||||
if sum_qty >= self.production_line_hour_capacity:
|
||||
production_line_hour_capacity = self.production_line_hour_capacity
|
||||
if sum_qty >= production_line_hour_capacity:
|
||||
message = '当前计划开始时间不能预约排程,超过生产线小时产能(%d件)%d件' % (
|
||||
production_line_hour_capacity, count)
|
||||
raise UserError(message)
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
@@ -38,13 +38,10 @@ class ResMrpWorkOrder(models.Model):
|
||||
processing_panel = fields.Char('加工面')
|
||||
sequence = fields.Integer(string='工序')
|
||||
routing_type = fields.Selection([
|
||||
# ('获取CNC加工程序', '获取CNC加工程序'),
|
||||
('装夹预调', '装夹预调'),
|
||||
# ('前置三元定位检测', '前置三元定位检测'),
|
||||
('CNC加工', 'CNC加工'),
|
||||
# ('后置三元质量检测', '后置三元质量检测'),
|
||||
('解除装夹', '解除装夹'),
|
||||
('切割', '切割'), ('表面工艺', '表面工艺')
|
||||
('切割', '切割'), ('表面工艺', '表面工艺'), ('线切割', '线切割'), ('人工线下加工', '人工线下加工')
|
||||
], string="工序类型")
|
||||
results = fields.Char('结果')
|
||||
state = fields.Selection([
|
||||
@@ -59,7 +56,8 @@ class ResMrpWorkOrder(models.Model):
|
||||
compute='_compute_state', store=True,
|
||||
default='pending', copy=False, readonly=True, recursive=True, index=True, tracking=True)
|
||||
|
||||
# state = fields.Selection(selection_add=[('to be detected', "待检测"), ('rework', '返工')], tracking=True)
|
||||
delivery_warning = fields.Selection([('normal', '正常'), ('warning', '告警'), ('overdue', '逾期')], string='时效',
|
||||
tracking=True)
|
||||
|
||||
@api.depends('production_id.manual_quotation')
|
||||
def _compute_manual_quotation(self):
|
||||
@@ -128,7 +126,7 @@ class ResMrpWorkOrder(models.Model):
|
||||
Y10_axis = fields.Float(default=0)
|
||||
Z10_axis = fields.Float(default=0)
|
||||
X_deviation_angle = fields.Integer(string="X轴偏差度", default=0)
|
||||
test_results = fields.Selection([("合格", "合格"), ("返工", "返工"), ("报废", "报废")], default='合格',
|
||||
test_results = fields.Selection([("合格", "合格")], default='合格',
|
||||
string="检测结果", tracking=True)
|
||||
cnc_ids = fields.One2many("sf.cnc.processing", 'workorder_id', string="CNC加工程序")
|
||||
cmm_ids = fields.One2many("sf.cmm.program", 'workorder_id', string="CMM程序")
|
||||
@@ -145,19 +143,107 @@ class ResMrpWorkOrder(models.Model):
|
||||
is_trayed = fields.Boolean(string='是否绑定托盘', default=False)
|
||||
|
||||
tag_type = fields.Selection([("重新加工", "重新加工")], string="标签", tracking=True)
|
||||
technology_design_id = fields.Many2one('sf.technology.design')
|
||||
|
||||
def _compute_default_construction_period_status(self):
|
||||
need_list = ['pending', 'waiting', 'ready', 'progress', 'to be detected', 'done']
|
||||
try:
|
||||
if self.state not in need_list:
|
||||
return False
|
||||
if not self.date_planned_finished:
|
||||
return False
|
||||
hours = self.get_hours_diff()
|
||||
if hours >= 12:
|
||||
return '正常'
|
||||
elif hours > 0 and hours < 12 and self.state != 'done':
|
||||
return '预警'
|
||||
elif hours > 0 and hours < 12 and self.state == 'done':
|
||||
return '正常'
|
||||
else:
|
||||
return '已逾期'
|
||||
except Exception as e:
|
||||
logging.error("Error processing production ID {}: {}".format(self.id, e))
|
||||
raise e
|
||||
|
||||
@api.depends('state', 'date_planned_finished')
|
||||
def _compute_construction_period_status(self):
|
||||
for worker in self:
|
||||
construction_period_status = worker._compute_default_construction_period_status()
|
||||
if construction_period_status and worker.construction_period_status != construction_period_status:
|
||||
worker.construction_period_status = construction_period_status
|
||||
|
||||
construction_period_status = fields.Selection([('正常', '正常'), ('预警', '预警'), ('已逾期', '已逾期')],
|
||||
string='工期状态',
|
||||
store=True,
|
||||
compute='_compute_construction_period_status',
|
||||
default=lambda
|
||||
self: self._compute_default_construction_period_status())
|
||||
|
||||
def get_page_all_records(self, model_name, func, domain, page_size=100):
|
||||
# 获取模型对象
|
||||
model = self.env[model_name].sudo()
|
||||
|
||||
# 初始化分页参数
|
||||
page_number = 1
|
||||
while True:
|
||||
# 计算偏移量
|
||||
offset = (page_number - 1) * page_size
|
||||
|
||||
# 获取当前页的数据
|
||||
records = model.search(domain, limit=page_size, offset=offset)
|
||||
|
||||
# 如果没有更多记录,退出循环
|
||||
if not records:
|
||||
break
|
||||
|
||||
# 将当前页的数据添加到结果列表
|
||||
func(records)
|
||||
# 增加页码
|
||||
page_number += 1
|
||||
|
||||
def run_compute_construction_period_status(self, records):
|
||||
records._compute_construction_period_status()
|
||||
|
||||
def _corn_update_construction_period_status(self):
|
||||
need_list = ['pending', 'waiting', 'ready', 'progress', 'to be detected']
|
||||
# need_list = [
|
||||
# 'progress',
|
||||
# 'to be detected']
|
||||
self.get_page_all_records('mrp.workorder', self.run_compute_construction_period_status,
|
||||
[('state', 'in', need_list)], 100)
|
||||
|
||||
def get_hours_diff(self):
|
||||
# 获取当前日期和时间
|
||||
current_datetime = fields.Datetime.now()
|
||||
|
||||
# 将 date_field 转换为 datetime 对象
|
||||
if self.date_planned_finished:
|
||||
date_obj = fields.Datetime.from_string(self.date_planned_finished)
|
||||
# 将 date 对象转换为 datetime 对象,设置时间为 00:00:00
|
||||
# date_obj = datetime.datetime.combine(date_obj, datetime.time.min)
|
||||
|
||||
# 计算两个日期之间的差值
|
||||
delta = date_obj - current_datetime
|
||||
|
||||
# 返回差值的小时数
|
||||
return int(delta.total_seconds() / 3600)
|
||||
else:
|
||||
return 0.0
|
||||
|
||||
@api.depends('name', 'production_id.name')
|
||||
def _compute_surface_technics_picking_ids(self):
|
||||
for workorder in self:
|
||||
if workorder.routing_type == '表面工艺':
|
||||
domain = [('origin', '=', workorder.production_id.name)]
|
||||
domain = [('origin', '=', workorder.production_id.name), ('state', 'not in', ['cancel']),
|
||||
('partner_id', '=', workorder.supplier_id.id)]
|
||||
previous_workorder = self.env['mrp.workorder'].search(
|
||||
[('sequence', '=', workorder.sequence - 1), ('routing_type', '=', '表面工艺'),
|
||||
('production_id', '=', workorder.production_id.id)])
|
||||
if previous_workorder:
|
||||
process_product = self.env['product.template']._get_process_parameters_product(
|
||||
previous_workorder.surface_technics_parameters_id)
|
||||
domain += [('partner_id', '=', process_product.partner_id.id)]
|
||||
if previous_workorder.supplier_id != workorder.supplier_id:
|
||||
# process_product = self.env['product.template']._get_process_parameters_product(
|
||||
# previous_workorder.surface_technics_parameters_id)
|
||||
domain += [('surface_technics_parameters_id', '=', workorder.surface_technics_parameters_id.id)]
|
||||
else:
|
||||
domain += [('surface_technics_parameters_id', '=', workorder.surface_technics_parameters_id.id)]
|
||||
picking_ids = self.env['stock.picking'].search(domain, order='id asc')
|
||||
@@ -183,29 +269,64 @@ class ResMrpWorkOrder(models.Model):
|
||||
@api.depends('state', 'production_id.name')
|
||||
def _compute_surface_technics_purchase_ids(self):
|
||||
for order in self:
|
||||
if order.routing_type == '表面工艺':
|
||||
production_programming = self.env['mrp.production'].search(
|
||||
[('programming_no', '=', order.production_id.programming_no)], order='name asc')
|
||||
production_no_remanufacture = production_programming.filtered(lambda a: a.is_remanufacture is False)
|
||||
production_list = [production.name for production in production_programming]
|
||||
purchase = self.env['purchase.order'].search([('origin', '=', ','.join(production_list))])
|
||||
for line in purchase.order_line:
|
||||
if line.product_id.server_product_process_parameters_id == order.surface_technics_parameters_id and line.product_qty == len(
|
||||
production_no_remanufacture):
|
||||
order.surface_technics_purchase_count = len(purchase)
|
||||
if order.routing_type == '表面工艺' and order.state not in ['cancel']:
|
||||
# if order.production_id.production_type == '自动化产线加工':
|
||||
# domain = [('programming_no', '=', order.production_id.programming_no)]
|
||||
# else:buzhdiao
|
||||
# domain = [('origin', '=', order.production_id.origin)]
|
||||
# production_programming = self.env['mrp.production'].search(domain, order='name asc')
|
||||
# production_list = [production.name for production in production_programming]
|
||||
# production_no_remanufacture = production_programming.filtered(lambda a: a.is_remanufacture is False)
|
||||
# technology_design = self.env['sf.technology.design'].search(
|
||||
# [('process_parameters_id', '=', order.surface_technics_parameters_id.id),
|
||||
# ('production_id', '=', order.production_id.id)])
|
||||
# if technology_design.is_auto is False:
|
||||
# domain = [('origin', '=', order.production_id.name)]
|
||||
# else:
|
||||
domain = [('purchase_type', '=', 'consignment'), ('origin', '=', order.production_id.name),
|
||||
('state', '!=', 'cancel')]
|
||||
purchase = self.env['purchase.order'].search(domain)
|
||||
purchase_num = 0
|
||||
if not purchase:
|
||||
order.surface_technics_purchase_count = 0
|
||||
for po in purchase:
|
||||
for line in po.order_line:
|
||||
if line.product_id.server_product_process_parameters_id == order.surface_technics_parameters_id:
|
||||
if line.product_qty == 1:
|
||||
purchase_num += 1
|
||||
order.surface_technics_purchase_count = purchase_num
|
||||
else:
|
||||
order.surface_technics_purchase_count = 0
|
||||
|
||||
def action_view_surface_technics_purchase(self):
|
||||
self.ensure_one()
|
||||
production_programming = self.env['mrp.production'].search(
|
||||
[('programming_no', '=', self.production_id.programming_no)], order='name asc')
|
||||
production_list = [production.name for production in production_programming]
|
||||
purchase_orders = self.env['purchase.order'].search([('origin', '=', ','.join(production_list))])
|
||||
# if self.routing_type == '表面工艺':
|
||||
# if self.production_id.production_type == '自动化产线加工':
|
||||
# domain = [('programming_no', '=', self.production_id.programming_no)]
|
||||
# else:
|
||||
# domain = [('origin', '=', self.production_id.origin)]
|
||||
# production_programming = self.env['mrp.production'].search(domain, order='name asc')
|
||||
# production_list = [production.name for production in production_programming]
|
||||
# production_no_remanufacture = production_programming.filtered(lambda a: a.is_remanufacture is False)
|
||||
# technology_design = self.env['sf.technology.design'].search(
|
||||
# [('process_parameters_id', '=', self.surface_technics_parameters_id.id),
|
||||
# ('production_id', '=', self.production_id.id)])
|
||||
# if technology_design.is_auto is False:
|
||||
# domain = [('origin', '=', self.production_id.name)]
|
||||
# else:
|
||||
domain = [('origin', '=', self.production_id.name), ('purchase_type', '=', 'consignment'),
|
||||
('state', '!=', 'cancel')]
|
||||
purchase_orders = self.env['purchase.order'].search(domain)
|
||||
purchase_orders_id = None
|
||||
for po in purchase_orders:
|
||||
for line in po.order_line:
|
||||
if line.product_id.server_product_process_parameters_id == self.surface_technics_parameters_id:
|
||||
if line.product_qty == 1:
|
||||
purchase_orders_id = line.order_id.id
|
||||
result = {
|
||||
"type": "ir.actions.act_window",
|
||||
"res_model": "purchase.order",
|
||||
"res_id": purchase_orders.id,
|
||||
"res_id": purchase_orders_id,
|
||||
# "domain": [['id', 'in', self.purchase_id]],
|
||||
"name": _("Purchase Orders"),
|
||||
'view_mode': 'form',
|
||||
@@ -225,6 +346,10 @@ class ResMrpWorkOrder(models.Model):
|
||||
material_height = fields.Float(string='高')
|
||||
# 零件图号
|
||||
part_number = fields.Char(related='production_id.part_number', string='零件图号')
|
||||
machining_drawings = fields.Binary('2D加工图纸', related='production_id.part_drawing', readonly=True)
|
||||
quality_standard = fields.Binary('质检标准', related='production_id.quality_standard', readonly=True)
|
||||
part_name = fields.Char(related='production_id.part_name', string='零件名称')
|
||||
|
||||
# 工序状态
|
||||
process_state = fields.Selection([
|
||||
('待装夹', '待装夹'),
|
||||
@@ -275,8 +400,11 @@ class ResMrpWorkOrder(models.Model):
|
||||
保存名称
|
||||
"""
|
||||
for record in self:
|
||||
if record.processing_panel:
|
||||
tem_name = record.production_id.name.replace('/', '_')
|
||||
record.save_name = tem_name + '_' + record.processing_panel
|
||||
else:
|
||||
record.save_name = ''
|
||||
|
||||
schedule_state = fields.Selection(related='production_id.schedule_state', store=True)
|
||||
# 工件装夹信息
|
||||
@@ -302,6 +430,7 @@ class ResMrpWorkOrder(models.Model):
|
||||
is_delivery = fields.Boolean('是否配送完成', default=False)
|
||||
rfid_code = fields.Char('RFID码')
|
||||
rfid_code_old = fields.Char('RFID码(已解除)')
|
||||
is_test_env = fields.Boolean('测试环境', default=False)
|
||||
|
||||
production_line_id = fields.Many2one('sf.production.line', related='production_id.production_line_id',
|
||||
string='生产线', store=True, tracking=True)
|
||||
@@ -317,9 +446,13 @@ class ResMrpWorkOrder(models.Model):
|
||||
detailed_reason = fields.Text('详细原因')
|
||||
is_rework = fields.Boolean(string='是否返工', default=False)
|
||||
|
||||
def button_change_env(self):
|
||||
self.is_test_env = not self.is_test_env
|
||||
|
||||
@api.constrains('blocked_by_workorder_ids')
|
||||
def _check_no_cyclic_dependencies(self):
|
||||
if self.production_id.state not in ['rework'] and self.state not in ['rework']:
|
||||
if self.production_id.state not in ['rework', 'technology_to_confirmed', 'confirmed'] and self.state not in [
|
||||
'rework']:
|
||||
if not self._check_m2m_recursion('blocked_by_workorder_ids'):
|
||||
raise ValidationError(_("您不能创建周期性的依赖关系."))
|
||||
|
||||
@@ -330,6 +463,7 @@ class ResMrpWorkOrder(models.Model):
|
||||
for workorder in self.blocked_by_workorder_ids:
|
||||
if workorder.state in ['done', 'cancel', 'rework']:
|
||||
continue
|
||||
if workorder.production_id.state not in ['technology_to_confirmed', 'confirmed']:
|
||||
workorder._plan_workorder(replan)
|
||||
start_date = max(start_date, workorder.date_planned_finished)
|
||||
# Plan only suitable workorders
|
||||
@@ -661,35 +795,14 @@ class ResMrpWorkOrder(models.Model):
|
||||
}}
|
||||
|
||||
# 拼接工单对象属性值
|
||||
def json_workorder_str(self, k, production, route, item):
|
||||
def json_workorder_str(self, production, route):
|
||||
# 计算预计时长duration_expected
|
||||
routing_types = ['切割', '装夹预调', 'CNC加工', '解除装夹']
|
||||
if route.routing_type in routing_types:
|
||||
if route.route_id.routing_type in routing_types:
|
||||
routing_workcenter = self.env['mrp.routing.workcenter'].sudo().search(
|
||||
[('name', '=', route.routing_type)])
|
||||
[('name', '=', route.routing_type if hasattr(route, 'routing_type') else route.route_id.routing_type)])
|
||||
duration_expected = routing_workcenter.time_cycle
|
||||
reserved_duration = routing_workcenter.reserved_duration
|
||||
# if route.routing_type == '切割':
|
||||
# duration_expected = self.env['mrp.routing.workcenter'].sudo().search(
|
||||
# [('name', '=', '切割')]).time_cycle
|
||||
# # elif route.routing_type == '获取CNC加工程序':
|
||||
# # duration_expected = self.env['mrp.routing.workcenter'].sudo().search(
|
||||
# # [('name', '=', '获取CNC加工程序')]).time_cycle
|
||||
# elif route.routing_type == '装夹预调':
|
||||
# duration_expected = self.env['mrp.routing.workcenter'].sudo().search(
|
||||
# [('name', '=', '装夹预调')]).time_cycle
|
||||
# # elif route.routing_type == '前置三元定位检测':
|
||||
# # duration_expected = self.env['mrp.routing.workcenter'].sudo().search(
|
||||
# # [('name', '=', '前置三元定位检测')]).time_cycle
|
||||
# elif route.routing_type == 'CNC加工':
|
||||
# duration_expected = self.env['mrp.routing.workcenter'].sudo().search(
|
||||
# [('name', '=', 'CNC加工')]).time_cycle
|
||||
# # elif route.routing_type == '后置三元质量检测':
|
||||
# # duration_expected = self.env['mrp.routing.workcenter'].sudo().search(
|
||||
# # [('name', '=', '后置三元质量检测')]).time_cycle
|
||||
# elif route.routing_type == '解除装夹':
|
||||
# duration_expected = self.env['mrp.routing.workcenter'].sudo().search(
|
||||
# [('name', '=', '解除装夹')]).time_cycle
|
||||
else:
|
||||
duration_expected = 60
|
||||
reserved_duration = 30
|
||||
@@ -697,26 +810,22 @@ class ResMrpWorkOrder(models.Model):
|
||||
'product_uom_id': production.product_uom_id.id,
|
||||
'qty_producing': 0,
|
||||
'operation_id': False,
|
||||
'name': route.route_workcenter_id.name,
|
||||
'processing_panel': k,
|
||||
'quality_point_ids': route.route_workcenter_id.quality_point_ids,
|
||||
'routing_type': route.routing_type,
|
||||
# 'work_state': '待发起',
|
||||
'workcenter_id': self.env['mrp.routing.workcenter'].get_workcenter(route.workcenter_ids.ids,
|
||||
route.routing_type,
|
||||
'name': route.name if hasattr(route, 'routing_type') else route.route_id.name,
|
||||
'processing_panel': False if hasattr(route, 'routing_type') else route.panel,
|
||||
'sequence': route.sequence,
|
||||
'quality_point_ids': False if hasattr(route, 'routing_type') else route.route_id.quality_point_ids,
|
||||
'routing_type': route.routing_type if hasattr(route, 'routing_type') else route.route_id.routing_type,
|
||||
'workcenter_id': False if hasattr(route, 'routing_type') else self.env[
|
||||
'mrp.routing.workcenter'].get_workcenter(route.route_id.workcenter_ids.ids,
|
||||
route.route_id.routing_type,
|
||||
production.product_id),
|
||||
# 设定初始化值,避免出现变成bool问题
|
||||
'date_planned_start': datetime.now(),
|
||||
'date_planned_finished': datetime.now() + timedelta(days=1),
|
||||
'duration_expected': duration_expected,
|
||||
'duration': 0,
|
||||
'tag_type': '重新加工' if item is False else False,
|
||||
'cnc_ids': False if route.routing_type != 'CNC加工' else self.env['sf.cnc.processing']._json_cnc_processing(
|
||||
k, item),
|
||||
'cmm_ids': False if route.routing_type != 'CNC加工' else self.env['sf.cmm.program']._json_cmm_program(k,
|
||||
item),
|
||||
# 'workpiece_delivery_ids': False if not route.routing_type == '装夹预调' else self._json_workpiece_delivery_list(
|
||||
# production)
|
||||
'technology_design_id': route.id,
|
||||
# 'tag_type': '重新加工' if item is False else False,
|
||||
'reserved_duration': reserved_duration,
|
||||
}]
|
||||
return workorders_values_str
|
||||
@@ -747,22 +856,24 @@ class ResMrpWorkOrder(models.Model):
|
||||
]
|
||||
|
||||
# 拼接工单对象属性值(表面工艺)
|
||||
def _json_workorder_surface_process_str(self, production, route, process_parameter, supplier_id):
|
||||
def _json_workorder_surface_process_str(self, production, route, supplier_id):
|
||||
workorders_values_str = [0, '', {
|
||||
'product_uom_id': production.product_uom_id.id,
|
||||
'qty_producing': 0,
|
||||
'operation_id': False,
|
||||
'name': '%s-%s' % (route.name, process_parameter.name),
|
||||
'name': route.process_parameters_id.display_name,
|
||||
'processing_panel': '',
|
||||
'sequence': route.sequence,
|
||||
'technology_design_id': route.id,
|
||||
'routing_type': '表面工艺',
|
||||
'surface_technics_parameters_id': process_parameter.id,
|
||||
'surface_technics_parameters_id': route.process_parameters_id.id,
|
||||
'work_state': '',
|
||||
'supplier_id': supplier_id,
|
||||
'is_subcontract': True if process_parameter.gain_way == '外协' else False,
|
||||
'is_subcontract': True if route.process_parameters_id.gain_way == '外协' else False,
|
||||
'workcenter_id': self.env[
|
||||
'mrp.workcenter'].get_process_outsourcing_workcenter() if process_parameter.gain_way == '外协' else
|
||||
self.env['mrp.routing.workcenter'].get_workcenter(route.workcenter_ids.ids,
|
||||
route.routing_type,
|
||||
'mrp.workcenter'].get_process_outsourcing_workcenter() if route.process_parameters_id.gain_way == '外协' else
|
||||
self.env['mrp.routing.workcenter'].get_workcenter(route.route_id.workcenter_ids.ids,
|
||||
route.route_id.routing_type,
|
||||
production.product_id),
|
||||
'date_planned_start': datetime.now(),
|
||||
'date_planned_finished': datetime.now() + timedelta(days=1),
|
||||
@@ -914,170 +1025,174 @@ class ResMrpWorkOrder(models.Model):
|
||||
return workorders_values_str
|
||||
|
||||
@api.depends('production_availability', 'blocked_by_workorder_ids', 'blocked_by_workorder_ids.state',
|
||||
'production_id.tool_state')
|
||||
'production_id.tool_state', 'production_id.schedule_state', 'sequence',
|
||||
'production_id.programming_state')
|
||||
def _compute_state(self):
|
||||
super()._compute_state()
|
||||
# super()._compute_state()
|
||||
for workorder in self:
|
||||
re_work = self.env['mrp.workorder'].search([('production_id', '=', workorder.production_id.id),
|
||||
('processing_panel', '=', workorder.processing_panel),
|
||||
('is_rework', '=', True), ('state', 'in', ['done', 'rework'])])
|
||||
cnc_workorder = self.env['mrp.workorder'].search(
|
||||
if workorder.sequence != 1:
|
||||
previous_workorder = self.env['mrp.workorder'].search(
|
||||
[('production_id', '=', workorder.production_id.id),
|
||||
('processing_panel', '=', workorder.processing_panel),
|
||||
('routing_type', '=', 'CNC加工'), ('state', 'in', ['done', 'rework']),
|
||||
('test_results', '=', '返工')])
|
||||
cnc_workorder_pending = self.env['mrp.workorder'].search(
|
||||
[('production_id', '=', workorder.production_id.id),
|
||||
('processing_panel', '=', workorder.processing_panel),
|
||||
('routing_type', '=', 'CNC加工'), ('state', 'in', ['pending'])])
|
||||
unclamp_workorder = self.env['mrp.workorder'].search(
|
||||
[('production_id', '=', workorder.production_id.id),
|
||||
('sequence', '=', workorder.sequence - 1),
|
||||
('state', 'in', ['done'])])
|
||||
if workorder.state not in ['cancel', 'progress', 'rework']:
|
||||
if workorder.production_id.state == 'rework':
|
||||
if workorder.routing_type == '装夹预调' and workorder.state not in ['done', 'rework',
|
||||
'cancel']:
|
||||
# # 有返工工单
|
||||
# if re_work:
|
||||
# 新工单
|
||||
if workorder.is_rework is False:
|
||||
if workorder.production_id.programming_state == '已编程' and workorder.production_id.is_rework is False:
|
||||
if re_work or cnc_workorder:
|
||||
('sequence', '=', workorder.sequence - 1)])
|
||||
if workorder.state == 'pending':
|
||||
if all([wo.state in ('done', 'cancel') for wo in workorder.blocked_by_workorder_ids]):
|
||||
if workorder.production_id.reservation_state == 'assigned' and workorder.production_id.schedule_state == '已排':
|
||||
if ((workorder.sequence == 1 and not workorder.blocked_by_workorder_ids)
|
||||
or (workorder.blocked_by_workorder_ids.state in ('done', 'cancel')
|
||||
and workorder.blocked_by_workorder_ids.test_results not in ['报废', '返工'])
|
||||
or (previous_workorder.state in ('done', 'cancel')
|
||||
and not workorder.blocked_by_workorder_ids
|
||||
and previous_workorder.test_results not in ['报废', '返工'])
|
||||
):
|
||||
workorder.state = 'ready'
|
||||
else:
|
||||
if workorder.production_id.is_rework is True:
|
||||
if re_work or cnc_workorder:
|
||||
continue
|
||||
if workorder.production_id.schedule_state == '未排' and workorder.state in ('waiting', 'ready'):
|
||||
if workorder.sequence != 1:
|
||||
workorder.state = 'pending'
|
||||
continue
|
||||
if workorder.state not in ('waiting', 'ready'):
|
||||
continue
|
||||
if workorder.state in (
|
||||
'waiting') and workorder.sequence == 1 and workorder.production_id.schedule_state == '已排':
|
||||
workorder.state = 'ready'
|
||||
continue
|
||||
if not all([wo.state in ('done', 'cancel') for wo in workorder.blocked_by_workorder_ids]):
|
||||
workorder.state = 'pending'
|
||||
if workorder.state in ['waiting']:
|
||||
if previous_workorder.state == 'waiting':
|
||||
workorder.state = 'pending'
|
||||
if workorder.sequence == 1 and workorder.state == 'pending':
|
||||
workorder.state = 'waiting'
|
||||
continue
|
||||
if workorder.production_id.reservation_state not in ('waiting', 'confirmed', 'assigned'):
|
||||
continue
|
||||
if workorder.production_id.reservation_state == 'assigned' and workorder.state == 'waiting' and workorder.production_id.schedule_state == '已排':
|
||||
workorder.state = 'ready'
|
||||
elif workorder.production_id.reservation_state != 'assigned' and workorder.state == 'ready':
|
||||
workorder.state = 'waiting'
|
||||
|
||||
elif workorder.routing_type == 'CNC加工' and workorder.state not in ['done', 'rework', 'cancel']:
|
||||
pre_workorder = self.env['mrp.workorder'].search(
|
||||
[('production_id', '=', workorder.production_id.id),
|
||||
('processing_panel', '=', workorder.processing_panel),
|
||||
('routing_type', '=', '装夹预调'), ('state', '=', 'done')])
|
||||
if pre_workorder:
|
||||
if re_work:
|
||||
workorder.state = 'waiting'
|
||||
elif workorder.routing_type == '解除装夹' and workorder.state not in ['done', 'rework', 'cancel']:
|
||||
if cnc_workorder:
|
||||
if not cnc_workorder_pending or unclamp_workorder.test_results == '报废':
|
||||
workorder.state = 'waiting'
|
||||
# else:
|
||||
# if workorder.production_id.is_rework is True:
|
||||
# workorder.state = 'waiting'
|
||||
elif workorder.production_id.state == 'progress':
|
||||
if workorder.routing_type == '装夹预调' and workorder.production_id.programming_state == '已编程' and \
|
||||
workorder.is_rework is False and workorder.state not in [
|
||||
'done', 'rework',
|
||||
'cancel']:
|
||||
if workorder.production_id.is_rework is False:
|
||||
if re_work or cnc_workorder or unclamp_workorder:
|
||||
for workorder in self:
|
||||
# 如果工单的工序没有进行排序则跳出循环
|
||||
if workorder.production_id.workorder_ids.filtered(lambda wk: wk.sequence == 0):
|
||||
continue
|
||||
# ===== 对所有按序号排序的非[进行中、完成、返工、取消]状态的工单,除了第一条之外的工单状态都设置为[等待其他工单] =====
|
||||
# logging.info(workorder.state)
|
||||
work_ids = workorder.production_id.workorder_ids.filtered(
|
||||
lambda wk: wk.state not in ['done', 'rework', 'cancel'])
|
||||
if not work_ids:
|
||||
continue
|
||||
min_sequence_wk = min(work_ids, key=lambda wk: wk.sequence)
|
||||
if workorder.state in ['done', 'rework', 'cancel', 'progress', 'to be detected']:
|
||||
continue
|
||||
else:
|
||||
if workorder != min_sequence_wk:
|
||||
if workorder.state != 'pending':
|
||||
workorder.state = 'pending'
|
||||
continue
|
||||
# ================= 如果制造订单制造类型为【人工线下加工】==========================
|
||||
if (workorder.production_id.production_type == '人工线下加工'
|
||||
and workorder.production_id.schedule_state == '已排'
|
||||
and len(workorder.production_id.picking_ids.filtered(
|
||||
lambda w: w.state not in ['done', 'cancel'])) == 0):
|
||||
workorder.state = 'ready'
|
||||
# if (re_work or cnc_workorder) and workorder.production_id.is_rework is False:
|
||||
# workorder.state = 'ready'
|
||||
if workorder.routing_type == '表面工艺' and workorder.state not in ['done', 'progress']:
|
||||
if unclamp_workorder:
|
||||
continue
|
||||
# ================= 如果制造订单刀具状态为[无效刀、缺刀] 或者 制造订单状态为[返工]==========================
|
||||
if (workorder.production_id.tool_state in ['1', '2'] or workorder.production_id.state == 'rework'
|
||||
or workorder.production_id.schedule_state != '已排'
|
||||
or workorder.production_id.reservation_state not in ['assigned']
|
||||
or workorder.production_id.workorder_ids.filtered(
|
||||
lambda wk: wk.sequence == workorder.sequence - 1).test_results in ['报废', '返工']):
|
||||
if workorder.state != 'waiting':
|
||||
workorder.state = 'waiting'
|
||||
continue
|
||||
if workorder.production_id.programming_state == '已编程':
|
||||
workorder.state = 'ready'
|
||||
elif workorder.state != 'waiting':
|
||||
workorder.state = 'waiting'
|
||||
# =========== 特殊工艺工单处理 ===================
|
||||
# if workorder.routing_type == '表面工艺' and workorder.is_subcontrac:
|
||||
# purchase_order = self.env['purchase.order'].search(
|
||||
# [('origin', 'ilike', workorder.production_id.name)])
|
||||
# if purchase_order.picking_ids.filtered(lambda p: p.state in ['waiting', 'confirmed', 'assigned']):
|
||||
# workorder.state = 'waiting'
|
||||
# continue
|
||||
if workorder.technology_design_id.routing_tag == 'special':
|
||||
if workorder.is_subcontract is False:
|
||||
workorder.state = 'ready'
|
||||
else:
|
||||
production_programming = self.env['mrp.production'].search(
|
||||
[('programming_no', '=', self.production_id.programming_no)], order='name asc')
|
||||
[('origin', '=', self.production_id.origin)], order='name asc')
|
||||
production_no_remanufacture = production_programming.filtered(
|
||||
lambda a: a.is_remanufacture is False)
|
||||
production_list = [production.name for production in production_programming]
|
||||
purchase_orders = self.env['purchase.order'].search(
|
||||
[('origin', 'ilike', ','.join(production_list))])
|
||||
for line in purchase_orders.order_line:
|
||||
if line.product_id.server_product_process_parameters_id == workorder.surface_technics_parameters_id and line.product_qty == len(
|
||||
production_no_remanufacture):
|
||||
if purchase_orders.state == 'purchase':
|
||||
if (
|
||||
line.product_id.server_product_process_parameters_id == workorder.surface_technics_parameters_id
|
||||
and line.product_qty == len(production_no_remanufacture)):
|
||||
if all(pur_order.state == 'purchase' for pur_order in purchase_orders):
|
||||
workorder.state = 'ready'
|
||||
else:
|
||||
workorder.state = 'waiting'
|
||||
elif workorder.production_id.state == 'scrap':
|
||||
if workorder.routing_type == '解除装夹' and unclamp_workorder.test_results == '报废':
|
||||
workorder.state = 'waiting'
|
||||
if workorder.routing_type == '装夹预调' and workorder.state in ['waiting', 'ready', 'pending']:
|
||||
workorder_ids = workorder.production_id.workorder_ids
|
||||
work_bo = True
|
||||
for wo in workorder_ids.filtered(lambda a: a.routing_type == '装夹预调' and a.state == 'rework'):
|
||||
if not workorder_ids.filtered(
|
||||
lambda a: (a.routing_type == '装夹预调' and a.state not in ['rework', 'cancel']
|
||||
and a.processing_panel == wo.processing_panel)):
|
||||
work_bo = False
|
||||
break
|
||||
if (workorder.production_id.programming_state == '已编程' and work_bo
|
||||
and not workorder_ids.filtered(lambda a: a.sequence == 0)):
|
||||
# 当工单对应制造订单的功能刀具状态为 【无效刀】时,先对的第一个装夹预调工单状态设置为 【等待组件】
|
||||
if workorder.production_id.tool_state in ['1', '2']:
|
||||
if workorder.state in ['ready']:
|
||||
workorder.state = 'waiting'
|
||||
continue
|
||||
elif workorder.state in ['waiting']:
|
||||
continue
|
||||
elif workorder.state == 'pending' and workorder == self.search(
|
||||
[('production_id', '=', workorder.production_id.id),
|
||||
('routing_type', '=', '装夹预调'),
|
||||
('state', 'not in', ['rework', 'done', 'cancel'])],
|
||||
limit=1,
|
||||
order="sequence"):
|
||||
workorder.state = 'waiting'
|
||||
continue
|
||||
elif workorder.production_id.tool_state in ['0']:
|
||||
if workorder_ids.filtered(lambda a: a.state == 'rework'):
|
||||
if not workorder_ids.filtered(
|
||||
lambda a: (a.routing_type not in ['装夹预调'] and
|
||||
a.state not in ['pending', 'done', 'rework', 'cancel'])):
|
||||
# 查询工序最小的非完工、非返工的装夹预调工单
|
||||
work_id = self.search(
|
||||
[('production_id', '=', workorder.production_id.id),
|
||||
('state', 'not in', ['rework', 'done', 'cancel'])],
|
||||
limit=1,
|
||||
order="sequence")
|
||||
if work_id.routing_type == '装夹预调':
|
||||
if workorder == work_id:
|
||||
if workorder.production_id.reservation_state == 'assigned':
|
||||
workorder.state = 'ready'
|
||||
elif workorder.production_id.reservation_state != 'assigned':
|
||||
workorder.state = 'waiting'
|
||||
continue
|
||||
elif (workorder.name == '装夹预调' and
|
||||
workorder.state not in ['rework', 'done', 'cancel']):
|
||||
if workorder.state != 'pending':
|
||||
workorder.state = 'pending'
|
||||
if workorder.production_id.tool_state in ['1', '2'] and workorder.state == 'ready':
|
||||
workorder.state = 'waiting'
|
||||
continue
|
||||
|
||||
# elif workorder.routing_type == 'CNC加工' and workorder.state not in ['done', 'cancel', 'progress',
|
||||
# 'rework']:
|
||||
# per_work = self.env['mrp.workorder'].search(
|
||||
# [('routing_type', '=', '装夹预调'), ('production_id', '=', workorder.production_id.id),
|
||||
# ('processing_panel', '=', workorder.processing_panel), ('is_rework', '=', True)])
|
||||
# if per_work:
|
||||
# workorder.state = 'waiting'
|
||||
# if workorder.routing_type == 'CNC加工' and workorder.state == 'progress':
|
||||
# workorder.state = 'to be detected'
|
||||
|
||||
# for workorder in self:
|
||||
# if workorder.is_rework is True and workorder.state == 'done':
|
||||
# cnc_work = self.env['mrp.workorder'].search([('routing_type','=','CNC加工'),('production_id','=',workorder.production_id.id)])
|
||||
# if cnc_work:
|
||||
# cnc_work.state = 'waiting'
|
||||
# if workorder.state == 'pending':
|
||||
# if all([wo.state in ('done', 'cancel') for wo in workorder.blocked_by_workorder_ids]):
|
||||
# workorder.state = 'ready' if workorder.production_id.reservation_state == 'assigned' else 'waiting'
|
||||
# continue
|
||||
# if workorder.state not in ('waiting', 'ready'):
|
||||
# continue
|
||||
# if not all([wo.state in ('done', 'cancel') for wo in workorder.blocked_by_workorder_ids]):
|
||||
# workorder.state = 'pending'
|
||||
# continue
|
||||
# if workorder.production_id.reservation_state not in ('waiting', 'confirmed', 'assigned'):
|
||||
# continue
|
||||
# if workorder.production_id.reservation_state == 'assigned' and workorder.state == 'waiting':
|
||||
# re_work = self.env['mrp.workorder'].search([('production_id', '=', workorder.production_id.id),
|
||||
# ('processing_panel', '=', workorder.processing_panel),
|
||||
# ('is_rework', '=', True), ('state', 'in', ['done', 'rework'])])
|
||||
# cnc_workorder = self.env['mrp.workorder'].search(
|
||||
# [('production_id', '=', workorder.production_id.id),
|
||||
# ('processing_panel', '=', workorder.processing_panel),
|
||||
# ('routing_type', '=', 'CNC加工'), ('state', 'in', ['done', 'rework']),
|
||||
# ('test_results', '=', '返工')])
|
||||
# cnc_workorder_pending = self.env['mrp.workorder'].search(
|
||||
# [('production_id', '=', workorder.production_id.id),
|
||||
# ('processing_panel', '=', workorder.processing_panel),
|
||||
# ('routing_type', '=', 'CNC加工'), ('state', 'in', ['pending'])])
|
||||
# unclamp_workorder = self.env['mrp.workorder'].search(
|
||||
# [('production_id', '=', workorder.production_id.id),
|
||||
# ('sequence', '=', workorder.sequence - 1),
|
||||
# ('state', 'in', ['done'])])
|
||||
# if workorder.state not in ['cancel', 'progress', 'rework']:
|
||||
# if workorder.production_id.state == 'rework':
|
||||
# if workorder.routing_type == '装夹预调':
|
||||
# # # 有返工工单
|
||||
# # if re_work:
|
||||
# # 新工单
|
||||
# if workorder.is_rework is False:
|
||||
# if (workorder.production_id.programming_state == '已编程'
|
||||
# and workorder.production_id.is_rework is False):
|
||||
# if re_work or cnc_workorder:
|
||||
# workorder.state = 'ready'
|
||||
# elif workorder.production_id.reservation_state != 'assigned' and workorder.state == 'ready':
|
||||
# else:
|
||||
# if workorder.production_id.is_rework is True:
|
||||
# if re_work or cnc_workorder:
|
||||
# workorder.state = 'waiting'
|
||||
#
|
||||
# elif workorder.routing_type == 'CNC加工':
|
||||
# pre_workorder = self.env['mrp.workorder'].search(
|
||||
# [('production_id', '=', workorder.production_id.id),
|
||||
# ('processing_panel', '=', workorder.processing_panel),
|
||||
# ('routing_type', '=', '装夹预调'), ('state', '=', 'done')])
|
||||
# if pre_workorder:
|
||||
# if re_work:
|
||||
# workorder.state = 'waiting'
|
||||
# elif workorder.routing_type == '解除装夹':
|
||||
# if cnc_workorder:
|
||||
# if not cnc_workorder_pending or unclamp_workorder.test_results == '报废':
|
||||
# workorder.state = 'waiting'
|
||||
# # else:
|
||||
# # if workorder.production_id.is_rework is True:
|
||||
# # workorder.state = 'waiting'
|
||||
# elif workorder.production_id.state == 'progress':
|
||||
# if (workorder.routing_type == '装夹预调' and workorder.production_id.programming_state == '已编程'
|
||||
# and workorder.is_rework is False and workorder.state not in ['done', 'rework', 'cancel']):
|
||||
# if workorder.production_id.is_rework is False:
|
||||
# if re_work or cnc_workorder or unclamp_workorder:
|
||||
# workorder.state = 'ready'
|
||||
# # if (re_work or cnc_workorder) and workorder.production_id.is_rework is False:
|
||||
# # workorder.state = 'ready'
|
||||
# elif workorder.production_id.state == 'scrap':
|
||||
# if workorder.routing_type == '解除装夹' and unclamp_workorder.test_results == '报废':
|
||||
# workorder.state = 'waiting'
|
||||
|
||||
# 重写工单开始按钮方法
|
||||
@@ -1088,21 +1203,30 @@ class ResMrpWorkOrder(models.Model):
|
||||
'actual_start_time': datetime.now()
|
||||
})
|
||||
|
||||
if self.routing_type == '装夹预调':
|
||||
if self.sequence == 1:
|
||||
# 判断工单状态是否为等待组件
|
||||
if self.state == 'waiting':
|
||||
raise UserError('制造订单【%s】缺少组件信息!' % self.production_id.name)
|
||||
# 判断是否有坯料的序列号信息
|
||||
boolean = False
|
||||
if self.production_id.move_raw_ids:
|
||||
if self.production_id.move_raw_ids[0].product_id.categ_type == '坯料':
|
||||
if self.production_id.move_raw_ids[0].move_line_ids:
|
||||
if self.production_id.move_raw_ids[0].move_line_ids:
|
||||
if self.production_id.move_raw_ids[0].move_line_ids[0].lot_id.name:
|
||||
boolean = True
|
||||
else:
|
||||
boolean = True
|
||||
if not boolean:
|
||||
raise UserError('制造订单【%s】缺少组件的序列号信息!' % self.production_id.name)
|
||||
self.pro_code = self.production_id.move_raw_ids[0].move_line_ids[0].lot_id.name
|
||||
# cnc校验
|
||||
if self.production_id.production_type == '自动化产线加工':
|
||||
cnc_workorder = self.search(
|
||||
[('production_id', '=', self.production_id.id), ('routing_type', '=', 'CNC加工')],
|
||||
limit=1, order='id asc')
|
||||
if not cnc_workorder.cnc_ids:
|
||||
raise UserError(_('该制造订单还未下发CNC程序,请稍后再试'))
|
||||
# if not cnc_workorder.cnc_ids:
|
||||
# raise UserError(_('该制造订单还未下发CNC程序,请稍后再试'))
|
||||
else:
|
||||
if self.production_id.tool_state in ['1', '2']:
|
||||
if self.production_id.tool_state == '1':
|
||||
@@ -1124,10 +1248,14 @@ class ResMrpWorkOrder(models.Model):
|
||||
[('barcode', 'ilike', 'WH-PREPRODUCTION')]).id),
|
||||
('location_dest_id', '=', self.env['stock.location'].search(
|
||||
[('barcode', 'ilike', 'VL-SPOC')]).id),
|
||||
('origin', '=', self.production_id.name)])
|
||||
if move_out.state != 'done':
|
||||
move_out.write({'state': 'assigned', 'production_id': False})
|
||||
self.env['stock.move.line'].create(move_out.get_move_line(self.production_id, self))
|
||||
('origin', '=', self.production_id.name), ('state', 'not in', ['cancel', 'done'])])
|
||||
for mo in move_out:
|
||||
pick = self.env['stock.picking'].search([('id', '=', mo.picking_id.id), ('name', 'ilike', 'OCOUT'),
|
||||
('partner_id', '=', self.supplier_id.id)])
|
||||
if pick:
|
||||
if mo.state != 'done':
|
||||
mo.write({'state': 'assigned', 'production_id': False})
|
||||
self.env['stock.move.line'].create(mo.get_move_line(self.production_id, self))
|
||||
|
||||
# move_out._action_assign()
|
||||
if self.state == 'waiting' or self.state == 'ready' or self.state == 'progress':
|
||||
@@ -1197,8 +1325,8 @@ class ResMrpWorkOrder(models.Model):
|
||||
if record.is_rework is False:
|
||||
if not record.material_center_point:
|
||||
raise UserError("坯料中心点为空,请检查")
|
||||
if record.X_deviation_angle <= 0:
|
||||
raise UserError("X偏差角度小于等于0,请检查!本次计算的X偏差角度为:%s" % record.X_deviation_angle)
|
||||
# if record.X_deviation_angle <= 0:
|
||||
# raise UserError("X偏差角度小于等于0,请检查!本次计算的X偏差角度为:%s" % record.X_deviation_angle)
|
||||
record.process_state = '待加工'
|
||||
# record.write({'process_state': '待加工'})
|
||||
record.production_id.process_state = '待加工'
|
||||
@@ -1228,23 +1356,10 @@ class ResMrpWorkOrder(models.Model):
|
||||
'''
|
||||
record.date_finished = datetime.now()
|
||||
if record.routing_type == '表面工艺':
|
||||
logging.info('record.picking_ids:%s' % record.picking_ids)
|
||||
logging.info('record.picking_out:%s' % record.picking_ids[0])
|
||||
if record.picking_ids:
|
||||
for pick_item in record.picking_ids:
|
||||
if pick_item.state not in ['done']:
|
||||
raise UserError(
|
||||
'请先完成该工单的工艺外协再进行操作')
|
||||
picking_out = record.env['stock.move.line'].search(
|
||||
[('picking_id', '=', record.picking_ids[0].id)])
|
||||
logging.info('picking_out:%s' % picking_out.picking_id.name)
|
||||
# if picking_out:
|
||||
# order_line_ids = []
|
||||
# logging.info('surface_technics_parameters_id:%s' % record.surface_technics_parameters_id.name)
|
||||
#
|
||||
# else:
|
||||
# raise UserError(
|
||||
# '请先在产品中配置表面工艺为%s相关的外协服务产品' % item.surface_technics_parameters_id.name)
|
||||
picks = record.picking_ids.filtered(lambda p: p.state not in ('done'))
|
||||
if picks:
|
||||
raise UserError('请先完成该工单的工艺外协再进行操作')
|
||||
tem_date_planned_finished = record.date_planned_finished
|
||||
tem_date_finished = record.date_finished
|
||||
logging.info('routing_type:%s' % record.routing_type)
|
||||
@@ -1263,7 +1378,7 @@ class ResMrpWorkOrder(models.Model):
|
||||
# record.recreateManufacturingOrWorkerOrder()
|
||||
is_production_id = False
|
||||
rework_workorder = record.production_id.workorder_ids.filtered(lambda p: p.state == 'rework')
|
||||
done_workorder = record.production_id.workorder_ids.filtered(lambda p1: p1.state == 'done')
|
||||
done_workorder = record.production_id.workorder_ids.filtered(lambda p1: p1.state in ['done'])
|
||||
if (len(rework_workorder) + len(done_workorder) == len(record.production_id.workorder_ids)) or (
|
||||
len(done_workorder) == len(record.production_id.workorder_ids)):
|
||||
is_production_id = True
|
||||
@@ -1280,7 +1395,8 @@ class ResMrpWorkOrder(models.Model):
|
||||
# workorder.rfid_code_old = rfid_code
|
||||
# workorder.rfid_code = False
|
||||
logging.info('workorder.rfid_code:%s' % workorder.rfid_code)
|
||||
if is_production_id is True and record.routing_type in ['解除装夹', '表面工艺']:
|
||||
# if is_production_id is True and record.routing_type in ['解除装夹', '表面工艺', '切割']:
|
||||
if is_production_id is True:
|
||||
logging.info('product_qty:%s' % record.production_id.product_qty)
|
||||
for move_raw_id in record.production_id.move_raw_ids:
|
||||
move_raw_id.quantity_done = move_raw_id.product_uom_qty
|
||||
@@ -1935,3 +2051,56 @@ class CMMprogram(models.Model):
|
||||
'program_create_date': datetime.strptime(item['program_create_date'], '%Y-%m-%d %H:%M:%S'),
|
||||
}))
|
||||
return cmm_program
|
||||
|
||||
def update_work_start_end(self, date_planned_start, date_planned_end):
|
||||
self.leave_id.write({
|
||||
'date_from': date_planned_start,
|
||||
'date_to': date_planned_end,
|
||||
})
|
||||
self.date_planned_finished = datetime.datetime.today() + datetime.timedelta(days=100)
|
||||
self.date_planned_start = date_planned_start
|
||||
self.date_planned_finished = date_planned_end
|
||||
routing_workcenter = self.env['mrp.routing.workcenter'].sudo().search(
|
||||
[('name', '=', self.routing_type)])
|
||||
|
||||
self.write({'date_planned_start': date_planned_start, 'date_planned_finished': date_planned_end,
|
||||
'duration_expected': routing_workcenter.time_cycle})
|
||||
|
||||
def auto_production_process(self, last_time, is_first, type_map):
|
||||
date_planned_end = None
|
||||
date_planned_start = None
|
||||
duration_expected = datetime.timedelta(minutes=self.duration_expected)
|
||||
reserve_time = datetime.timedelta(minutes=self.reserved_duration)
|
||||
if is_first:
|
||||
# 第一轮加工
|
||||
if self.routing_type == '装夹预调':
|
||||
date_planned_end = last_time - reserve_time
|
||||
date_planned_start = date_planned_end - duration_expected
|
||||
elif self.routing_type == 'CNC加工':
|
||||
date_planned_start = last_time
|
||||
date_planned_end = last_time + duration_expected
|
||||
last_time = date_planned_end
|
||||
else:
|
||||
date_planned_start = last_time + reserve_time
|
||||
date_planned_end = date_planned_start + duration_expected
|
||||
last_time = date_planned_end
|
||||
type_map.update({self.routing_type: True})
|
||||
else:
|
||||
date_planned_start = last_time + reserve_time
|
||||
date_planned_end = date_planned_start + duration_expected
|
||||
last_time = date_planned_end
|
||||
return date_planned_start, date_planned_end, last_time
|
||||
|
||||
def manual_offline_process(self, last_time, is_first):
|
||||
date_planned_end = None
|
||||
date_planned_start = None
|
||||
duration_expected = datetime.timedelta(minutes=self.duration_expected)
|
||||
reserve_time = datetime.timedelta(minutes=self.reserved_duration)
|
||||
if is_first:
|
||||
date_planned_start = last_time
|
||||
date_planned_end = last_time + duration_expected
|
||||
|
||||
else:
|
||||
date_planned_start = last_time + reserve_time
|
||||
date_planned_end = date_planned_start + duration_expected
|
||||
return date_planned_start, date_planned_end, last_time
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user