Compare commits
788 Commits
feature/零件
...
feature/齐套
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d1f7b11eb | ||
|
|
0d76d1a7d3 | ||
|
|
9ad06b4361 | ||
|
|
f9c13341e6 | ||
|
|
d2a61c79d0 | ||
|
|
5f80b377e5 | ||
|
|
ab2e685004 | ||
|
|
dcc5eef006 | ||
|
|
7e8b2adb3d | ||
|
|
9aa870065b | ||
|
|
60c39929b2 | ||
|
|
9a5b8a2627 | ||
|
|
73696eb72c | ||
|
|
55babb8a35 | ||
|
|
6682dbfb7d | ||
|
|
e0b8e5fe07 | ||
|
|
800f2eb6a7 | ||
|
|
3cc633131e | ||
|
|
e3fb266890 | ||
|
|
844f7de1ab | ||
|
|
13d33488dc | ||
|
|
266601cf84 | ||
|
|
2e168a4ba7 | ||
|
|
68ba47ed70 | ||
|
|
5bb6fcd4f7 | ||
|
|
9a05bbd82c | ||
|
|
b33c992b25 | ||
|
|
5947b3dfe9 | ||
|
|
95e43b0fba | ||
|
|
e7afc76753 | ||
|
|
788183e239 | ||
|
|
4ddabdefa1 | ||
|
|
364127beb3 | ||
|
|
109ea8729d | ||
|
|
32de726164 | ||
|
|
79f89f068b | ||
|
|
52189cff72 | ||
|
|
aadccce47e | ||
|
|
abd88fd721 | ||
|
|
2c2fa87719 | ||
|
|
f38f60a6a8 | ||
|
|
7d8b7048a8 | ||
|
|
e7b312fb22 | ||
|
|
bc641bde3a | ||
|
|
d73ffab9b5 | ||
|
|
4b1c0bc3b5 | ||
|
|
45730e2dd4 | ||
|
|
8903f32cf8 | ||
|
|
8bd1c8a095 | ||
|
|
abf9a0997b | ||
|
|
aec2d1c516 | ||
|
|
127efb67d7 | ||
|
|
78edf1f7eb | ||
|
|
e6e13a5970 | ||
|
|
55cc4906ef | ||
|
|
427b548939 | ||
|
|
8c9db7e8c8 | ||
|
|
990f73ea4e | ||
|
|
d21e0c7fd9 | ||
|
|
b4ed65a75c | ||
|
|
53ba2ec589 | ||
|
|
566bc0e1e2 | ||
|
|
5f216684f0 | ||
|
|
e86bd59a43 | ||
|
|
e55625e4b6 | ||
|
|
790dd3dd05 | ||
|
|
84846fb3da | ||
|
|
f45a46255b | ||
|
|
fe5b005e45 | ||
|
|
fc33280509 | ||
|
|
60ed04cf77 | ||
|
|
3439b47b0c | ||
|
|
33c5d648b5 | ||
|
|
c7b50d9706 | ||
|
|
05caed1d91 | ||
|
|
8b66fda899 | ||
|
|
1456d4303b | ||
|
|
9d2f23de7c | ||
|
|
9393a48e70 | ||
|
|
749f44d6df | ||
|
|
4f000a6be4 | ||
|
|
7af7079a5c | ||
|
|
376eb9e56f | ||
|
|
531bc2e090 | ||
|
|
bc3ab21d75 | ||
|
|
d1811ea24b | ||
|
|
b426d5d505 | ||
|
|
1dfa22900d | ||
|
|
0eeebf437a | ||
|
|
397d4f29a1 | ||
|
|
33205c5d29 | ||
|
|
568f3e4f30 | ||
|
|
8c43fc2b76 | ||
|
|
8960d3d07c | ||
|
|
95b5c86242 | ||
|
|
61034c3424 | ||
|
|
c2000aa9c5 | ||
|
|
e29456bbf7 | ||
|
|
10e995ec7f | ||
|
|
a2f8dc6cec | ||
|
|
fec095ca6b | ||
|
|
aed33dbb35 | ||
|
|
dbe8c95558 | ||
|
|
5c7e6e969f | ||
|
|
d70b757487 | ||
|
|
fb3bb8f1c0 | ||
|
|
9123aeaee8 | ||
|
|
e3af0bea3c | ||
|
|
af1bc021d6 | ||
|
|
2febc369bb | ||
|
|
6ee1c5f9a9 | ||
|
|
5ef8023169 | ||
|
|
fa03b562a2 | ||
|
|
5f55c954d1 | ||
|
|
e0ca13b5b7 | ||
|
|
dba38f4d37 | ||
|
|
ceb7a02209 | ||
|
|
1811dbf0fd | ||
|
|
1c1d1a74ad | ||
|
|
347019d7ee | ||
|
|
7d46d00fd7 | ||
|
|
5a22402e7a | ||
|
|
f53e34aeb4 | ||
|
|
e145e8a3a4 | ||
|
|
0ef6fe73f3 | ||
|
|
ffad4b7995 | ||
|
|
2c7fbd3aef | ||
|
|
72b8d33a3e | ||
|
|
6517d2bd12 | ||
|
|
2c97287218 | ||
|
|
012ff120b4 | ||
|
|
960f05090c | ||
|
|
6321e7ef23 | ||
|
|
69d200973b | ||
|
|
448a2cd277 | ||
|
|
5a071188cc | ||
|
|
b8894609a9 | ||
|
|
3f8fd6da62 | ||
|
|
154a17657c | ||
|
|
62ead52f00 | ||
|
|
23d6e38b24 | ||
|
|
bdc23afc56 | ||
|
|
b8043b3ad2 | ||
|
|
8841d800ea | ||
|
|
920e96ffc6 | ||
|
|
d52f5fa841 | ||
|
|
37c5c9d498 | ||
|
|
6867d7e4ce | ||
|
|
9cbd311fec | ||
|
|
0e753b1c85 | ||
|
|
48316c55b7 | ||
|
|
b33ba9c354 | ||
|
|
e0559e9887 | ||
|
|
39a25bb6c8 | ||
|
|
796e9b0cef | ||
|
|
e129c08426 | ||
|
|
c6b47bd68d | ||
|
|
126d60f8d7 | ||
|
|
4225a8fe1b | ||
|
|
a13a79f41f | ||
|
|
a828c823dd | ||
|
|
9cf2bac9c6 | ||
|
|
1926375d58 | ||
|
|
23dd88b7ba | ||
|
|
f164488e48 | ||
|
|
25b53794bb | ||
|
|
484fab85be | ||
|
|
6ed5de6400 | ||
|
|
8224f36567 | ||
|
|
2449b92bc8 | ||
|
|
03ec94d223 | ||
|
|
d26e6edd31 | ||
|
|
2ea24f2049 | ||
|
|
b11b6ef283 | ||
|
|
b1a04f8f44 | ||
|
|
0b5415dc47 | ||
|
|
59569806e6 | ||
|
|
4e1be6f4d5 | ||
|
|
cdf8fbb12a | ||
|
|
5d0f094da7 | ||
|
|
865d2216af | ||
|
|
95cb5251dc | ||
|
|
1d01e3ad2e | ||
|
|
c8fe7504c7 | ||
|
|
222efc57c2 | ||
|
|
735d5c659d | ||
|
|
50d188b737 | ||
|
|
af3ea0f702 | ||
|
|
8bf3b68cee | ||
|
|
2766bc7d34 | ||
|
|
1082384d00 | ||
|
|
25aab1576d | ||
|
|
87891b45ef | ||
|
|
b2cfdd8d78 | ||
|
|
540b7bcbea | ||
|
|
c6cb1d367d | ||
|
|
001900bd65 | ||
|
|
0204e0e24f | ||
|
|
2b3a2dd21c | ||
|
|
57acad4716 | ||
|
|
9f97c82a46 | ||
|
|
7cafddd345 | ||
|
|
c796697a8e | ||
|
|
ee523e9aac | ||
|
|
87fdc7bf74 | ||
|
|
fc41f30244 | ||
|
|
fbcd8c57c5 | ||
|
|
e7d84e9df2 | ||
|
|
b7642d1e0f | ||
|
|
05ffbdcc78 | ||
|
|
4b8d00ec1d | ||
|
|
d2dbf4f986 | ||
|
|
b0f2fe6a8e | ||
|
|
edfd59468f | ||
|
|
d7f04b61b5 | ||
|
|
573b50da68 | ||
|
|
2e0dfc5b02 | ||
|
|
18cdc39719 | ||
|
|
de951b1b45 | ||
|
|
b8cebe07fe | ||
|
|
14fa88da01 | ||
|
|
162814411f | ||
|
|
1bdb81f5f7 | ||
|
|
49e4c88d83 | ||
|
|
db81114a07 | ||
|
|
e019383187 | ||
|
|
4a09148b53 | ||
|
|
007f39f137 | ||
|
|
ab139daf02 | ||
|
|
24d83b70d2 | ||
|
|
307510e1ab | ||
|
|
9b7222961c | ||
|
|
3304398c4c | ||
|
|
074e59cee4 | ||
|
|
7ab8ab47ac | ||
|
|
9b01254b3c | ||
|
|
3a89ebff60 | ||
|
|
d0ee2a6564 | ||
|
|
257d4a3b0a | ||
|
|
53a67d7c76 | ||
|
|
40fe1f15a2 | ||
|
|
30a6e5bf2e | ||
|
|
f4babfcd24 | ||
|
|
794ea0cbb0 | ||
|
|
619285608d | ||
|
|
433d5d63b7 | ||
|
|
38109028d4 | ||
|
|
cf16a9dd59 | ||
|
|
4fcbeb30cf | ||
|
|
be8fbca9aa | ||
|
|
b393951968 | ||
|
|
6add565a98 | ||
|
|
1b0dd96b40 | ||
|
|
89f8718fe1 | ||
|
|
a5243970d5 | ||
|
|
7941c1981c | ||
|
|
f31e25b3b1 | ||
|
|
ae7e49e307 | ||
|
|
51c8287bbc | ||
|
|
942d6661f2 | ||
|
|
51ae598aac | ||
|
|
83229c9ab1 | ||
|
|
e62f933ca4 | ||
|
|
1f8e118965 | ||
|
|
e46e6dfc2a | ||
|
|
2c52372b0a | ||
|
|
50f8bf5ab1 | ||
|
|
5cf3d399f4 | ||
|
|
e9fc78186e | ||
|
|
a2b2faaa95 | ||
|
|
4c7208784f | ||
|
|
981569170c | ||
|
|
6bf666ac18 | ||
|
|
3a9cd3f39d | ||
|
|
45b62abcbe | ||
|
|
87740dbee3 | ||
|
|
a323acf7fc | ||
|
|
c23715a1ef | ||
|
|
896c1ad3a7 | ||
|
|
1856a1a4ef | ||
|
|
8bd09cddf0 | ||
|
|
52579673de | ||
|
|
31cd131993 | ||
|
|
9f94a4e06f | ||
|
|
e66c6b1e1b | ||
|
|
e97d0af941 | ||
|
|
65122d38d5 | ||
|
|
b626cbe217 | ||
|
|
4b443e65f6 | ||
|
|
14700d6bf0 | ||
|
|
a3c0fd3ccf | ||
|
|
a29265f17d | ||
|
|
0821ed021a | ||
|
|
ac4883db66 | ||
|
|
5706aa0052 | ||
|
|
f780e4f7ce | ||
|
|
e6d8ebb7b3 | ||
|
|
3663e04b34 | ||
|
|
c2e4085b50 | ||
|
|
33c881b12f | ||
|
|
c80e12d731 | ||
|
|
5446ef18e2 | ||
|
|
9c4d545915 | ||
|
|
f6d8cb6267 | ||
|
|
c898e02860 | ||
|
|
5477582a69 | ||
|
|
9cb22d810e | ||
|
|
cab6b6fa2a | ||
|
|
35bf954529 | ||
|
|
ceb38aa483 | ||
|
|
11ecad5ef2 | ||
|
|
8249d1427f | ||
|
|
94bcfc0543 | ||
|
|
b4d31c7c4b | ||
|
|
61bcd72a41 | ||
|
|
d7f7bb9a57 | ||
|
|
ee87e1dacf | ||
|
|
2f6c41c999 | ||
|
|
d0d4db1555 | ||
|
|
62cbb4b796 | ||
|
|
f040406002 | ||
|
|
bfff4ac440 | ||
|
|
a97386c37c | ||
|
|
18ae46207a | ||
|
|
bacddd2ad8 | ||
|
|
dd5794899d | ||
|
|
e5b730b2ef | ||
|
|
aea158de41 | ||
|
|
a933a0ffea | ||
|
|
7575424760 | ||
|
|
6c2eb40e6a | ||
|
|
f10f595fa4 | ||
|
|
6d1de42d76 | ||
|
|
5dc16c039c | ||
|
|
c416cdbeed | ||
|
|
18c7b22319 | ||
|
|
b5339046b9 | ||
|
|
e0ba222382 | ||
|
|
58b00e6442 | ||
|
|
9182dbfb5d | ||
|
|
27516844af | ||
|
|
99237445ac | ||
|
|
9349ca91d3 | ||
|
|
51c517145b | ||
|
|
c55f3d77bf | ||
|
|
95716c2e3e | ||
|
|
5f72519dc2 | ||
|
|
c24bba3137 | ||
|
|
01bb6fd0aa | ||
|
|
bf4add6b78 | ||
|
|
51a8964b89 | ||
|
|
7d986fe139 | ||
|
|
fffbfc21c2 | ||
|
|
6451bfbc42 | ||
|
|
5aa848de53 | ||
|
|
efc4ae31c9 | ||
|
|
0863238819 | ||
|
|
4f181e5eba | ||
|
|
2bf43ae9a1 | ||
|
|
d98d04d4ed | ||
|
|
602d6678bc | ||
|
|
8fd0c4e1f1 | ||
|
|
514fd79c3e | ||
|
|
95c25ac7b8 | ||
|
|
21d052e222 | ||
|
|
95e2c2db0d | ||
|
|
17a29b7b29 | ||
|
|
dd745423a1 | ||
|
|
a534e5f400 | ||
|
|
4dc7b5857e | ||
|
|
dc679c46cc | ||
|
|
8ccf6cc365 | ||
|
|
f8457ae66b | ||
|
|
12c8641f2e | ||
|
|
f42938f668 | ||
|
|
a856c5cbf7 | ||
|
|
6411e79904 | ||
|
|
946f08c479 | ||
|
|
4a198639ec | ||
|
|
234812bb40 | ||
|
|
dd43e31c3c | ||
|
|
2f5b0281c3 | ||
|
|
d4cf2a9d17 | ||
|
|
ecf5dcf2f2 | ||
|
|
848e8a5fa8 | ||
|
|
cc38383e32 | ||
|
|
39de4e5ea1 | ||
|
|
8b6c904dae | ||
|
|
a63f2d28f6 | ||
|
|
08812f169e | ||
|
|
ce79016bef | ||
|
|
fef960f7e8 | ||
|
|
425c9fb64b | ||
|
|
fc9a58c0c3 | ||
|
|
ed90ad34e6 | ||
|
|
5662094ec4 | ||
|
|
404c56e134 | ||
|
|
9ee614aa10 | ||
|
|
57789dc5a5 | ||
|
|
52d436909b | ||
|
|
3a760a66e1 | ||
|
|
72415d633c | ||
|
|
5c67a8c190 | ||
|
|
46ba682848 | ||
|
|
6b38062e87 | ||
|
|
0945754736 | ||
|
|
644ff967e5 | ||
|
|
5f79d2038c | ||
|
|
defd779279 | ||
|
|
e2e820267e | ||
|
|
94f179a6d6 | ||
|
|
bf9f4c1276 | ||
|
|
51a633594f | ||
|
|
7d7c7b0fcf | ||
|
|
d88ac22b7c | ||
|
|
1f4e1c11c8 | ||
|
|
9f1beb4013 | ||
|
|
f864466987 | ||
|
|
9cf70cc54c | ||
|
|
82bd50cb97 | ||
|
|
4bce26721d | ||
|
|
3fb4e7c413 | ||
|
|
a7ab8679f4 | ||
|
|
ca9a91e30a | ||
|
|
314d738412 | ||
|
|
699e03ccda | ||
|
|
8f0ade7b43 | ||
|
|
50bc8786e8 | ||
|
|
0777e63bc7 | ||
|
|
128bebf338 | ||
|
|
7a71077aa7 | ||
|
|
10a1d43a17 | ||
|
|
87d351e9e9 | ||
|
|
d2daae1a8f | ||
|
|
5997c24895 | ||
|
|
df53989f22 | ||
|
|
9bab687080 | ||
|
|
a5ac8b8b84 | ||
|
|
2cde398e11 | ||
|
|
88026fea5d | ||
|
|
443a21a0cc | ||
|
|
e14646a6fc | ||
|
|
6a920be6d1 | ||
|
|
3811079a7f | ||
|
|
ad8e0b6af0 | ||
|
|
04cb910803 | ||
|
|
42292818af | ||
|
|
bcafd9cf38 | ||
|
|
12ebd87f1d | ||
|
|
bdef852b98 | ||
|
|
1d5fb747d4 | ||
|
|
8116e4f97d | ||
|
|
e3e5fcc378 | ||
|
|
879b5492db | ||
|
|
27b9a4f982 | ||
|
|
94007bae2b | ||
|
|
bf92028027 | ||
|
|
2b47e566d3 | ||
|
|
5aa2f1aa18 | ||
|
|
b7128ba81a | ||
|
|
49546f9d08 | ||
|
|
6959bd9a09 | ||
|
|
3a2babf2d5 | ||
|
|
d7d094c84d | ||
|
|
a06e24583d | ||
|
|
0cbd830901 | ||
|
|
4b29def105 | ||
|
|
582abb3f2e | ||
|
|
40137ba69c | ||
|
|
804f6a82b4 | ||
|
|
d16d47dfbe | ||
|
|
41cf9d5474 | ||
|
|
59aa6b4f10 | ||
|
|
a759106fdc | ||
|
|
8bb101c6b2 | ||
|
|
f02044b513 | ||
|
|
3d937b85c9 | ||
|
|
5a61b3b459 | ||
|
|
afccb5ee6a | ||
|
|
2b0648d9bc | ||
|
|
8ea3487044 | ||
|
|
b24ed5fe4c | ||
|
|
b801b265c3 | ||
|
|
27a67167fe | ||
|
|
8fa9534b4e | ||
|
|
db745e46b6 | ||
|
|
f598b6c71c | ||
|
|
dccb0b3fb0 | ||
|
|
83feb78f43 | ||
|
|
ac09794b10 | ||
|
|
3f88b11a18 | ||
|
|
b97acfb181 | ||
|
|
6767b693f5 | ||
|
|
7b77d846c3 | ||
|
|
5e3bce1931 | ||
|
|
ef5c2649c9 | ||
|
|
5400c9ec69 | ||
|
|
7c7876e96f | ||
|
|
5438ff6df1 | ||
|
|
05b5e9cfd9 | ||
|
|
86dfd437cc | ||
|
|
3a92e4cfd8 | ||
|
|
bff0ff9401 | ||
|
|
9b3fe5c070 | ||
|
|
92d593bf24 | ||
|
|
dc6fb4ddda | ||
|
|
a84f8636a4 | ||
|
|
7c26045377 | ||
|
|
da579a15b3 | ||
|
|
c9ee0af25a | ||
|
|
151bc5da4f | ||
|
|
1015af483c | ||
|
|
bb3971c93e | ||
|
|
aa9efe932e | ||
|
|
8e09e9715c | ||
|
|
30a55658de | ||
|
|
7ecdc31db4 | ||
|
|
4234a6bbd9 | ||
|
|
8fcc436ce5 | ||
|
|
53a676bc93 | ||
|
|
c6f625fe44 | ||
|
|
397c35867b | ||
|
|
f843610872 | ||
|
|
41e4d2656a | ||
|
|
4272d2855f | ||
|
|
e63a12cbc1 | ||
|
|
2867264e88 | ||
|
|
7c2ddd8a0c | ||
|
|
4daddcff61 | ||
|
|
666c0167d7 | ||
|
|
6b7f1aedbe | ||
|
|
4234494f08 | ||
|
|
cbc73ee8e3 | ||
|
|
ec3d2a9239 | ||
|
|
a55da93dc4 | ||
|
|
73dce5e75e | ||
|
|
9b009ec5a3 | ||
|
|
7d70184a5d | ||
|
|
9f9c08b1f0 | ||
|
|
5886b4e132 | ||
|
|
77478fd173 | ||
|
|
2154533ad5 | ||
|
|
eab7bf04c3 | ||
|
|
ba910f011e | ||
|
|
c43d8ee280 | ||
|
|
acc04f396d | ||
|
|
3425376705 | ||
|
|
7849ce6342 | ||
|
|
69e8bf67d1 | ||
|
|
722bdc251d | ||
|
|
0403a2d223 | ||
|
|
7a4c3f155c | ||
|
|
acd8d9c758 | ||
|
|
d33a801f89 | ||
|
|
a74c07f0bb | ||
|
|
9f1cac0789 | ||
|
|
fcd0319a9c | ||
|
|
ed2c76adf1 | ||
|
|
1d45486e20 | ||
|
|
d209d40356 | ||
|
|
3355731632 | ||
|
|
ddd902cad8 | ||
|
|
e5566edd7c | ||
|
|
ea0f9d6ee2 | ||
|
|
79006e062a | ||
|
|
ee9783d4a6 | ||
|
|
ff7c4a9ce7 | ||
|
|
48b94e8d84 | ||
|
|
a395994a1c | ||
|
|
43c29e30b0 | ||
|
|
be639d7631 | ||
|
|
66c745af3c | ||
|
|
29ee275840 | ||
|
|
f92b844c16 | ||
|
|
63870ed7a9 | ||
|
|
558c99f48b | ||
|
|
1264a4305e | ||
|
|
a4f8183654 | ||
|
|
3ea8e9d35f | ||
|
|
3fb2890bf1 | ||
|
|
0c3ba5c172 | ||
|
|
1b22d02700 | ||
|
|
962931bc9d | ||
|
|
9cfc8418ec | ||
|
|
e4af2aacb8 | ||
|
|
c67944f689 | ||
|
|
9c713c2a37 | ||
|
|
53d4be596f | ||
|
|
195302b67f | ||
|
|
6cb5e909d4 | ||
|
|
cb98f5cf25 | ||
|
|
f87a6690fe | ||
|
|
b041feb297 | ||
|
|
06e9d5a538 | ||
|
|
b9aac6c558 | ||
|
|
2de0e9f02f | ||
|
|
722b601890 | ||
|
|
20722c12b8 | ||
|
|
2100ee9590 | ||
|
|
56f2ea0356 | ||
|
|
6ff8395916 | ||
|
|
bc85c457ad | ||
|
|
085e6359ce | ||
|
|
7eeea92a3e | ||
|
|
d672f3f4d7 | ||
|
|
c79cf2e5ad | ||
|
|
33a5fc0ff4 | ||
|
|
2dcaa25952 | ||
|
|
ba88070fad | ||
|
|
3f940992be | ||
|
|
197ae6bc01 | ||
|
|
07336326ce | ||
|
|
c93553e78e | ||
|
|
0db31f7936 | ||
|
|
61a3cf606e | ||
|
|
91d79008e1 | ||
|
|
8cdf77f609 | ||
|
|
37edc858c2 | ||
|
|
17fdf20e03 | ||
|
|
a896108638 | ||
|
|
026697f363 | ||
|
|
ec934abc42 | ||
|
|
cc030957fb | ||
|
|
87786dbd80 | ||
|
|
9d0ffd23b2 | ||
|
|
3fb56f15c8 | ||
|
|
bdf4696c08 | ||
|
|
6c926bf081 | ||
|
|
ddb0c304b9 | ||
|
|
cf8c14e738 | ||
|
|
6bd6816495 | ||
|
|
2bae98950e | ||
|
|
ec379a7541 | ||
|
|
119acf1543 | ||
|
|
0f6f1aae24 | ||
|
|
c40ecfb6ce | ||
|
|
a51a4c2fbb | ||
|
|
315e2aa03d | ||
|
|
10bea40159 | ||
|
|
78ba8d0ead | ||
|
|
e61742cc5b | ||
|
|
d0d1a640d9 | ||
|
|
e613a2f283 | ||
|
|
88e83c0e14 | ||
|
|
f912a81e7b | ||
|
|
e09226e966 | ||
|
|
8a7a90ff0d | ||
|
|
d05a3606c3 | ||
|
|
4b5c3b383b | ||
|
|
1c34f2f15c | ||
|
|
80f259651c | ||
|
|
1c57ee0be1 | ||
|
|
c732bbad62 | ||
|
|
5285fcd066 | ||
|
|
d318d8cb32 | ||
|
|
5b9dc05653 | ||
|
|
a513592b21 | ||
|
|
e686ea9469 | ||
|
|
fd5ff0904e | ||
|
|
651918c51c | ||
|
|
7b13dfcc0e | ||
|
|
60e139d5e0 | ||
|
|
8d5ea0ae19 | ||
|
|
d7597359ba | ||
|
|
f8309bfaba | ||
|
|
7ed756f922 | ||
|
|
e837b84a50 | ||
|
|
e7cb100ab1 | ||
|
|
d00c9dd38c | ||
|
|
1d857be16a | ||
|
|
5914e4ca6e | ||
|
|
d96970fb96 | ||
|
|
0af9064fce | ||
|
|
21148ae74b | ||
|
|
1a3590b6b6 | ||
|
|
b55c6c1fe7 | ||
|
|
8c61dcac29 | ||
|
|
41d4e9785f | ||
|
|
5bf86930e9 | ||
|
|
7ad9885377 | ||
|
|
71433c18b7 | ||
|
|
4b60ad307b | ||
|
|
1a5c8e5f56 | ||
|
|
8b1e12eb9f | ||
|
|
dbf2257a88 | ||
|
|
f34c01d1b0 | ||
|
|
9c73062593 | ||
|
|
878fef18df | ||
|
|
8348c4fc48 | ||
|
|
8643fb2385 | ||
|
|
77744e75d7 | ||
|
|
4cbcf08da8 | ||
|
|
af2a589679 | ||
|
|
4b6f04aa9d | ||
|
|
4634d43012 | ||
|
|
dd6e8b6707 | ||
|
|
55337815c7 | ||
|
|
0ccb7cb3d1 | ||
|
|
d2155b17b4 | ||
|
|
11a5217430 | ||
|
|
855e0eb1c2 | ||
|
|
6312bb988a | ||
|
|
fe9548a0d1 | ||
|
|
0347eb48e4 | ||
|
|
0aae15cbce | ||
|
|
5a7d70fb6b | ||
|
|
8ea8bf1f48 | ||
|
|
df78019226 | ||
|
|
6ad945b720 | ||
|
|
3285d4da57 | ||
|
|
60539462a0 | ||
|
|
81b425ae0c | ||
|
|
33fabc068a | ||
|
|
db4dd33709 | ||
|
|
4acb0fa0ba | ||
|
|
c5ad94c5f3 | ||
|
|
43c6686240 | ||
|
|
5c35eae859 | ||
|
|
fde28bed8a | ||
|
|
16ae845ad9 | ||
|
|
e10648ad07 | ||
|
|
d2b02bb6f7 | ||
|
|
0e1c44c3ac | ||
|
|
5ffbe4c6fc | ||
|
|
5ae167c133 | ||
|
|
0702e1dd51 | ||
|
|
e861897527 | ||
|
|
3bb909c2ee | ||
|
|
da19b86bf5 | ||
|
|
dc318769af | ||
|
|
311b95bca5 | ||
|
|
80118b61c2 | ||
|
|
94d0c14e1f | ||
|
|
3f6f9bb709 | ||
|
|
49a1ec353a | ||
|
|
b89cfb899b | ||
|
|
0ff0cc5fbd | ||
|
|
37173968fd | ||
|
|
470482b7e2 | ||
|
|
5b51cc3de4 | ||
|
|
9e939467e5 | ||
|
|
b05492615f | ||
|
|
32ed0e9693 | ||
|
|
623ebe3ec3 | ||
|
|
da06688571 | ||
|
|
c01451336d | ||
|
|
2db5068e85 | ||
|
|
c955953335 | ||
|
|
9220c4b7c4 | ||
|
|
70b21c607e | ||
|
|
4fe7300ec0 | ||
|
|
282458c945 | ||
|
|
f6e87493d3 | ||
|
|
e141f0af2c | ||
|
|
4618c83b2f | ||
|
|
e89400f04e | ||
|
|
051f8128e9 | ||
|
|
8f61f258b1 | ||
|
|
a864845d2b | ||
|
|
a6a2e53111 | ||
|
|
a11329eaf8 | ||
|
|
d571b77915 | ||
|
|
24897f07f8 | ||
|
|
53779b89a7 | ||
|
|
a1a94867f0 | ||
|
|
0a666f568d | ||
|
|
ad8ec770b6 | ||
|
|
3cc3c48ab3 | ||
|
|
f5da36a82c | ||
|
|
da489555b0 | ||
|
|
29337bfceb | ||
|
|
ef0d05a29d | ||
|
|
b8bec37e15 | ||
|
|
dedc820b50 | ||
|
|
2ccedc95f2 | ||
|
|
b276f616e5 | ||
|
|
cb645aa1b9 | ||
|
|
2409dab8b0 | ||
|
|
e8fc38e6ed | ||
|
|
3d355ec303 | ||
|
|
52e585e637 | ||
|
|
05dac9fb0c | ||
|
|
d3a1e9b341 | ||
|
|
8db80e1ed6 | ||
|
|
fc378df597 | ||
|
|
0575d89227 | ||
|
|
39afc99b8f | ||
|
|
f230ad55fb |
2
jikimo_demand_plan_queue/__init__.py
Normal file
2
jikimo_demand_plan_queue/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from . import models
|
||||
18
jikimo_demand_plan_queue/__manifest__.py
Normal file
18
jikimo_demand_plan_queue/__manifest__.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
{
|
||||
'name': '机企猫 需求计划排程队列',
|
||||
'version': '1.0',
|
||||
'summary': """ 使用队列进行排程 """,
|
||||
'author': 'fox',
|
||||
'website': '',
|
||||
'category': '',
|
||||
'depends': ['queue_job_batch', 'sf_demand_plan'],
|
||||
'data': [
|
||||
|
||||
],
|
||||
|
||||
'application': True,
|
||||
'installable': True,
|
||||
'auto_install': False,
|
||||
'license': 'LGPL-3',
|
||||
}
|
||||
2
jikimo_demand_plan_queue/models/__init__.py
Normal file
2
jikimo_demand_plan_queue/models/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from . import production_demand_plan
|
||||
20
jikimo_demand_plan_queue/models/production_demand_plan.py
Normal file
20
jikimo_demand_plan_queue/models/production_demand_plan.py
Normal file
@@ -0,0 +1,20 @@
|
||||
from odoo import models, fields
|
||||
|
||||
|
||||
class ProductionDemandPlan(models.Model):
|
||||
_inherit = 'sf.production.demand.plan'
|
||||
|
||||
|
||||
def _do_production_schedule(self, pro_plan_list):
|
||||
"""使用队列进行排程"""
|
||||
batch_size = 10
|
||||
current_time = fields.Datetime.now().strftime('%Y%m%d%H%M%S')
|
||||
index = 1
|
||||
for i in range(0, len(pro_plan_list), batch_size):
|
||||
batch = self.env['queue.job.batch'].get_new_batch('plan-%s-%s' % (current_time, index))
|
||||
pro_plans = pro_plan_list[i:i+batch_size]
|
||||
pro_plans.with_context(
|
||||
job_batch=batch
|
||||
).with_delay().do_production_schedule()
|
||||
index += 1
|
||||
batch.enqueue()
|
||||
@@ -119,7 +119,9 @@ patch(ListRenderer.prototype, 'jikimo_frontend.ListRenderer', {
|
||||
this.listherHeaderBodyNum()
|
||||
})
|
||||
const treeModifiers = this.getFieldModifiers(this.props.archInfo.__rawArch);
|
||||
|
||||
if(treeModifiers) {
|
||||
if(treeModifiers.merge_fields) {
|
||||
this.props.merge_key = treeModifiers.merge_key;
|
||||
this.props.merge_fields = treeModifiers.merge_fields.split(',');
|
||||
const data = this.setColumns(this.props.merge_key);
|
||||
@@ -127,6 +129,15 @@ patch(ListRenderer.prototype, 'jikimo_frontend.ListRenderer', {
|
||||
this.mergeColumns(this.props.merge_fields, data)
|
||||
})
|
||||
}
|
||||
if(treeModifiers.pacthResize) {
|
||||
|
||||
owl.onPatched(() => {
|
||||
this.columnWidths = null;
|
||||
this.freezeColumnWidths();
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
return this._super(...arguments);
|
||||
},
|
||||
setRequired() {
|
||||
|
||||
@@ -157,11 +157,11 @@ td.o_required_modifier {
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.o_kanban_primary_left {
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
// .o_kanban_primary_left {
|
||||
// display: flex;
|
||||
// flex-direction: row-reverse;
|
||||
// justify-content: flex-start;
|
||||
// }
|
||||
|
||||
.o_list_view .o_list_table thead {
|
||||
position: sticky;
|
||||
@@ -521,11 +521,6 @@ div:has(.o_required_modifier) > label::before {
|
||||
}
|
||||
}
|
||||
|
||||
// 设置表格横向滚动
|
||||
.o_list_renderer.o_renderer {
|
||||
max-width: 100%;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
// 设置表单页面label文本不换行
|
||||
.o_form_view .o_group .o_wrap_label .o_form_label {
|
||||
|
||||
2
jikimo_printing/__init__.py
Normal file
2
jikimo_printing/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from . import models
|
||||
18
jikimo_printing/__manifest__.py
Normal file
18
jikimo_printing/__manifest__.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
{
|
||||
'name': '机企猫 打印模块',
|
||||
'version': '1.0',
|
||||
'summary': """ 包含机台二维码,程序单打印等 """,
|
||||
'author': '机企猫',
|
||||
'website': 'https://www.jikimo.com',
|
||||
'category': '机企猫',
|
||||
'depends': ['sf_manufacturing', 'sf_maintenance', 'base_report_to_printer'],
|
||||
'data': [
|
||||
'views/maintenance_views.xml',
|
||||
],
|
||||
|
||||
'application': True,
|
||||
'installable': True,
|
||||
'auto_install': False,
|
||||
'license': 'LGPL-3',
|
||||
}
|
||||
5
jikimo_printing/models/__init__.py
Normal file
5
jikimo_printing/models/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from . import jikimo_printing
|
||||
from . import maintenance_printing
|
||||
from . import workorder_printing
|
||||
|
||||
87
jikimo_printing/models/jikimo_printing.py
Normal file
87
jikimo_printing/models/jikimo_printing.py
Normal file
@@ -0,0 +1,87 @@
|
||||
from io import BytesIO
|
||||
import qrcode
|
||||
from reportlab.pdfgen import canvas
|
||||
from reportlab.lib.pagesizes import A4
|
||||
from PIL import Image
|
||||
import logging
|
||||
from reportlab.lib.utils import ImageReader
|
||||
from odoo import models, fields, api
|
||||
import base64
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class JikimoPrinting(models.AbstractModel):
|
||||
_name = 'jikimo.printing'
|
||||
|
||||
def print_qr_code(self, data):
|
||||
"""
|
||||
打印二维码
|
||||
"""
|
||||
printer = self.env['printing.printer'].get_default()
|
||||
if not printer:
|
||||
_logger.error("未找到默认打印机")
|
||||
return False
|
||||
|
||||
# 生成二维码
|
||||
qr = qrcode.QRCode(version=1, box_size=10, border=5)
|
||||
qr.add_data(data)
|
||||
qr.make(fit=True)
|
||||
qr_image = qr.make_image(fill_color="black", back_color="white")
|
||||
|
||||
# 将PIL Image转换为reportlab可用的格式
|
||||
temp_image = BytesIO()
|
||||
qr_image.save(temp_image, format="PNG")
|
||||
temp_image.seek(0)
|
||||
|
||||
# 创建PDF
|
||||
pdf_buffer = BytesIO()
|
||||
c = canvas.Canvas(pdf_buffer, pagesize=A4)
|
||||
|
||||
# 计算位置
|
||||
a4_width, a4_height = A4
|
||||
qr_width = 200
|
||||
qr_height = 200
|
||||
x = (a4_width - qr_width) / 2
|
||||
y = (a4_height - qr_height) / 2
|
||||
|
||||
# 直接从BytesIO绘制图片
|
||||
c.drawImage(ImageReader(Image.open(temp_image)), x, y, width=qr_width, height=qr_height)
|
||||
c.save()
|
||||
|
||||
# 获取PDF内容并打印
|
||||
pdf_content = pdf_buffer.getvalue()
|
||||
# _logger.info(f"打印内容: {pdf_content}")
|
||||
printer.print_document(report=None, content=pdf_content, doc_format='pdf')
|
||||
|
||||
# 清理资源
|
||||
pdf_buffer.close()
|
||||
temp_image.close()
|
||||
|
||||
return True
|
||||
|
||||
def print_pdf(self, pdf_data):
|
||||
"""
|
||||
打印PDF
|
||||
"""
|
||||
printer = self.env['printing.printer'].get_default()
|
||||
if not printer:
|
||||
_logger.error("未找到默认打印机")
|
||||
return False
|
||||
|
||||
pdf_data_str = pdf_data.decode('ascii', errors='ignore')
|
||||
decoded_data = base64.b64decode(pdf_data_str)
|
||||
|
||||
# 处理二进制数据
|
||||
pdf_buffer = BytesIO()
|
||||
pdf_buffer.write(decoded_data)
|
||||
pdf_buffer.seek(0)
|
||||
|
||||
# 获取PDF内容
|
||||
pdf_content = pdf_buffer.getvalue()
|
||||
|
||||
printer.print_document(report=None, content=pdf_content, doc_format='pdf')
|
||||
# 清理资源
|
||||
pdf_buffer.close()
|
||||
|
||||
_logger.info("成功打印PDF")
|
||||
return True
|
||||
69
jikimo_printing/models/maintenance_printing.py
Normal file
69
jikimo_printing/models/maintenance_printing.py
Normal file
@@ -0,0 +1,69 @@
|
||||
from odoo import models, fields, api
|
||||
|
||||
class MaintenancePrinting(models.Model):
|
||||
_inherit = 'maintenance.equipment'
|
||||
|
||||
def print_single_method(self):
|
||||
|
||||
print('self.name========== %s' % self.name)
|
||||
self.ensure_one()
|
||||
# maintenance_equipment_id = self.id
|
||||
# # host = "192.168.50.110" # 可以根据实际情况修改
|
||||
# # port = 9100 # 可以根据实际情况修改
|
||||
|
||||
# # 获取默认打印机配置
|
||||
# printer_config = self.env['printer.configuration'].sudo().search([('model', '=', self._name)], limit=1)
|
||||
# if not printer_config:
|
||||
# raise UserError('请先配置打印机')
|
||||
# host = printer_config.printer_id.ip_address
|
||||
# port = printer_config.printer_id.port
|
||||
# self.print_qr_code(maintenance_equipment_id, host, port)
|
||||
|
||||
# 切换成A4打印机
|
||||
|
||||
try:
|
||||
self.env['jikimo.printing'].print_qr_code(self.MTcode)
|
||||
except Exception as e:
|
||||
raise UserError(f"打印失败: {str(e)}")
|
||||
|
||||
|
||||
# def generate_zpl_code(self, code):
|
||||
# """生成ZPL代码用于打印二维码标签
|
||||
# Args:
|
||||
# code: 需要编码的内容
|
||||
# Returns:
|
||||
# str: ZPL指令字符串
|
||||
# """
|
||||
# zpl_code = "^XA\n" # 开始ZPL格式
|
||||
|
||||
# # 设置打印参数
|
||||
# zpl_code += "^LH0,0\n" # 设置标签起始位置
|
||||
# zpl_code += "^CI28\n" # 设置中文编码
|
||||
# zpl_code += "^PW400\n" # 设置打印宽度为400点
|
||||
# zpl_code += "^LL300\n" # 设置标签长度为300点
|
||||
|
||||
# # 打印标题
|
||||
# zpl_code += "^FO10,20\n" # 设置标题位置
|
||||
# zpl_code += "^A0N,30,30\n" # 设置字体大小
|
||||
# zpl_code += "^FD机床二维码^FS\n" # 打印标题文本
|
||||
|
||||
# # 打印二维码
|
||||
# zpl_code += "^FO50,60\n" # 设置二维码位置
|
||||
# zpl_code += f"^BQN,2,8\n" # 设置二维码参数:模式2,放大倍数8
|
||||
# zpl_code += f"^FDLA,{code}^FS\n" # 二维码内容
|
||||
|
||||
# # 打印编码文本
|
||||
# zpl_code += "^FO50,220\n" # 设置编码文本位置
|
||||
# zpl_code += "^A0N,25,25\n" # 设置字体大小
|
||||
# zpl_code += f"^FD编码: {code}^FS\n" # 打印编码文本
|
||||
|
||||
# # 打印日期
|
||||
# zpl_code += "^FO50,260\n"
|
||||
# zpl_code += "^A0N,20,20\n"
|
||||
# zpl_code += f"^FD打印日期: {fields.Date.today()}^FS\n"
|
||||
|
||||
# zpl_code += "^PQ1\n" # 打印1份
|
||||
# zpl_code += "^XZ\n" # 结束ZPL格式
|
||||
|
||||
# return zpl_code
|
||||
|
||||
31
jikimo_printing/models/workorder_printing.py
Normal file
31
jikimo_printing/models/workorder_printing.py
Normal file
@@ -0,0 +1,31 @@
|
||||
|
||||
import logging
|
||||
from odoo import models, fields, api
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class MrpWorkorder(models.Model):
|
||||
_name = 'mrp.workorder'
|
||||
_inherit = ['mrp.workorder']
|
||||
|
||||
def _compute_state(self):
|
||||
super(MrpWorkorder, self)._compute_state()
|
||||
for workorder in self:
|
||||
work_ids = workorder.production_id.workorder_ids.filtered(lambda w: w.routing_type == '装夹预调' or w.routing_type == '人工线下加工')
|
||||
for wo in work_ids:
|
||||
if wo.state == 'ready' and not wo.production_id.product_id.is_print_program:
|
||||
# 触发打印程序
|
||||
pdf_data = workorder.processing_drawing
|
||||
if pdf_data:
|
||||
try:
|
||||
# 执行打印
|
||||
self.env['jikimo.printing'].print_pdf(pdf_data)
|
||||
wo.production_id.product_id.is_print_program = True
|
||||
except Exception as e:
|
||||
_logger.error(f"工单 {wo.name} 的PDF打印失败: {str(e)}")
|
||||
|
||||
class ProductTemplate(models.Model):
|
||||
_inherit = 'product.template'
|
||||
|
||||
is_print_program = fields.Boolean(string='是否打印程序', default=False)
|
||||
|
||||
19
jikimo_printing/views/maintenance_views.xml
Normal file
19
jikimo_printing/views/maintenance_views.xml
Normal file
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0"?>
|
||||
<odoo>
|
||||
<record id="sf_maintenance_equipment_view_form_qrcode_print" model="ir.ui.view">
|
||||
<field name="name">sf_equipment.form</field>
|
||||
<field name="model">maintenance.equipment</field>
|
||||
<field name="inherit_id" ref="sf_maintenance.sf_hr_equipment_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='qr_code_image']" position="after">
|
||||
<label for="print_single_method"/>
|
||||
<div class="col-12 col-lg-6 o_setting_box" style="white-space: nowrap">
|
||||
<button type="object" class="oe_highlight" name='print_single_method' string="打印机床二维码"
|
||||
attrs="{'invisible': [('equipment_type', '!=', '机床')]}"/>
|
||||
</div>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
||||
|
||||
@@ -8,16 +8,21 @@
|
||||
'category': 'purchase',
|
||||
'depends': ['sf_manufacturing', 'purchase_request'],
|
||||
'data': [
|
||||
'security/ir.model.access.csv',
|
||||
'views/sale_order_view.xml',
|
||||
'views/mrp_production.xml',
|
||||
'views/purchase_request_view.xml',
|
||||
'wizard/purchase_request_line_make_purchase_order_view.xml',
|
||||
'views/purchase_request_line_view.xml',
|
||||
'views/stock_picking_views.xml',
|
||||
'wizard/purchase_request_wizard_views.xml',
|
||||
'views/purchase_request_menu_views.xml',
|
||||
],
|
||||
# 'assets': {
|
||||
# 'web.assets_backend': [
|
||||
# 'jikimo_purchase_request/static/src/**/*'
|
||||
# ],
|
||||
# },
|
||||
'assets': {
|
||||
'web.assets_backend': [
|
||||
'jikimo_purchase_request/static/src/**/*'
|
||||
],
|
||||
},
|
||||
'application': True,
|
||||
'installable': True,
|
||||
'auto_install': False,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * purchase_request
|
||||
# * jikimo_purchase_request
|
||||
#
|
||||
# Translators:
|
||||
# Jeffery Chen Fan <jeffery9@gmail.com>, 2016
|
||||
@@ -410,7 +410,7 @@ msgstr "显示名称"
|
||||
#: model_terms:ir.ui.view,arch_db:purchase_request.view_purchase_request_form
|
||||
#: model_terms:ir.ui.view,arch_db:purchase_request.view_purchase_request_search
|
||||
msgid "Done"
|
||||
msgstr "完成"
|
||||
msgstr "关闭"
|
||||
|
||||
#. module: purchase_request
|
||||
#: model:ir.model.fields,field_description:purchase_request.field_purchase_request_line__move_dest_ids
|
||||
@@ -614,7 +614,7 @@ msgstr "手动开票"
|
||||
#. module: purchase_request
|
||||
#: model:ir.model.fields,field_description:purchase_request.field_purchase_request_line_make_purchase_order__sync_data_planned
|
||||
msgid "Match existing PO lines by Scheduled Date"
|
||||
msgstr ""
|
||||
msgstr "仅匹配计划日期相同的采购订单行"
|
||||
|
||||
#. module: purchase_request
|
||||
#: model:ir.model.fields,field_description:purchase_request.field_purchase_request__message_has_error
|
||||
@@ -862,7 +862,7 @@ msgstr "采购申请 %s 已完成"
|
||||
#: code:addons/purchase_request/wizard/purchase_request_line_make_purchase_order.py:0
|
||||
#, python-format
|
||||
msgid "Purchase Request %s is not approved or in progress"
|
||||
msgstr "采购申请 %s 未获批准或在进行中"
|
||||
msgstr "采购申请 %s 状态非已批准或进行中"
|
||||
|
||||
#. module: purchase_request
|
||||
#: model:ir.model,name:purchase_request.model_purchase_request_allocation
|
||||
@@ -1043,7 +1043,7 @@ msgstr "询价单"
|
||||
#. module: purchase_request
|
||||
#: model:ir.model.fields,field_description:purchase_request.field_purchase_request_line__purchased_qty
|
||||
msgid "RFQ/PO Qty"
|
||||
msgstr ""
|
||||
msgstr "已订购数"
|
||||
|
||||
#. module: purchase_request
|
||||
#. odoo-python
|
||||
|
||||
@@ -5,3 +5,5 @@ from . import sale_order
|
||||
from . import mrp_production
|
||||
from . import purchase_order
|
||||
from . import stock_rule
|
||||
from . import stock_picking
|
||||
from . import product_product
|
||||
|
||||
@@ -9,18 +9,22 @@ class MrpProduction(models.Model):
|
||||
@api.depends('state')
|
||||
def _compute_pr_mp_count(self):
|
||||
for item in self:
|
||||
pr_ids = self.env['purchase.request'].sudo().search([('origin', 'like', item.name)])
|
||||
if pr_ids:
|
||||
item.pr_mp_count = len(pr_ids)
|
||||
else:
|
||||
if item.product_id.is_customer_provided:
|
||||
item.pr_mp_count = 0
|
||||
else:
|
||||
pr_ids = item._get_purchase_request()
|
||||
item.pr_mp_count = len(pr_ids)
|
||||
# pr_ids = self.env['purchase.request'].sudo().search([('origin', 'like', item.name), ('is_subcontract', '!=', 'True')])
|
||||
|
||||
def action_view_pr_mp(self):
|
||||
"""
|
||||
采购请求
|
||||
"""
|
||||
self.ensure_one()
|
||||
pr_ids = self.env['purchase.request'].sudo().search([('origin', 'like', self.name)])
|
||||
|
||||
# 由于采购申请合并了所有销售订单行的采购,所以不区分产品
|
||||
pr_ids = self._get_purchase_request()
|
||||
|
||||
action = {
|
||||
'res_model': 'purchase.request',
|
||||
'type': 'ir.actions.act_window',
|
||||
@@ -33,7 +37,16 @@ class MrpProduction(models.Model):
|
||||
else:
|
||||
action.update({
|
||||
'name': _("从 %s生成采购请求单", self.name),
|
||||
'domain': [('id', 'in', pr_ids)],
|
||||
'domain': [('id', 'in', pr_ids.ids)],
|
||||
'view_mode': 'tree,form',
|
||||
})
|
||||
return action
|
||||
|
||||
def _get_purchase_request(self):
|
||||
"""获取跟制造订单相关的采购申请单(根据采购申请单行项目的产品匹配)"""
|
||||
mrp_names = self.env['mrp.production'].search([('origin', '=', self.origin)]).mapped('name')
|
||||
pr_ids = self.env['purchase.request'].sudo().search([('origin', 'in', mrp_names)])
|
||||
product_list = self.product_id._get_product_include_bom()
|
||||
pr_line_ids = pr_ids.line_ids.filtered(lambda l: l.product_id in product_list)
|
||||
return pr_line_ids.mapped('request_id')
|
||||
|
||||
17
jikimo_purchase_request/models/product_product.py
Normal file
17
jikimo_purchase_request/models/product_product.py
Normal file
@@ -0,0 +1,17 @@
|
||||
from odoo import models
|
||||
|
||||
|
||||
class ProductProduct(models.Model):
|
||||
_inherit = 'product.product'
|
||||
|
||||
|
||||
def _get_product_include_bom(self):
|
||||
"""获取产品列表(包括所有bom)"""
|
||||
self.ensure_one()
|
||||
product_list = [self]
|
||||
bom_ids = self.bom_ids
|
||||
while (bom_ids):
|
||||
bom_product_ids = bom_ids.bom_line_ids.mapped('product_id')
|
||||
product_list.append(bom_product_ids)
|
||||
bom_ids = bom_product_ids.bom_ids
|
||||
return product_list
|
||||
@@ -1,4 +1,5 @@
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.tools import float_compare
|
||||
|
||||
|
||||
class PurchaseOrder(models.Model):
|
||||
@@ -14,3 +15,42 @@ class PurchaseOrder(models.Model):
|
||||
('cancel', '取消'),
|
||||
('rejected', '已驳回')
|
||||
], string='Status', readonly=True, index=True, copy=False, default='draft', tracking=True)
|
||||
|
||||
|
||||
def button_confirm(self):
|
||||
res = super(PurchaseOrder, self).button_confirm()
|
||||
# 取消反向调拨单
|
||||
reverse_move_ids = self.env['stock.move'].search([
|
||||
('origin', '=', self.name),
|
||||
('purchase_line_id', '=', False),
|
||||
('state', '!=', 'done')
|
||||
])
|
||||
if reverse_move_ids:
|
||||
reverse_move_ids.picking_id.action_cancel()
|
||||
return res
|
||||
|
||||
def button_cancel(self):
|
||||
"""
|
||||
将取消的采购订单关联的库存移动撤销
|
||||
"""
|
||||
move_ids = self.order_line.move_dest_ids.filtered(lambda move: move.state != 'done' and not move.scrapped)
|
||||
res =super(PurchaseOrder, self).button_cancel()
|
||||
if move_ids.mapped('created_purchase_request_line_id'):
|
||||
move_ids.write({'state': 'waiting', 'is_done': False})
|
||||
return res
|
||||
|
||||
def write(self, vals):
|
||||
res = super(PurchaseOrder, self).write(vals)
|
||||
if 'state' in vals and vals['state'] == 'purchase':
|
||||
purchase_request = self.order_line.purchase_request_lines.request_id
|
||||
if purchase_request:
|
||||
finished = True
|
||||
# 判断该采购申请所有明细行是否都完成
|
||||
for purchase_request_line in purchase_request.line_ids:
|
||||
finished_qty = sum(purchase_request_line.purchase_lines.filtered(lambda line: line.state == 'purchase').mapped('product_qty'))
|
||||
if float_compare(finished_qty ,purchase_request_line.product_qty, precision_rounding=purchase_request_line.product_id.uom_id.rounding) < 0:
|
||||
finished = False
|
||||
break
|
||||
if finished:
|
||||
purchase_request.button_done()
|
||||
return res
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import re
|
||||
import ast
|
||||
from odoo import models, fields, api
|
||||
from odoo import models, fields, api, _
|
||||
from itertools import groupby
|
||||
from odoo.tools import float_compare
|
||||
|
||||
|
||||
class PurchaseRequest(models.Model):
|
||||
@@ -29,6 +31,57 @@ class PurchaseRequest(models.Model):
|
||||
action['context'] = origin_context
|
||||
return action
|
||||
|
||||
def button_done(self):
|
||||
product_qty_map = {key: sum(line.product_qty for line in group) for key, group in
|
||||
groupby(self.line_ids, key=lambda x: x.product_id.id)}
|
||||
lines = self.mapped("line_ids.purchase_lines.order_id")
|
||||
# 采购单产品和数量
|
||||
product_summary = {}
|
||||
product_rounding = {}
|
||||
if lines:
|
||||
for line in lines:
|
||||
for line_item in line.order_line:
|
||||
if line_item.state == 'purchase':
|
||||
product_id = line_item.product_id.id
|
||||
qty = line_item.product_qty
|
||||
product_rounding[product_id] = line_item.product_id.uom_id.rounding
|
||||
if product_id in product_summary:
|
||||
product_summary[product_id] += qty
|
||||
else:
|
||||
product_summary[product_id] = qty
|
||||
|
||||
# 校验产品数量
|
||||
discrepancies = []
|
||||
for product_id, qty in product_qty_map.items():
|
||||
if product_id in product_summary:
|
||||
if float_compare(product_summary[product_id], qty, precision_rounding=product_rounding[product_id]) < 0:
|
||||
discrepancies.append((product_id, qty, product_summary[product_id]))
|
||||
else:
|
||||
discrepancies.append((product_id, qty, 0))
|
||||
|
||||
if discrepancies:
|
||||
# 弹出提示框
|
||||
message = "产品与采购数量不一致:\n"
|
||||
for product_id, required_qty, order_qty in discrepancies:
|
||||
product_name = self.env['product.product'].browse(product_id).display_name # 获取产品名称
|
||||
message += f"产品 {product_name},需求数量 {required_qty},关联采购订单确认的数量 {order_qty}。\n"
|
||||
# 添加确认框
|
||||
message += "确认关闭?"
|
||||
return {
|
||||
'name': _('采购申请'),
|
||||
'type': 'ir.actions.act_window',
|
||||
'views': [(self.env.ref(
|
||||
'jikimo_purchase_request.purchase_request_wizard_wizard_form_view').id,
|
||||
'form')],
|
||||
'res_model': 'purchase.request.wizard',
|
||||
'target': 'new',
|
||||
'context': {
|
||||
'default_purchase_request_id': self.id,
|
||||
'default_message': message,
|
||||
}}
|
||||
return super(PurchaseRequest, self).button_done()
|
||||
|
||||
|
||||
class PurchaseRequestLine(models.Model):
|
||||
_inherit = 'purchase.request.line'
|
||||
_description = '采购申请明细'
|
||||
@@ -47,6 +100,20 @@ class PurchaseRequestLine(models.Model):
|
||||
('outsourcing', "委外加工"),
|
||||
], string='供货方式', compute='_compute_supply_method', store=True)
|
||||
|
||||
purchase_request_count = fields.Integer(string='采购申请数量', compute='_compute_purchase_request_count',
|
||||
readonly=True)
|
||||
purchase_count = fields.Integer(string="采购订单数量", compute="_compute_purchase_count", readonly=True)
|
||||
|
||||
@api.depends("purchase_lines")
|
||||
def _compute_purchase_count(self):
|
||||
for rec in self:
|
||||
rec.purchase_count = len(rec.mapped("purchase_lines.order_id"))
|
||||
|
||||
@api.depends('request_id')
|
||||
def _compute_purchase_request_count(self):
|
||||
for order in self:
|
||||
order.purchase_request_count = len(order.request_id)
|
||||
|
||||
@api.depends('origin')
|
||||
def _compute_supply_method(self):
|
||||
for prl in self:
|
||||
@@ -79,10 +146,12 @@ class PurchaseRequestLine(models.Model):
|
||||
continue
|
||||
if record.product_id.categ_id.name == '坯料':
|
||||
product_name = ''
|
||||
match = re.search(r'(S\d{5}-\d)', record.product_id.name)
|
||||
match = re.search(r'(S\d{5}-\d+)', record.product_id.name)
|
||||
# 如果匹配成功,提取结果
|
||||
if match:
|
||||
product_name = match.group(0)
|
||||
else:
|
||||
product_name = record.product_id.name
|
||||
sale_order_name = ''
|
||||
match_sale = re.search(r'S(\d+)', record.product_id.name)
|
||||
if match_sale:
|
||||
@@ -98,3 +167,36 @@ class PurchaseRequestLine(models.Model):
|
||||
else:
|
||||
record.part_number = record.product_id.part_number
|
||||
record.part_name = record.product_id.part_name
|
||||
|
||||
def _compute_qty_to_buy(self):
|
||||
for pr in self:
|
||||
qty_to_buy = sum(pr.mapped("product_qty"))
|
||||
if pr.purchase_count > 0:
|
||||
qty_to_buy -= sum(pr.mapped("purchase_lines").filtered(lambda po: po.state != 'cancel').mapped(
|
||||
"product_qty"))
|
||||
pr.qty_to_buy = qty_to_buy > 0.0
|
||||
pr.pending_qty_to_receive = qty_to_buy
|
||||
|
||||
def action_view_purchase_request(self):
|
||||
action = self.env["ir.actions.actions"]._for_xml_id("purchase_request.purchase_request_form_action")
|
||||
action.update({
|
||||
'res_id': self.request_id.id,
|
||||
'views': [[False, 'form']],
|
||||
})
|
||||
return action
|
||||
|
||||
def action_view_purchase_order(self):
|
||||
action = self.env["ir.actions.actions"]._for_xml_id("purchase.purchase_rfq")
|
||||
lines = self.mapped("purchase_lines.order_id")
|
||||
if len(lines) > 1:
|
||||
action["domain"] = [("id", "in", lines.ids)]
|
||||
elif lines:
|
||||
action["views"] = [
|
||||
(self.env.ref("purchase.purchase_order_form").id, "form")
|
||||
]
|
||||
action["res_id"] = lines.id
|
||||
origin_context = ast.literal_eval(action['context'])
|
||||
if 'search_default_draft' in origin_context:
|
||||
origin_context.pop('search_default_draft')
|
||||
action['context'] = origin_context
|
||||
return action
|
||||
|
||||
47
jikimo_purchase_request/models/stock_picking.py
Normal file
47
jikimo_purchase_request/models/stock_picking.py
Normal file
@@ -0,0 +1,47 @@
|
||||
from odoo import fields, api, models, _
|
||||
|
||||
|
||||
class StockPicking(models.Model):
|
||||
_inherit = "stock.picking"
|
||||
|
||||
purchase_request_count = fields.Integer('采购订单数量', compute='_compute_purchase_request')
|
||||
|
||||
@api.depends('name')
|
||||
def _compute_purchase_request(self):
|
||||
for record in self:
|
||||
purchase_request_ids = self.env['purchase.request'].search([('origin', '=', record.name)])
|
||||
record.purchase_request_count = len(purchase_request_ids)
|
||||
|
||||
def action_view_purchase_request(self):
|
||||
self.ensure_one()
|
||||
|
||||
purchase_request_ids = self.env['purchase.request'].search([('origin', '=', self.name)])
|
||||
|
||||
action = {
|
||||
'res_model': 'purchase.request',
|
||||
'type': 'ir.actions.act_window',
|
||||
}
|
||||
if len(purchase_request_ids) == 1:
|
||||
action.update({
|
||||
'view_mode': 'form',
|
||||
'res_id': purchase_request_ids[0].id,
|
||||
})
|
||||
else:
|
||||
action.update({
|
||||
'name': _("从 %s生成采购请求单", self.name),
|
||||
'domain': [('id', 'in', purchase_request_ids.ids)],
|
||||
'view_mode': 'tree,form',
|
||||
})
|
||||
return action
|
||||
|
||||
def _action_done(self):
|
||||
res = super(StockPicking, self)._action_done()
|
||||
# 将新产生的backorder对应上原来的采购申请明细行
|
||||
backorder_ids = self.backorder_ids
|
||||
if backorder_ids:
|
||||
purchase_request_lines = self.move_ids.move_orig_ids.purchase_line_id.purchase_request_lines
|
||||
if purchase_request_lines:
|
||||
purchase_request_lines.move_dest_ids = [
|
||||
(4, x.id) for x in backorder_ids.move_ids if x.product_id.id in purchase_request_lines.mapped('product_id.id')
|
||||
]
|
||||
return res
|
||||
@@ -1,4 +1,5 @@
|
||||
from odoo import api, fields, models
|
||||
from collections import defaultdict
|
||||
|
||||
|
||||
class StockRule(models.Model):
|
||||
@@ -44,13 +45,38 @@ class StockRule(models.Model):
|
||||
purchase_request_line_model.create(request_line_data)
|
||||
|
||||
def _run_buy(self, procurements):
|
||||
res = super(StockRule, self)._run_buy(procurements)
|
||||
# 判断是否根据规则生成新的采购申请单据,如果生成则修改状态为 approved
|
||||
origins = list(set([procurement[0].origin for procurement in procurements]))
|
||||
for origin in origins:
|
||||
pr_ids = self.env["purchase.request"].sudo().search(
|
||||
[('origin', 'like', origin), ('rule_new_add', '=', True), ('state', '=', 'draft')])
|
||||
if pr_ids:
|
||||
pr_ids.write({'need_validation': False})
|
||||
pr_ids.write({"state": "approved", 'need_validation': True, 'rule_new_add': False})
|
||||
# 如果补货组相同,并且产品相同,则合并
|
||||
procurements_dict = defaultdict()
|
||||
for procurement, rule in procurements:
|
||||
if (procurement.product_id.id, procurement.values['group_id'], rule.id) not in procurements_dict:
|
||||
procurements_dict[(procurement.product_id.id, procurement.values['group_id'], rule.id)] = {
|
||||
'product_id': procurement.product_id,
|
||||
'product_qty': procurement.product_qty,
|
||||
'product_uom': procurement.product_uom,
|
||||
'location_id': procurement.location_id,
|
||||
'name': procurement.name,
|
||||
'origin': procurement.origin,
|
||||
'company_id': procurement.company_id,
|
||||
'values': procurement.values,
|
||||
'rule': rule
|
||||
}
|
||||
else:
|
||||
procurements_dict[(procurement.product_id.id, procurement.values['group_id'], rule.id)]['product_qty'] += procurement.product_qty
|
||||
procurements_dict[(procurement.product_id.id, procurement.values['group_id'], rule.id)]['values']['move_dest_ids'] |= procurement.values['move_dest_ids']
|
||||
new_procurements = []
|
||||
for k, p in procurements_dict.items():
|
||||
new_procurements.append((
|
||||
self.env['procurement.group'].Procurement(
|
||||
product_id=p['product_id'],
|
||||
product_qty=p['product_qty'],
|
||||
product_uom=p['product_uom'],
|
||||
location_id=p['location_id'],
|
||||
name=p['name'],
|
||||
origin=p['origin'],
|
||||
company_id=p['company_id'],
|
||||
values=p['values']
|
||||
), p['rule'])
|
||||
)
|
||||
|
||||
res = super(StockRule, self)._run_buy(new_procurements)
|
||||
return res
|
||||
|
||||
2
jikimo_purchase_request/security/ir.model.access.csv
Normal file
2
jikimo_purchase_request/security/ir.model.access.csv
Normal file
@@ -0,0 +1,2 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_purchase_request_wizard_group_user,purchase.request.wizard,model_purchase_request_wizard,base.group_user,1,1,1,1
|
||||
|
3
jikimo_purchase_request/static/src/change.scss
Normal file
3
jikimo_purchase_request/static/src/change.scss
Normal file
@@ -0,0 +1,3 @@
|
||||
th[data-name=keep_description] {
|
||||
min-width: 220px;
|
||||
}
|
||||
22
jikimo_purchase_request/views/purchase_request_line_view.xml
Normal file
22
jikimo_purchase_request/views/purchase_request_line_view.xml
Normal file
@@ -0,0 +1,22 @@
|
||||
<odoo>
|
||||
<record id="purchase_request_line_form_sf" model="ir.ui.view">
|
||||
<field name="name">purchase.request.line.sf.form</field>
|
||||
<field name="model">purchase.request.line</field>
|
||||
<field name="inherit_id" ref="purchase_request.purchase_request_line_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//h1" position="before">
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<button type="object" name="action_view_purchase_request" class="oe_stat_button"
|
||||
icon="fa-file">
|
||||
<field name="purchase_request_count" widget="statinfo" string="采购申请"/>
|
||||
</button>
|
||||
<button type="object" name="action_view_purchase_order" class="oe_stat_button"
|
||||
attrs="{'invisible': [('purchase_count', '=', 0)]}" icon="fa-shopping-cart">
|
||||
<field name="purchase_count" widget="statinfo" string="采购订单"/>
|
||||
</button>
|
||||
</div>
|
||||
</xpath>
|
||||
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<record id="menu_purhcase_request" model="ir.ui.menu">
|
||||
<field name="name">采购申请</field>
|
||||
<field name="parent_id" ref="purchase.menu_purchase_root" />
|
||||
<field name="sequence">2</field>
|
||||
</record>
|
||||
|
||||
<record id="purchase_request.menu_purchase_request_pro_mgt" model="ir.ui.menu">
|
||||
<field name="sequence">1</field>
|
||||
<field name="parent_id" ref="jikimo_purchase_request.menu_purhcase_request"/>
|
||||
</record>
|
||||
|
||||
<record id="purchase_request.menu_purchase_request_line" model="ir.ui.menu">
|
||||
<field name="sequence">10</field>
|
||||
<field name="parent_id" ref="jikimo_purchase_request.menu_purhcase_request"/>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
|
||||
@@ -15,6 +15,26 @@
|
||||
<field name="part_number"/>
|
||||
<field name="part_name"/>
|
||||
</xpath>
|
||||
<xpath expr="//button[@name='button_done']" position="attributes">
|
||||
<attribute name="class"/>
|
||||
</xpath>
|
||||
<xpath expr="//button[@name='button_in_progress']" position="attributes">
|
||||
<attribute name="invisible">1</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//button[@name='%(purchase_request.action_purchase_request_line_make_purchase_order)d']" position="attributes">
|
||||
<attribute name="class">oe_highlight</attribute>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_purchase_request_tree_sf" model="ir.ui.view">
|
||||
<field name="name">purchase.request.sf.tree</field>
|
||||
<field name="model">purchase.request</field>
|
||||
<field name="inherit_id" ref="purchase_request.view_purchase_request_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='activity_ids']" position="attributes">
|
||||
<attribute name="optional">hide</attribute>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -26,14 +46,15 @@
|
||||
<xpath expr="//field[@name='requested_by']" position="replace">
|
||||
<field name="supply_method"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='assigned_to']" position="replace">
|
||||
<xpath expr="//field[@name='assigned_to']" position="attributes">
|
||||
<attribute name="invisible">True</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='name']" position="attributes">
|
||||
<attribute name="invisible">True</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='supplier_id']" position="after">
|
||||
<field name="requested_by" widget="many2one_avatar_user"/>
|
||||
<field name="assigned_to" widget="many2one_avatar_user"/>
|
||||
<field name="assigned_to" widget="many2one_avatar_user" invisible="1"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='purchased_qty']" position="attributes">
|
||||
<attribute name="string">采购数量</attribute>
|
||||
@@ -44,7 +65,7 @@
|
||||
<xpath expr="//field[@name='product_id']" position="after">
|
||||
<field name="related_product"/>
|
||||
<field name="part_number"/>
|
||||
<field name="part_name"/>
|
||||
<field name="part_name" invisible="1"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
@@ -62,4 +83,9 @@
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.actions.act_window" id="purchase_request.purchase_request_form_action">
|
||||
<field name="name">Purchase Requests</field>
|
||||
<field name="context"></field>
|
||||
</record>
|
||||
</odoo>
|
||||
21
jikimo_purchase_request/views/stock_picking_views.xml
Normal file
21
jikimo_purchase_request/views/stock_picking_views.xml
Normal file
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<record id="stock_pikcing_inherited_form_jikimo_purchase_request" model="ir.ui.view">
|
||||
<field name="name">stock.pikcing.inherited.form.jikimo.purchase.request</field>
|
||||
<field name="model">stock.picking</field>
|
||||
<field name="inherit_id" ref="stock.view_picking_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//div[@name='button_box']/button" position="before">
|
||||
<button class="oe_stat_button" name="action_view_purchase_request" type="object" icon="fa-credit-card"
|
||||
attrs="{'invisible': [('purchase_request_count', '=', 0)]}">
|
||||
<div class="o_field_widget o_stat_info">
|
||||
<span class="o_stat_value">
|
||||
<field name="purchase_request_count"/>
|
||||
</span>
|
||||
<span class="o_stat_text">采购申请</span>
|
||||
</div>
|
||||
</button>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -1,3 +1,4 @@
|
||||
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0)
|
||||
|
||||
from . import purchase_request_line_make_purchase_order
|
||||
from . import purchase_request_wizard
|
||||
|
||||
@@ -32,6 +32,7 @@ class PurchaseRequestLineMakePurchaseOrder(models.TransientModel):
|
||||
line.company_id,
|
||||
line.request_id.origin,
|
||||
)
|
||||
# po_data.update({'related_product':line.related_product.id})
|
||||
purchase = purchase_obj.create(po_data)
|
||||
|
||||
# Look for any other PO line in the selected PO with same
|
||||
@@ -63,6 +64,8 @@ class PurchaseRequestLineMakePurchaseOrder(models.TransientModel):
|
||||
po_line_data = self._prepare_purchase_order_line(purchase, item)
|
||||
if item.keep_description:
|
||||
po_line_data["name"] = item.name
|
||||
if line.related_product:
|
||||
po_line_data.update({'related_product': line.related_product.id})
|
||||
po_line = po_line_obj.create(po_line_data)
|
||||
po_line_product_uom_qty = po_line.product_uom._compute_quantity(
|
||||
po_line.product_uom_qty, alloc_uom
|
||||
@@ -87,8 +90,40 @@ class PurchaseRequestLineMakePurchaseOrder(models.TransientModel):
|
||||
"type": "ir.actions.act_window",
|
||||
}
|
||||
|
||||
def _check_valid_request_line(self, request_line_ids):
|
||||
for line in self.env["purchase.request.line"].browse(request_line_ids):
|
||||
if line.request_id.state not in ["approved", "in_progress"]:
|
||||
raise UserError(
|
||||
_("采购申请 %s 未审批或未进行中")
|
||||
% line.request_id.name
|
||||
)
|
||||
super(PurchaseRequestLineMakePurchaseOrder, self)._check_valid_request_line(request_line_ids)
|
||||
|
||||
@api.model
|
||||
def check_group(self, request_lines):
|
||||
# 去掉合并必须同一采购组的限制
|
||||
pass
|
||||
|
||||
def get_items(self, request_line_ids):
|
||||
request_line_obj = self.env["purchase.request.line"]
|
||||
items = []
|
||||
request_lines = request_line_obj.browse(request_line_ids).filtered(lambda line: line.pending_qty_to_receive > 0)
|
||||
self._check_valid_request_line(request_line_ids)
|
||||
self.check_group(request_lines)
|
||||
for line in request_lines:
|
||||
items.append([0, 0, self._prepare_item(line)])
|
||||
return items
|
||||
|
||||
|
||||
class PurchaseRequestLineMakePurchaseOrderItem(models.TransientModel):
|
||||
_inherit = "purchase.request.line.make.purchase.order.item"
|
||||
|
||||
supply_method = fields.Selection(related='line_id.supply_method', string='供货方式')
|
||||
|
||||
wiz_id = fields.Many2one(
|
||||
comodel_name="purchase.request.line.make.purchase.order",
|
||||
string="Wizard",
|
||||
required=False,
|
||||
ondelete="cascade",
|
||||
readonly=True,
|
||||
)
|
||||
|
||||
12
jikimo_purchase_request/wizard/purchase_request_wizard.py
Normal file
12
jikimo_purchase_request/wizard/purchase_request_wizard.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from odoo import models, fields, api
|
||||
|
||||
|
||||
class PurchaseRequestWizard(models.TransientModel):
|
||||
_name = 'purchase.request.wizard'
|
||||
_description = '采购申请向导'
|
||||
|
||||
purchase_request_id = fields.Many2one('purchase.request', string='采购申请')
|
||||
message = fields.Char(string='提示', readonly=True)
|
||||
|
||||
def confirm(self):
|
||||
return self.purchase_request_id.write({"state": "done"})
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record model="ir.ui.view" id="purchase_request_wizard_wizard_form_view">
|
||||
<field name="name">purchase.request.wizard.form.view</field>
|
||||
<field name="model">purchase.request.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<sheet>
|
||||
<div>
|
||||
<div style="white-space: pre-wrap;">
|
||||
<field name="message"/>
|
||||
</div>
|
||||
</div>
|
||||
<footer>
|
||||
<button string="确认" name="confirm" type="object" class="oe_highlight"/>
|
||||
<button string="取消" class="btn btn-secondary" special="cancel"/>
|
||||
</footer>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -1,10 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
{
|
||||
'name': "机企猫 采购审批流程",
|
||||
'name': "机企猫 采购申请审批流程",
|
||||
|
||||
'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,3 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import models
|
||||
from . import stock_rule
|
||||
@@ -22,3 +22,9 @@ class PurchaseRequest(models.Model):
|
||||
self.state = 'approved'
|
||||
|
||||
return res
|
||||
|
||||
@api.model
|
||||
def _get_under_validation_exceptions(self):
|
||||
res = super(PurchaseRequest, self)._get_under_validation_exceptions()
|
||||
res.append("state")
|
||||
return res
|
||||
|
||||
16
jikimo_purchase_request_tier_validation/models/stock_rule.py
Normal file
16
jikimo_purchase_request_tier_validation/models/stock_rule.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from odoo import models, api
|
||||
|
||||
class StockRule(models.Model):
|
||||
_inherit = 'stock.rule'
|
||||
|
||||
def _run_buy(self, procurements):
|
||||
res = super(StockRule, self)._run_buy(procurements)
|
||||
# 判断是否根据规则生成新的采购申请单据,如果生成则修改状态为 approved
|
||||
origins = list(set([procurement[0].origin for procurement in procurements]))
|
||||
for origin in origins:
|
||||
pr_ids = self.env["purchase.request"].sudo().search(
|
||||
[('origin', 'like', origin), ('rule_new_add', '=', True), ('state', '=', 'draft')])
|
||||
if pr_ids:
|
||||
pr_ids.write({'need_validation': False})
|
||||
pr_ids.write({"state": "approved", 'need_validation': True, 'rule_new_add': False})
|
||||
return res
|
||||
@@ -1,12 +1,12 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
{
|
||||
'name': "机企猫 采购申请审批流程",
|
||||
'name': "机企猫 采购审批流程",
|
||||
|
||||
'summary': """
|
||||
采购申请审批流程""",
|
||||
采购审批流程""",
|
||||
|
||||
'description': """
|
||||
采购申请审批流程""",
|
||||
采购审批流程""",
|
||||
|
||||
'author': "My Company",
|
||||
'website': "https://www.yourcompany.com",
|
||||
|
||||
17472
jikimo_purchase_tier_validation/i18n/zh_CN.po
Normal file
17472
jikimo_purchase_tier_validation/i18n/zh_CN.po
Normal file
File diff suppressed because it is too large
Load Diff
@@ -9,5 +9,6 @@ class MrpBom(models.Model):
|
||||
|
||||
# 成品的供应商从模板中获取
|
||||
if product_type == 'product':
|
||||
bom_id.subcontractor_id = product.product_tmpl_id.seller_ids.partner_id.id
|
||||
if product.product_tmpl_id.seller_ids:
|
||||
bom_id.subcontractor_id = product.product_tmpl_id.seller_ids[-1].partner_id.id
|
||||
return bom_id
|
||||
|
||||
2
jikimo_test_generate_product_name/__init__.py
Normal file
2
jikimo_test_generate_product_name/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from . import models
|
||||
18
jikimo_test_generate_product_name/__manifest__.py
Normal file
18
jikimo_test_generate_product_name/__manifest__.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
{
|
||||
'name': 'Jikimo_test_generate_product_name',
|
||||
'version': '',
|
||||
'summary': """ Jikimo_test_generate_product_name Summary """,
|
||||
'author': '',
|
||||
'website': '',
|
||||
'category': '',
|
||||
'depends': ['sf_manufacturing'],
|
||||
'data': [
|
||||
|
||||
],
|
||||
|
||||
'application': True,
|
||||
'installable': True,
|
||||
'auto_install': False,
|
||||
'license': 'LGPL-3',
|
||||
}
|
||||
2
jikimo_test_generate_product_name/models/__init__.py
Normal file
2
jikimo_test_generate_product_name/models/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from . import product_template
|
||||
21
jikimo_test_generate_product_name/models/product_template.py
Normal file
21
jikimo_test_generate_product_name/models/product_template.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from odoo import models
|
||||
|
||||
|
||||
class ProductTemplate(models.Model):
|
||||
_inherit = 'product.template'
|
||||
|
||||
def generate_product_name(self, order_id, item, i):
|
||||
"""生成成品名称"""
|
||||
# 3D文件名(去掉后缀,截取前40个字符)+“-”+模型ID
|
||||
product_name = '%s-%s' % ('.'.join(item['model_name'].split('.')[:-1])[:40], item['model_id'])
|
||||
return product_name
|
||||
|
||||
def generate_embryo_name(self, order_id, item, materials_id, materials_type_id, embryo_redundancy_id, i):
|
||||
"""生成坯料名称"""
|
||||
embryo_name = '%s-%s[%s * %s * %s]%s' % (materials_id.name, materials_type_id.name,
|
||||
self.format_float(item['model_long'] + embryo_redundancy_id.long),
|
||||
self.format_float(item['model_width'] + embryo_redundancy_id.width),
|
||||
self.format_float(item['model_height'] + embryo_redundancy_id.height),
|
||||
item['model_id'])
|
||||
return embryo_name
|
||||
|
||||
3
jikimo_work_reporting_api/__init__.py
Normal file
3
jikimo_work_reporting_api/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from . import controllers
|
||||
from . import models
|
||||
17
jikimo_work_reporting_api/__manifest__.py
Normal file
17
jikimo_work_reporting_api/__manifest__.py
Normal file
@@ -0,0 +1,17 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
{
|
||||
'name': '机企猫 报工系统API',
|
||||
'version': '1.0.0',
|
||||
'summary': """ 机企猫 报工系统API """,
|
||||
'author': '机企猫',
|
||||
'website': 'https://xt.sf.jikimo.com',
|
||||
'category': 'sf',
|
||||
'depends': ['base', 'sf_maintenance', 'jikimo_mini_program'],
|
||||
'data': [
|
||||
],
|
||||
|
||||
'application': True,
|
||||
'installable': True,
|
||||
'auto_install': False,
|
||||
'license': 'LGPL-3',
|
||||
}
|
||||
2
jikimo_work_reporting_api/controllers/__init__.py
Normal file
2
jikimo_work_reporting_api/controllers/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from . import main
|
||||
69
jikimo_work_reporting_api/controllers/main.py
Normal file
69
jikimo_work_reporting_api/controllers/main.py
Normal file
@@ -0,0 +1,69 @@
|
||||
import json
|
||||
from odoo import http
|
||||
from odoo.http import request
|
||||
from odoo.addons.sf_machine_connect.models.ftp_operate import transfer_files
|
||||
from odoo.addons.sf_base.decorators.api_log import api_log
|
||||
|
||||
class MainController(http.Controller):
|
||||
|
||||
@http.route('/api/manual_download_program', type='json', methods=['POST'], auth='wechat_token', cors='*')
|
||||
@api_log('人工线下加工编程文件传输', requester='报工系统')
|
||||
def manual_download_program(self):
|
||||
"""
|
||||
人工线下加工传输编程文件
|
||||
"""
|
||||
data = json.loads(request.httprequest.data)
|
||||
maintenance_equipment_id = data.get('maintenance_equipment_id')
|
||||
model_id = data.get('model_id')
|
||||
if not maintenance_equipment_id or not model_id:
|
||||
return {'code': 400, 'message': '参数错误'}
|
||||
try:
|
||||
model_id = int(model_id)
|
||||
except Exception as e:
|
||||
return {'code': 400, 'message': '参数类型错误'}
|
||||
maintenance_equipment = request.env['maintenance.equipment'].sudo().search(
|
||||
[('MTcode', '=', maintenance_equipment_id), ('category_id.equipment_type', '=', '机床')],
|
||||
limit=1
|
||||
)
|
||||
if not maintenance_equipment:
|
||||
return {'code': 400, 'message': '机台不存在,请扫描正确的机台二维码'}
|
||||
product = request.env['product.template'].sudo().search([('model_id', '=', model_id)], limit=1)
|
||||
if not product:
|
||||
return {'code': 400, 'message': '请扫描正确的图纸'}
|
||||
# 获取刀具组
|
||||
tool_groups_id = request.env['sf.tool.groups'].sudo().search([('equipment_ids', 'in', maintenance_equipment.id)], limit=1)
|
||||
if not tool_groups_id:
|
||||
return {'code': 400, 'message': '刀具组不存在'}
|
||||
|
||||
ftp_resconfig = request.env['res.config.settings'].sudo().get_values()
|
||||
if not ftp_resconfig['ftp_host'] or not ftp_resconfig['ftp_port'] or not ftp_resconfig['ftp_user'] or not ftp_resconfig['ftp_password']:
|
||||
return {'code': 400, 'message': '编程文件FTP配置错误'}
|
||||
source_ftp_info = {
|
||||
'host': ftp_resconfig['ftp_host'],
|
||||
'port': int(ftp_resconfig['ftp_port']),
|
||||
'username': ftp_resconfig['ftp_user'],
|
||||
'password': ftp_resconfig['ftp_password']
|
||||
}
|
||||
if not maintenance_equipment.ftp_host or not maintenance_equipment.ftp_port or not maintenance_equipment.ftp_username or not maintenance_equipment.ftp_password:
|
||||
return {'code': 400, 'message': '机台FTP配置错误'}
|
||||
target_ftp_info = {
|
||||
'host': maintenance_equipment.ftp_host,
|
||||
'port': int(maintenance_equipment.ftp_port),
|
||||
'username': maintenance_equipment.ftp_username,
|
||||
'password': maintenance_equipment.ftp_password
|
||||
}
|
||||
# 传输nc文件
|
||||
try:
|
||||
result = transfer_files(
|
||||
source_ftp_info,
|
||||
target_ftp_info,
|
||||
'/' + str(model_id),
|
||||
'/',
|
||||
match_str=r'^\d*-' + tool_groups_id.name + r'-\w{2}-all\.nc$'
|
||||
)
|
||||
if len(result) > 0:
|
||||
return {'code': 200, 'message': '传输成功', 'file_list': result}
|
||||
else:
|
||||
return {'code': 404, 'message': '未找到编程文件'}
|
||||
except Exception as e:
|
||||
return {'code': 500, 'message': str(e)}
|
||||
1
jikimo_work_reporting_api/models/__init__.py
Normal file
1
jikimo_work_reporting_api/models/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
@@ -4,6 +4,7 @@ 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
|
||||
from odoo.addons.sf_base.decorators.api_log import api_log
|
||||
from datetime import datetime
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
@@ -12,6 +13,7 @@ class WorkorderExceptionConroller(http.Controller):
|
||||
|
||||
@http.route('/AutoDeviceApi/BillError', type='json', auth='public', methods=['GET', 'POST'], csrf=False,
|
||||
cors="*")
|
||||
@api_log('工单对接错误', requester='中控系统')
|
||||
def workder_exception(self, **kw):
|
||||
"""
|
||||
记录工单异常
|
||||
|
||||
@@ -52,10 +52,10 @@ class JikimoWorkorderException(models.Model):
|
||||
|
||||
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')
|
||||
base_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)
|
||||
url = base_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, message_queue_ids
|
||||
|
||||
@@ -130,9 +130,10 @@ class QualityPoint(models.Model):
|
||||
|
||||
class QualityCheck(models.Model):
|
||||
_inherit = "quality.check"
|
||||
part_name = fields.Char('零件名称', related='product_id.part_name')
|
||||
part_name = fields.Char('零件名称', related='product_id.part_name', readonly=False, store=True)
|
||||
part_number = fields.Char('零件图号', related='product_id.part_number', readonly=False, store=True)
|
||||
material_name = fields.Char('材料名称', compute='_compute_material_name')
|
||||
model_id = fields.Char('模型ID', related='product_id.model_id')
|
||||
|
||||
# # 总数量,值为调拨单_产品明细_数量
|
||||
# total_qty = fields.Float('总数量', compute='_compute_total_qty', readonly=True)
|
||||
@@ -141,7 +142,7 @@ class QualityCheck(models.Model):
|
||||
# # 出厂检验报告编号
|
||||
# report_number = fields.Char('出厂检验报告编号', compute='_compute_report_number', readonly=True)
|
||||
# 总数量,值为调拨单_产品明细_数量
|
||||
total_qty = fields.Char('总数量', compute='_compute_total_qty')
|
||||
total_qty = fields.Char('总数量', compute='_compute_total_qty', store=True)
|
||||
|
||||
column_nums = fields.Integer('测量值列数', default=1)
|
||||
|
||||
@@ -153,9 +154,9 @@ class QualityCheck(models.Model):
|
||||
for move in record.picking_id.move_ids_without_package:
|
||||
if move.product_id == record.product_id:
|
||||
total_qty = int(move.product_uom_qty)
|
||||
record.total_qty = total_qty if total_qty > 0 else ''
|
||||
record.total_qty = total_qty if total_qty > 0 else 0
|
||||
else:
|
||||
record.total_qty = ''
|
||||
record.total_qty = 0
|
||||
|
||||
# 检验数
|
||||
check_qty = fields.Integer('检验数', default=lambda self: self._get_default_check_qty())
|
||||
@@ -206,7 +207,15 @@ class QualityCheck(models.Model):
|
||||
('NG', 'NG')
|
||||
], string='出厂检验报告结果', default='OK')
|
||||
measure_operator = fields.Many2one('res.users', string='操机员')
|
||||
quality_manager = fields.Many2one('res.users', string='质检员')
|
||||
quality_manager = fields.Many2one('res.users', string='质检员', compute='_compute_quality_manager')
|
||||
|
||||
@api.depends('measure_line_ids')
|
||||
def _compute_quality_manager(self):
|
||||
for record in self:
|
||||
if record.measure_line_ids:
|
||||
record.quality_manager = record.env.user.id
|
||||
else:
|
||||
record.quality_manager = False
|
||||
|
||||
# 流水号(从1开始,最大99)
|
||||
serial_number = fields.Integer('流水号', default=1, readonly=True)
|
||||
@@ -250,6 +259,23 @@ class QualityCheck(models.Model):
|
||||
line[field_name] = False
|
||||
self.column_nums = self.column_nums - 1
|
||||
|
||||
def upload_measure_line(self):
|
||||
"""
|
||||
上传测量值
|
||||
"""
|
||||
|
||||
for record in self:
|
||||
if not record.part_name or not record.part_number:
|
||||
raise UserError(_('零件名称和零件图号均不能为空'))
|
||||
|
||||
# 如果验证通过,返回原动作
|
||||
action = self.env.ref('quality_control.import_complex_model_wizard').read()[0]
|
||||
action['context'] = {
|
||||
'default_model_name': 'quality.check.measure.line',
|
||||
'default_check_id': self.id,
|
||||
}
|
||||
return action
|
||||
|
||||
def do_preview(self):
|
||||
"""
|
||||
预览出厂检验报告
|
||||
@@ -313,7 +339,7 @@ class QualityCheck(models.Model):
|
||||
|
||||
# 4. 获取报告动作并生成PDF(此时二维码将包含正确的文档ID)
|
||||
report_action = self.env.ref('sf_quality.action_report_quality_inspection')
|
||||
pdf_content, _ = report_action._render_qweb_pdf(
|
||||
pdf_content, v = report_action._render_qweb_pdf(
|
||||
report_ref=report_action.report_name,
|
||||
res_ids=self.ids
|
||||
)
|
||||
@@ -336,7 +362,6 @@ class QualityCheck(models.Model):
|
||||
|
||||
# 7. 更新其他信息
|
||||
self.serial_number += 1
|
||||
self.quality_manager = self.env.user.id
|
||||
|
||||
if self.publish_status == 'canceled' and self.picking_id.state == 'done':
|
||||
self.upload_factory_report()
|
||||
@@ -711,8 +736,9 @@ class QualityCheck(models.Model):
|
||||
def _compute_qty_to_test(self):
|
||||
for qc in self:
|
||||
if qc.is_lot_tested_fractionally:
|
||||
rounding = qc.product_id.uom_id.rounding if qc.product_id.uom_id else 0.01
|
||||
qc.qty_to_test = float_round(qc.qty_line * qc.testing_percentage_within_lot / 100,
|
||||
precision_rounding=self.product_id.uom_id.rounding, rounding_method="UP")
|
||||
precision_rounding=rounding, rounding_method="UP")
|
||||
else:
|
||||
qc.qty_to_test = qc.qty_line
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<field name="model">quality.check.measure.line</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree editable="bottom" class="measureTable">
|
||||
<field name="sequence" class="measureTableSequence"/>
|
||||
<!-- <field name="sequence" class="measureTableSequence"/> -->
|
||||
<!-- <field name="column_nums"/> -->
|
||||
<field name="measure_item"/>
|
||||
<field name="measure_value1" attrs="{ 'column_invisible': [('parent.column_nums', '<', 1)] }"/>
|
||||
|
||||
@@ -267,13 +267,13 @@
|
||||
<field name="company_id" invisible="1"/>
|
||||
<field name="categ_type" invisible="1"/>
|
||||
<field name="product_id" attrs="{'invisible' : [('measure_on', '=', 'operation')]}"/>
|
||||
<field name="part_name" attrs="{'invisible': [('categ_type', '!=', '成品')]}"/>
|
||||
<field name="part_name" attrs="{'invisible': [('categ_type', '!=', '成品')], 'readonly': [('publish_status', '=', 'published')]}"/>
|
||||
<field name="part_number" attrs="{'invisible': [('categ_type', '!=', '成品')], 'readonly': [('publish_status', '=', 'published')]}"/>
|
||||
<field name="material_name" attrs="{'invisible': [('categ_type', '!=', '成品')]}"/>
|
||||
<field name="total_qty" attrs="{'invisible': ['|', ('measure_on', '!=', 'product'), ('is_out_check', '=', False)]}"/>
|
||||
<field name="check_qty" attrs="{'invisible': [('measure_on', '!=', 'product')], 'readonly': [('publish_status', '=', 'published')]}"/>
|
||||
<field name="check_qty" attrs="{'invisible': ['|', ('measure_on', '!=', 'product'), ('is_out_check', '=', False)], 'readonly': [('publish_status', '=', 'published')]}"/>
|
||||
<!-- <field name="categ_type"/> -->
|
||||
<field name="report_number_id"/>
|
||||
<field name="report_number_id" attrs="{'invisible': ['|', ('measure_on', '!=', 'product'), ('is_out_check', '=', False)]}"/>
|
||||
<field name="column_nums" invisible="1"/>
|
||||
<field name="publish_status" invisible="1"/>
|
||||
<field name="show_lot_text" invisible="1"/>
|
||||
@@ -320,7 +320,7 @@
|
||||
<field name="team_id"/>
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
<field name="user_id" string="Control Person" invisible="1"/>
|
||||
<field name="measure_operator" string="操机员" attrs="{'readonly': [('publish_status', '=', 'published')]}"/>
|
||||
<field name="measure_operator" string="操机员" attrs="{'invisible': ['|', ('measure_on', '!=', 'product'), ('is_out_check', '=', False)], 'readonly': [('publish_status', '=', 'published')]}"/>
|
||||
|
||||
</group>
|
||||
</group>
|
||||
@@ -334,11 +334,15 @@
|
||||
<div class="o_row">
|
||||
<button name="add_measure_line" type="object" class="btn-primary" string="添加测量值" attrs="{'invisible': [('publish_status', '=', 'published')]}"/>
|
||||
<button name="remove_measure_line" type="object" class="btn-primary" string="删除测量值" attrs="{'invisible': [('publish_status', '=', 'published')]}"/>
|
||||
<button name="%(quality_control.import_complex_model_wizard)d" string="上传"
|
||||
type="action"
|
||||
<!-- <button name="%(quality_control.import_complex_model_wizard)d" string="上传111" -->
|
||||
<!-- type="action" -->
|
||||
<!-- class="btn-primary" -->
|
||||
<!-- attrs="{'force_show':1, 'invisible': [('publish_status', '=', 'published')]}" -->
|
||||
<!-- context="{'default_model_name': 'quality.check.measure.line', 'default_check_id': id}"/> -->
|
||||
<button name="upload_measure_line" string="上传"
|
||||
type="object"
|
||||
class="btn-primary"
|
||||
attrs="{'force_show':1, 'invisible': [('publish_status', '=', 'published')]}"
|
||||
context="{'default_model_name': 'quality.check.measure.line', 'default_check_id': id}"/>
|
||||
attrs="{'force_show':1, 'invisible': [('publish_status', '=', 'published')]}"/>
|
||||
</div>
|
||||
<br/>
|
||||
<div class="o_row">
|
||||
@@ -489,6 +493,9 @@
|
||||
<field name="picking_id"/>
|
||||
<field name="lot_id"/>
|
||||
<field name="team_id"/>
|
||||
<field name="part_number"/>
|
||||
<field name="part_name"/>
|
||||
<field name="model_id"/>
|
||||
<filter string="In Progress" name="progress" domain="[('quality_state', '=', 'none')]"/>
|
||||
<filter string="Passed" name="passed" domain="[('quality_state', '=', 'pass')]"/>
|
||||
<filter string="Failed" name="failed" domain="[('quality_state', '=', 'fail')]"/>
|
||||
|
||||
@@ -22,6 +22,7 @@ _logger = logging.getLogger(__name__)
|
||||
class ImportComplexModelWizard(models.TransientModel):
|
||||
_name = 'quality.check.import.complex.model.wizard'
|
||||
file_data = fields.Binary("数据文件")
|
||||
filename = fields.Char(string='文件名')
|
||||
model_name = fields.Char(string='Model Name')
|
||||
field_basis = fields.Char(string='Field Basis')
|
||||
check_id = fields.Many2one(string='质检单', comodel_name='quality.check')
|
||||
@@ -93,6 +94,11 @@ class ImportComplexModelWizard(models.TransientModel):
|
||||
|
||||
return repeats
|
||||
|
||||
def convert_float(self, value):
|
||||
if isinstance(value, float) and value.is_integer():
|
||||
return int(value)
|
||||
return value
|
||||
|
||||
def import_data(self):
|
||||
"""导入Excel数据"""
|
||||
if not self.file_data:
|
||||
@@ -161,6 +167,20 @@ class ImportComplexModelWizard(models.TransientModel):
|
||||
|
||||
# 从第二行开始读取数据(跳过表头)
|
||||
max_columns = 1
|
||||
for row_index in range(1, sheet.nrows):
|
||||
row = sheet.row_values(row_index)
|
||||
# 检查行是否有数据
|
||||
if not any(row):
|
||||
continue
|
||||
|
||||
if row[2] == '':
|
||||
continue
|
||||
logging.info('================%s, %s==' % (row[1], type(row[1])))
|
||||
|
||||
compare_value = self.convert_float(row[1])
|
||||
if str(compare_value) != quality_check.part_number:
|
||||
print(sheet.row_values(row_index))
|
||||
raise UserError(_('上传内容图号错误,请修改'))
|
||||
for row_index in range(1, sheet.nrows):
|
||||
row = sheet.row_values(row_index)
|
||||
|
||||
@@ -177,14 +197,14 @@ class ImportComplexModelWizard(models.TransientModel):
|
||||
'sequence': len(quality_check.measure_line_ids) + 1,
|
||||
'product_name': str(row[0]) if row[0] else '', # 产品名称列
|
||||
'drawing_no': str(row[1]) if row[1] else '', # 图号列
|
||||
'measure_item': row[2] or '', # 检测项目列
|
||||
'measure_value1': str(row[4]) if row[4] else '', # 测量值1
|
||||
'measure_value2': str(row[5]) if row[5] else '', # 测量值2
|
||||
'measure_value3': str(row[6]) if len(row) > 6 and row[6] else '', # 测量值3
|
||||
'measure_value4': str(row[7]) if len(row) > 7 and row[7] else '', # 测量值4
|
||||
'measure_value5': str(row[8]) if len(row) > 8 and row[8] else '', # 测量值5
|
||||
'measure_item': str(self.convert_float(row[2])) or '', # 检测项目列
|
||||
'measure_value1': str(self.convert_float(row[4])) if row[4] else '', # 测量值1
|
||||
'measure_value2': str(self.convert_float(row[5])) if row[5] else '', # 测量值2
|
||||
'measure_value3': str(self.convert_float(row[6])) if len(row) > 6 and row[6] else '', # 测量值3
|
||||
'measure_value4': str(self.convert_float(row[7])) if len(row) > 7 and row[7] else '', # 测量值4
|
||||
'measure_value5': str(self.convert_float(row[8])) if len(row) > 8 and row[8] else '', # 测量值5
|
||||
'measure_result': 'NG' if row[9] == 'NG' else 'OK', # 判定列
|
||||
'remark': row[10] if len(row) > 10 and row[10] else '', # 备注列
|
||||
'remark': self.convert_float(row[10]) if len(row) > 10 and row[10] else '', # 备注列
|
||||
}
|
||||
|
||||
for i in range(1, 6):
|
||||
@@ -424,7 +444,8 @@ class ImportComplexModelWizard(models.TransientModel):
|
||||
# )
|
||||
|
||||
def download_excel_template(self):
|
||||
base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url') + '/quality_control/static/src/binary/出厂检验报告上传模版.xlsx'
|
||||
base_url = self.env['ir.config_parameter'].sudo().get_param(
|
||||
'web.base.url') + '/quality_control/static/src/binary/出厂检验报告上传模版.xlsx'
|
||||
|
||||
# 只有当原始 URL 使用 http 时才替换为 https
|
||||
if base_url.startswith("http://"):
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<group>
|
||||
<field name="file_data" widget="binary" options="{'accepted_file_extensions': '.xlsx'}"/>
|
||||
<field name="file_data" widget="binary" filename="filename" options="{'accepted_file_extensions': '.xls,.xlsx'}"/>
|
||||
<field name="filename" invisible="1"/>
|
||||
</group>
|
||||
<footer>
|
||||
<button string="确认导入" name="import_data" type="object" class="btn-primary"/>
|
||||
|
||||
@@ -23,8 +23,8 @@ class QualityCheckWizard(models.TransientModel):
|
||||
lot_name = fields.Char(related='current_check_id.lot_name')
|
||||
lot_line_id = fields.Many2one(related='current_check_id.lot_line_id')
|
||||
qty_line = fields.Float(related='current_check_id.qty_line')
|
||||
qty_to_test = fields.Float(related='current_check_id.qty_to_test')
|
||||
qty_tested = fields.Float(related='current_check_id.qty_tested', readonly=False)
|
||||
qty_to_test = fields.Float(related='current_check_id.qty_to_test', string='待检')
|
||||
qty_tested = fields.Float(related='current_check_id.qty_tested', string='已检', readonly=False)
|
||||
measure = fields.Float(related='current_check_id.measure', readonly=False)
|
||||
measure_on = fields.Selection(related='current_check_id.measure_on')
|
||||
quality_state = fields.Selection(related='current_check_id.quality_state')
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
<field name="lot_id" position="after">
|
||||
<field name="workorder_id" invisible="1"/>
|
||||
<field name="production_id" invisible="1"/>
|
||||
<field name="finished_lot_id" attrs="{'invisible': [('finished_lot_id', '=', False)]}" groups="stock.group_production_lot"/>
|
||||
<!-- <field name="finished_lot_id" attrs="{'invisible': [('finished_lot_id', '=', False)]}" groups="stock.group_production_lot"/> -->
|
||||
</field>
|
||||
<xpath expr="//field[@name='lot_id']" position="after">
|
||||
<field name="lot_id" attrs="{'invisible': [('workorder_id', '=', False)]}" groups="stock.group_production_lot" string="Component Lot/Serial"/>
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
pystrich
|
||||
cpca
|
||||
pycryptodome==3.20
|
||||
cpca==0.5.5
|
||||
wechatpy==1.8.18
|
||||
pycryptodome==3.22.0
|
||||
openupgradelib==3.10.0
|
||||
opcua==0.98.13
|
||||
openpyxl
|
||||
@@ -1,3 +1,4 @@
|
||||
from . import models
|
||||
from . import commons
|
||||
from . import controllers
|
||||
from . import decorators
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
'views/menu_fixture_view.xml',
|
||||
'views/change_base_view.xml',
|
||||
'views/Printer.xml',
|
||||
|
||||
'views/api_log_views.xml',
|
||||
],
|
||||
'demo': [
|
||||
],
|
||||
|
||||
@@ -9,7 +9,6 @@ class Printer(models.Model):
|
||||
ip_address = fields.Char(string='IP 地址', required=True)
|
||||
port = fields.Integer(string='端口', default=9100)
|
||||
|
||||
|
||||
class TableStyle(models.Model):
|
||||
_name = 'table.style'
|
||||
_description = '标签样式'
|
||||
|
||||
@@ -2,7 +2,16 @@
|
||||
import time, datetime
|
||||
import hashlib
|
||||
from odoo import models
|
||||
from typing import Optional
|
||||
import socket
|
||||
import os
|
||||
import logging
|
||||
import qrcode
|
||||
from reportlab.pdfgen import canvas
|
||||
from reportlab.lib.units import inch
|
||||
from PyPDF2 import PdfFileReader, PdfFileWriter
|
||||
from reportlab.pdfbase import pdfmetrics
|
||||
from reportlab.pdfbase.ttfonts import TTFont
|
||||
|
||||
class Common(models.Model):
|
||||
_name = 'sf.sync.common'
|
||||
@@ -92,3 +101,145 @@ class PrintingUtils(models.AbstractModel):
|
||||
# host = "192.168.50.110" # 可以作为参数传入,或者在此配置
|
||||
# port = 9100 # 可以作为参数传入,或者在此配置
|
||||
self.send_to_printer(host, port, zpl_code)
|
||||
|
||||
|
||||
def add_qr_code_to_pdf(
|
||||
self,
|
||||
pdf_path:str,
|
||||
content:str,
|
||||
qr_code_buttom_text:Optional[str]=False,
|
||||
buttom_text:Optional[str]=False,
|
||||
):
|
||||
"""
|
||||
在PDF文件中添加二维码
|
||||
:param pdf_path: PDF文件路径
|
||||
:param content: 二维码内容
|
||||
:param qr_code_buttom_text: 二维码下方文字
|
||||
:param buttom_text: 正文下方文字
|
||||
:return: 是否成功
|
||||
"""
|
||||
if not os.path.exists(pdf_path):
|
||||
logging.warning(f'PDF文件不存在: {pdf_path}')
|
||||
return False
|
||||
|
||||
# 生成二维码
|
||||
qr = qrcode.QRCode(version=1, box_size=10, border=5)
|
||||
qr.add_data(str(content))
|
||||
qr.make(fit=True)
|
||||
qr_img = qr.make_image(fill_color="black", back_color="white")
|
||||
|
||||
# 保存二维码为临时文件
|
||||
qr_temp_path = '/tmp/qr_temp.png'
|
||||
qr_img.save(qr_temp_path)
|
||||
|
||||
# 创建一个临时PDF文件路径
|
||||
output_temp_path = '/tmp/output_temp.pdf'
|
||||
|
||||
try:
|
||||
# 使用reportlab创建一个新的PDF
|
||||
|
||||
|
||||
# 注册中文字体
|
||||
font_paths = [
|
||||
"/usr/share/fonts/chinese/simsun.ttc", # Windows系统宋体
|
||||
"c:/windows/fonts/simsun.ttc", # Windows系统宋体另一个位置
|
||||
"/usr/share/fonts/truetype/droid/DroidSansFallbackFull.ttf", # Linux Droid字体
|
||||
"/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc", # 文泉驿正黑
|
||||
"/usr/share/fonts/chinese/TrueType/simsun.ttc", # 某些Linux发行版位置
|
||||
]
|
||||
|
||||
font_found = False
|
||||
for font_path in font_paths:
|
||||
if os.path.exists(font_path):
|
||||
try:
|
||||
pdfmetrics.registerFont(TTFont('SimSun', font_path))
|
||||
font_found = True
|
||||
break
|
||||
except:
|
||||
continue
|
||||
|
||||
# 读取原始PDF
|
||||
with open(pdf_path, "rb") as original_file:
|
||||
existing_pdf = PdfFileReader(original_file)
|
||||
output = PdfFileWriter()
|
||||
|
||||
# 处理最后一页
|
||||
last_page = existing_pdf.getNumPages() - 1
|
||||
page = existing_pdf.getPage(last_page)
|
||||
# 获取页面尺寸
|
||||
page_width = float(page.mediaBox.getWidth())
|
||||
page_height = float(page.mediaBox.getHeight())
|
||||
|
||||
# 创建一个新的PDF页面用于放置二维码
|
||||
c = canvas.Canvas(output_temp_path, pagesize=(page_width, page_height))
|
||||
|
||||
# 设置字体
|
||||
if font_found:
|
||||
c.setFont('SimSun', 10) # 增大字体大小到14pt
|
||||
else:
|
||||
# 如果没有找到中文字体,使用默认字体
|
||||
c.setFont('Helvetica', 10)
|
||||
logging.warning("未找到中文字体,将使用默认字体")
|
||||
|
||||
# 在右下角绘制二维码,预留边距
|
||||
qr_size = 1.5 * inch # 二维码大小为2英寸
|
||||
margin = 0.1 * inch # 边距为0.4英寸
|
||||
qr_y = margin + 20 # 将二维码向上移动一点,为文字留出空间
|
||||
c.drawImage(qr_temp_path, page_width - qr_size - margin, qr_y, width=qr_size, height=qr_size)
|
||||
|
||||
if qr_code_buttom_text:
|
||||
# 在二维码下方绘制文字
|
||||
text = qr_code_buttom_text
|
||||
text_width = c.stringWidth(text, "SimSun" if font_found else "Helvetica", 10) # 准确计算文字宽度
|
||||
text_x = page_width - qr_size - margin + (qr_size - text_width) / 2 # 文字居中对齐
|
||||
text_y = margin + 20 # 文字位置靠近底部
|
||||
c.drawString(text_x, text_y, text)
|
||||
|
||||
# 设置字体
|
||||
if font_found:
|
||||
c.setFont('SimSun', 12) # 增大字体大小到14pt
|
||||
else:
|
||||
# 如果没有找到中文字体,使用默认字体
|
||||
c.setFont('Helvetica', 120)
|
||||
logging.warning("未找到中文字体,将使用默认字体")
|
||||
|
||||
if buttom_text:
|
||||
# 在下方中间添加文字
|
||||
text = buttom_text
|
||||
text_width = c.stringWidth(text, "SimSun" if font_found else "Helvetica", 12) # 准确计算文字宽度
|
||||
text_x = (page_width - text_width) / 2 # 文字居中对齐
|
||||
text_y = margin + 20 # 文字位置靠近底部
|
||||
c.drawString(text_x, text_y, text)
|
||||
|
||||
c.save()
|
||||
|
||||
# 读取带有二维码的临时PDF
|
||||
with open(output_temp_path, "rb") as qr_file:
|
||||
qr_pdf = PdfFileReader(qr_file)
|
||||
qr_page = qr_pdf.getPage(0)
|
||||
|
||||
# 合并原始页面和二维码页面
|
||||
page.mergePage(qr_page)
|
||||
|
||||
# 添加剩余的页面
|
||||
for i in range(0, last_page):
|
||||
output.addPage(existing_pdf.getPage(i))
|
||||
|
||||
output.addPage(page)
|
||||
|
||||
# 保存最终的PDF到一个临时文件
|
||||
final_temp_path = pdf_path + '.tmp'
|
||||
with open(final_temp_path, "wb") as output_file:
|
||||
output.write(output_file)
|
||||
|
||||
# 替换原始文件
|
||||
os.replace(final_temp_path, pdf_path)
|
||||
|
||||
return True
|
||||
|
||||
finally:
|
||||
# 清理临时文件
|
||||
if os.path.exists(qr_temp_path):
|
||||
os.remove(qr_temp_path)
|
||||
if os.path.exists(output_temp_path):
|
||||
os.remove(output_temp_path)
|
||||
@@ -1,15 +1,18 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import logging
|
||||
import json
|
||||
import base64
|
||||
import logging
|
||||
from odoo import http
|
||||
from odoo.http import request
|
||||
from odoo.addons.sf_base.decorators.api_log import api_log
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class Manufacturing_Connect(http.Controller):
|
||||
|
||||
@http.route('/AutoDeviceApi/MachineToolGroup', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False,
|
||||
cors="*")
|
||||
@api_log('机床刀具组', requester='中控系统')
|
||||
def get_maintenance_tool_groups_Info(self, **kw):
|
||||
"""
|
||||
机床刀具组接口
|
||||
|
||||
1
sf_base/decorators/__init__.py
Normal file
1
sf_base/decorators/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import api_log
|
||||
65
sf_base/decorators/api_log.py
Normal file
65
sf_base/decorators/api_log.py
Normal file
@@ -0,0 +1,65 @@
|
||||
|
||||
import functools
|
||||
import json
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from odoo.http import request
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
def api_log(name=None, requester=None):
|
||||
"""记录API请求日志的装饰器"""
|
||||
def decorator(func):
|
||||
@functools.wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
start_time = datetime.now()
|
||||
|
||||
# 获取请求信息
|
||||
try:
|
||||
# 获取请求数据
|
||||
request_data = json.loads(request.httprequest.data) if request.httprequest.data else {}
|
||||
# 获取请求路径
|
||||
path = request.httprequest.path
|
||||
# 获取请求方法
|
||||
method = request.httprequest.method
|
||||
# 获取客户端IP
|
||||
remote_addr = request.httprequest.remote_addr
|
||||
|
||||
# 执行原始函数
|
||||
result = func(*args, **kwargs)
|
||||
origin_result = result
|
||||
if isinstance(result, str):
|
||||
result = json.loads(result)
|
||||
|
||||
# 计算响应时间
|
||||
end_time = datetime.now()
|
||||
response_time = (end_time - start_time).total_seconds()
|
||||
|
||||
# 获取响应状态
|
||||
status = result.get('code') if 'code' in result else result.get('ErrorCode') if 'ErrorCode' in result else 200
|
||||
|
||||
# 创建日志记录
|
||||
log_vals = {
|
||||
'name': name or func.__name__,
|
||||
'path': path,
|
||||
'method': method.upper(),
|
||||
'request_data': json.dumps(request_data, ensure_ascii=False),
|
||||
'response_data': json.dumps(result, ensure_ascii=False),
|
||||
'remote_addr': remote_addr,
|
||||
'response_time': response_time,
|
||||
'status': 200 if status == 0 else status,
|
||||
'requester': requester,
|
||||
'responser': '智能工厂'
|
||||
}
|
||||
|
||||
# 异步创建日志记录
|
||||
request.env['api.request.log'].sudo().with_context(tracking_disable=True).create(log_vals)
|
||||
|
||||
return origin_result
|
||||
|
||||
except Exception as e:
|
||||
_logger.error(f"API日志记录失败: {str(e)}")
|
||||
# 即使日志记录失败,也要返回原始结果
|
||||
return func(*args, **kwargs)
|
||||
return wrapper
|
||||
return decorator
|
||||
@@ -6,3 +6,4 @@ from . import functional_fixture
|
||||
from . import tool_other_features
|
||||
from . import basic_parameters_fixture
|
||||
from . import ir_sequence
|
||||
from . import api_log
|
||||
|
||||
72
sf_base/models/api_log.py
Normal file
72
sf_base/models/api_log.py
Normal file
@@ -0,0 +1,72 @@
|
||||
from odoo import models, fields, api
|
||||
import json, ast
|
||||
import logging
|
||||
import requests
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ApiRequestLog(models.Model):
|
||||
_name = 'api.request.log'
|
||||
_description = '接口请求日志'
|
||||
_order = 'id desc'
|
||||
|
||||
name = fields.Char('接口名称')
|
||||
path = fields.Char('请求路径')
|
||||
method = fields.Char('请求方法')
|
||||
request_data = fields.Text('请求数据')
|
||||
response_data = fields.Text('响应数据')
|
||||
remote_addr = fields.Char('客户端IP')
|
||||
response_time = fields.Float('响应时间(秒)', digits=(16, 6))
|
||||
status = fields.Integer('状态码')
|
||||
requester = fields.Char('请求方')
|
||||
responser = fields.Char('响应方')
|
||||
|
||||
@api.model
|
||||
def log_request(self, method, url, name=None, responser=None, **kwargs):
|
||||
# Log the request
|
||||
request_headers = kwargs.get('headers', {})
|
||||
request_body = kwargs.get('json') or kwargs.get('params') or {}
|
||||
|
||||
_logger.info(f"Request: {method} {url} Headers: {request_headers} Body: {request_body}")
|
||||
|
||||
# Make the actual request
|
||||
response = requests.request(method, url, **kwargs)
|
||||
|
||||
# Log the response
|
||||
response_status = response.status_code
|
||||
response_headers = response.headers
|
||||
response_body = response.text
|
||||
response_time = response.elapsed.total_seconds()
|
||||
|
||||
_logger.info(f"Response: Status: {response_status} Headers: {response_headers} Body: {response_body}")
|
||||
|
||||
try:
|
||||
# 如果是字符串,先尝试用 ast.literal_eval 安全地转换成 Python 对象
|
||||
if isinstance(response_body, str):
|
||||
|
||||
response_body_obj = json.loads(response_body)
|
||||
else:
|
||||
response_body_obj = response_body
|
||||
|
||||
# 再使用 json.dumps 转换成标准的 JSON 字符串
|
||||
response_body = json.dumps(response_body_obj, ensure_ascii=False)
|
||||
except Exception as e:
|
||||
_logger.warning(f"转换 response_body 到标准 JSON 失败: {str(e)}")
|
||||
# 如果转换失败,保持原样
|
||||
|
||||
# Save to database
|
||||
self.sudo().create({
|
||||
'name': name,
|
||||
'path': url,
|
||||
'method': method.upper(),
|
||||
'request_data': request_body,
|
||||
'response_data': response_body,
|
||||
'remote_addr': None,
|
||||
'response_time': response_time,
|
||||
'status': response_status,
|
||||
'requester': '智能工厂',
|
||||
'responser': responser
|
||||
})
|
||||
|
||||
return response
|
||||
@@ -32,6 +32,7 @@ class FixtureModel(models.Model):
|
||||
multi_mounting_type_id = fields.Many2one('sf.multi_mounting.type', string="联装类型")
|
||||
brand_id = fields.Many2one('sf.machine.brand', string="品牌")
|
||||
model_file = fields.Binary(string="图片")
|
||||
glb_url = fields.Char(string="图片")
|
||||
status = fields.Boolean('状态')
|
||||
active = fields.Boolean('有效', default=False)
|
||||
|
||||
|
||||
@@ -254,3 +254,6 @@ access_sf_machining_accuracy_admin,sf_machining_accuracy_admin,model_sf_machinin
|
||||
|
||||
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
|
||||
|
||||
access_api_request_log_user,api.request.log.user,model_api_request_log,base.group_user,1,0,0,0
|
||||
access_api_request_log_admin,api.request.log.admin,model_api_request_log,base.group_system,1,1,1,1
|
||||
|
@@ -148,12 +148,17 @@ td.o_required_modifier {
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.o_kanban_primary_left {
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
justify-content: flex-start;
|
||||
// .o_kanban_primary_left {
|
||||
// display: flex;
|
||||
// flex-direction: row-reverse;
|
||||
// justify-content: flex-start;
|
||||
// }
|
||||
.o_list_button {
|
||||
min-width: 32px;
|
||||
}
|
||||
.o_list_record_remove {
|
||||
padding-left: 0px !important;
|
||||
}
|
||||
|
||||
.diameter:before {
|
||||
content:"Ф";
|
||||
display:inline;
|
||||
|
||||
83
sf_base/views/api_log_views.xml
Normal file
83
sf_base/views/api_log_views.xml
Normal file
@@ -0,0 +1,83 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="view_api_request_log_tree" model="ir.ui.view">
|
||||
<field name="name">api.request.log.tree</field>
|
||||
<field name="model">api.request.log</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree>
|
||||
<field name="name"/>
|
||||
<field name="path"/>
|
||||
<field name="method"/>
|
||||
<field name="remote_addr"/>
|
||||
<field name="response_time" sum="0"/>
|
||||
<field name="requester"/>
|
||||
<field name="responser"/>
|
||||
<field name="create_date" string="请求时间"/>
|
||||
<field name="status" sum="0"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_api_request_log_form" model="ir.ui.view">
|
||||
<field name="name">api.request.log.form</field>
|
||||
<field name="model">api.request.log</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<sheet>
|
||||
<group>
|
||||
<group>
|
||||
<field name="name"/>
|
||||
<field name="path"/>
|
||||
<field name="method"/>
|
||||
<field name="remote_addr"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="response_time"/>
|
||||
<field name="status"/>
|
||||
<field name="requester"/>
|
||||
<field name="responser"/>
|
||||
<field name="create_date" string="请求时间"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="请求数据">
|
||||
<field name="request_data"/>
|
||||
</page>
|
||||
<page string="响应数据">
|
||||
<field name="response_data"/>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_api_request_log_search">
|
||||
<field name="name">api.request.log.search</field>
|
||||
<field name="model">api.request.log</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="API请求日志">
|
||||
<field name="name"/>
|
||||
<field name="requester"/>
|
||||
<field name="responser"/>
|
||||
<group>
|
||||
<filter name="name" context="{'group_by':'name'}"/>
|
||||
<filter name="requester" context="{'group_by':'requester'}"/>
|
||||
<filter name="responser" context="{'group_by':'responser'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_api_request_log" model="ir.actions.act_window">
|
||||
<field name="name">API请求日志</field>
|
||||
<field name="res_model">api.request.log</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_api_request_log"
|
||||
name="API请求日志"
|
||||
parent="base.next_id_9"
|
||||
action="action_api_request_log"
|
||||
sequence="100"/>
|
||||
</odoo>
|
||||
@@ -5,7 +5,7 @@
|
||||
<record model="ir.ui.view" id="mrs_production_process_parameter_tree">
|
||||
<field name="model">sf.production.process.parameter</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="表面工艺可选参数" create="0" delete="0">
|
||||
<tree string="工艺可选参数" create="0" delete="0">
|
||||
<field name="code"/>
|
||||
<field name="name"/>
|
||||
<field name="gain_way"/>
|
||||
@@ -15,7 +15,7 @@
|
||||
<record model="ir.ui.view" id="mrs_production_process_parameter_form">
|
||||
<field name="model">sf.production.process.parameter</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="表面工艺可选参数" create="0" delete="0" >
|
||||
<form string="工艺可选参数" create="0" delete="0" >
|
||||
<sheet>
|
||||
<div class="oe_title">
|
||||
<h1>
|
||||
@@ -104,7 +104,7 @@
|
||||
<record model="ir.ui.view" id="sf_production_process_category_form">
|
||||
<field name="model">sf.production.process.category</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="表面工艺类别" create="0" edit="0" delete="1">
|
||||
<form string="工艺类别" create="0" edit="0" delete="1">
|
||||
<sheet>
|
||||
<div class="oe_title">
|
||||
<h1>
|
||||
@@ -120,7 +120,7 @@
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="表面工艺">
|
||||
<page string="工艺">
|
||||
<field name='production_process_ids' widget="ony2many">
|
||||
<tree editable="bottom">
|
||||
<field name="code" string="编码号" readonly="1" force_save="1"/>
|
||||
@@ -139,7 +139,7 @@
|
||||
<record model="ir.ui.view" id="sf_production_process_category_tree">
|
||||
<field name="model">sf.production.process.category</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="表面工艺类别" default_order="sequence, id" create="0" edit="0" delete="1">
|
||||
<tree string="工艺类别" default_order="sequence, id" create="0" edit="0" delete="1">
|
||||
<field name="sequence" widget="handle" string="序号" readonly="1"/>
|
||||
<field name="code"/>
|
||||
<field name="name" string="名称"/>
|
||||
@@ -163,7 +163,7 @@
|
||||
<record model="ir.ui.view" id="sf_production_process_tree">
|
||||
<field name="model">sf.production.process</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="表面工艺" create="0" edit="0" delete="0">
|
||||
<tree string="工艺" create="0" edit="0" delete="0">
|
||||
<field name="sequence" string="加工顺序" readonly="1"/>
|
||||
<field name="code"/>
|
||||
<field name="name" string="名称"/>
|
||||
@@ -175,7 +175,7 @@
|
||||
<record model="ir.ui.view" id="sf_production_process_form">
|
||||
<field name="model">sf.production.process</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="表面工艺" create="0" delete="0">
|
||||
<form string="工艺" create="0" delete="0">
|
||||
<sheet>
|
||||
<div class="oe_title">
|
||||
<h1>
|
||||
@@ -395,7 +395,7 @@
|
||||
<field name="view_mode">tree,form</field>
|
||||
</record>
|
||||
<record id="sf_production_process" model="ir.actions.act_window">
|
||||
<field name="name">表面工艺</field>
|
||||
<field name="name">工艺</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">sf.production.process</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
@@ -414,13 +414,13 @@
|
||||
<!-- </record>-->
|
||||
|
||||
<record id="sf_production_process_category" model="ir.actions.act_window">
|
||||
<field name="name">表面工艺类别</field>
|
||||
<field name="name">工艺类别</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">sf.production.process.category</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
</record>
|
||||
<record id="mrs_production_process_parameter_action" model="ir.actions.act_window">
|
||||
<field name="name">表面工艺可选参数</field>
|
||||
<field name="name">工艺可选参数</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">sf.production.process.parameter</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
|
||||
@@ -158,6 +158,8 @@
|
||||
<!-- <field name="upload_model_file" widget="many2many_binary"/>-->
|
||||
<field name="model_file" widget="Viewer3D" string="模型" readonly="1" force_save="1"
|
||||
attrs="{'invisible': [('model_file', '=', False)]}"/>
|
||||
<field name="glb_url" widget="Viewer3D" string="模型" readonly="1" force_save="1"
|
||||
attrs="{'invisible': [('glb_url', '=', False)]}"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
|
||||
<menuitem
|
||||
id="menu_sf_production_process"
|
||||
name="表面工艺"
|
||||
name="工艺"
|
||||
parent="menu_sf_production_process_1"
|
||||
sequence="2"
|
||||
action="sf_production_process"
|
||||
@@ -86,7 +86,7 @@
|
||||
|
||||
<menuitem
|
||||
id="menu_sf_production_process_category"
|
||||
name="表面工艺类别"
|
||||
name="工艺类别"
|
||||
parent="menu_sf_production_process_1"
|
||||
sequence="1"
|
||||
action="sf_production_process_category"
|
||||
@@ -113,7 +113,7 @@
|
||||
|
||||
<menuitem
|
||||
id="mrs_production_process_parameter_view"
|
||||
name="表面工艺可选参数"
|
||||
name="工艺可选参数"
|
||||
parent="menu_sf_production_process_1"
|
||||
sequence="2"
|
||||
action="mrs_production_process_parameter_action"
|
||||
|
||||
@@ -29,7 +29,7 @@ class Sf_Bf_Connect(http.Controller):
|
||||
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'])
|
||||
kw['delivery_end_date'], kw['payments_way'], kw['pay_way'], model_display_version=kw.get('model_display_version'))
|
||||
i = 1
|
||||
# 给sale_order的default_code字段赋值
|
||||
aa = request.env['sale.order'].sudo().search([('name', '=', order_id.name)])
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
import hashlib
|
||||
from odoo import models
|
||||
from odoo import models, SUPERUSER_ID
|
||||
from odoo.http import request
|
||||
|
||||
__author__ = 'jinling.yang'
|
||||
@@ -48,5 +48,7 @@ class Http(models.AbstractModel):
|
||||
_logger.info('sf_secret_key:%s' % factory_secret.sf_secret_key)
|
||||
if check_sf_str != datas['HTTP_CHECKSTR']:
|
||||
raise AuthenticationError('数据校验不通过')
|
||||
# 设置管理员用户
|
||||
request.update_env(user=SUPERUSER_ID)
|
||||
else:
|
||||
raise AuthenticationError('请求参数中无token')
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
<field name="is_bill" invisible="True"/>
|
||||
<field name="logistics_status" invisible="True"/>
|
||||
<field name="logistics_way" invisible="True"/>
|
||||
<button string="物流下单" name="create_order" type="object" confirm="是否确认物流下单" class="btn-primary"
|
||||
<!-- <button string="物流下单" name="create_order" type="object" confirm="是否确认物流下单" class="btn-primary" -->
|
||||
attrs="{'invisible': ['|', '|', '|', ('check_out', '!=', 'OUT'), ('state', '!=', 'assigned'), ('is_bill', '=', True), ('logistics_way', '=', '自提')]}"/>
|
||||
<button string="获取物流面单" name="get_bill" type="object" confirm="是否获取物流面单" class="btn-primary"
|
||||
attrs="{'invisible': ['|', '|', '|', '|', ('check_out', '!=', 'OUT'), ('state', '!=', 'assigned'), ('logistics_status', '=', '2'), ('is_bill', '=', False), ('logistics_way', '=', '自提')]}"/>
|
||||
|
||||
3
sf_demand_plan/__init__.py
Normal file
3
sf_demand_plan/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from . import models
|
||||
from . import wizard
|
||||
33
sf_demand_plan/__manifest__.py
Normal file
33
sf_demand_plan/__manifest__.py
Normal file
@@ -0,0 +1,33 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
{
|
||||
'name': '机企猫智能工厂 需求计划',
|
||||
'version': '1.0',
|
||||
'summary': '智能工厂计划管理',
|
||||
'sequence': 1,
|
||||
'description': """
|
||||
在本模块,支持齐套检查与下达生产
|
||||
""",
|
||||
'category': 'sf',
|
||||
'website': 'https://www.sf.jikimo.com',
|
||||
'depends': ['sf_plan','jikimo_printing'],
|
||||
'data': [
|
||||
'security/ir.model.access.csv',
|
||||
'views/demand_plan.xml',
|
||||
'wizard/sf_demand_plan_print_wizard_view.xml',
|
||||
],
|
||||
'demo': [
|
||||
],
|
||||
'assets': {
|
||||
'web.assets_qweb': [
|
||||
],
|
||||
'web.assets_backend': [
|
||||
'sf_demand_plan/static/src/scss/style.css',
|
||||
'sf_demand_plan/static/src/js/print_demand.js',
|
||||
]
|
||||
},
|
||||
'license': 'LGPL-3',
|
||||
'installable': True,
|
||||
'application': False,
|
||||
'auto_install': False,
|
||||
}
|
||||
4
sf_demand_plan/models/__init__.py
Normal file
4
sf_demand_plan/models/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import sf_production_demand_plan
|
||||
from . import sale_order
|
||||
29
sf_demand_plan/models/sale_order.py
Normal file
29
sf_demand_plan/models/sale_order.py
Normal file
@@ -0,0 +1,29 @@
|
||||
from odoo import models, fields, api, _
|
||||
|
||||
|
||||
class ReSaleOrder(models.Model):
|
||||
_inherit = 'sale.order'
|
||||
|
||||
mrp_production_ids = fields.Many2many(
|
||||
'mrp.production',
|
||||
compute='_compute_mrp_production_ids',
|
||||
string='与此销售订单相关联的制造订单',
|
||||
groups='mrp.group_mrp_user', store=True)
|
||||
|
||||
def sale_order_create_line(self, product, item):
|
||||
ret = super(ReSaleOrder, self).sale_order_create_line(product, item)
|
||||
vals = {
|
||||
'sale_order_id': ret.order_id.id,
|
||||
'sale_order_line_id': ret.id,
|
||||
}
|
||||
demand_plan = self.env['sf.production.demand.plan'].sudo().create(vals)
|
||||
if demand_plan.product_id.machining_drawings_name:
|
||||
filename_url = demand_plan.product_id.machining_drawings_name.rsplit('.', 1)[0]
|
||||
wizard_vals = {
|
||||
'demand_plan_id': demand_plan.id,
|
||||
'model_id': demand_plan.model_id,
|
||||
'filename_url': filename_url,
|
||||
'type': '1',
|
||||
}
|
||||
self.env['sf.demand.plan.print.wizard'].sudo().create(wizard_vals)
|
||||
return ret
|
||||
553
sf_demand_plan/models/sf_production_demand_plan.py
Normal file
553
sf_demand_plan/models/sf_production_demand_plan.py
Normal file
@@ -0,0 +1,553 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import ast
|
||||
import json
|
||||
from odoo import models, fields, api, _
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.tools import float_compare
|
||||
from datetime import datetime, timedelta
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class SfProductionDemandPlan(models.Model):
|
||||
_name = 'sf.production.demand.plan'
|
||||
_description = 'sf_production_demand_plan'
|
||||
|
||||
def _get_machining_precision(self):
|
||||
machinings = self.env['sf.machining.accuracy'].sudo().search([])
|
||||
list = [(m.sync_id, m.name) for m in machinings]
|
||||
return list
|
||||
|
||||
priority = fields.Selection([
|
||||
('1', '紧急'),
|
||||
('2', '高'),
|
||||
('3', '中'),
|
||||
('4', '低'),
|
||||
], string='优先级', default='3')
|
||||
status = fields.Selection([
|
||||
('10', '草稿'),
|
||||
('20', '待确认'),
|
||||
('30', '需求确认'),
|
||||
('50', '待下达生产'),
|
||||
('60', '已下达'),
|
||||
('100', '取消'),
|
||||
], string='状态', compute='_compute_status', store=True)
|
||||
sale_order_id = fields.Many2one(comodel_name="sale.order",
|
||||
string="销售订单", readonly=True)
|
||||
sale_order_line_id = fields.Many2one(comodel_name="sale.order.line",
|
||||
string="销售订单明细", readonly=True)
|
||||
sale_order_line_number = fields.Char(string='销售订单行', compute='_compute_sale_order_line_number', store=True)
|
||||
company_id = fields.Many2one(
|
||||
related='sale_order_id.company_id',
|
||||
store=True, index=True, precompute=True)
|
||||
customer_name = fields.Char('客户', related='sale_order_id.customer_name')
|
||||
order_remark = fields.Text(related='sale_order_id.remark',
|
||||
string="订单备注", store=True)
|
||||
glb_url = fields.Char(related='sale_order_line_id.glb_url', string='glb文件地址')
|
||||
product_id = fields.Many2one(
|
||||
comodel_name='product.product',
|
||||
related='sale_order_line_id.product_id',
|
||||
string='产品', store=True, index=True)
|
||||
model_id = fields.Char('模型ID', related='product_id.model_id')
|
||||
part_name = fields.Char('零件名称', related='product_id.part_name')
|
||||
part_number = fields.Char('零件图号', compute='_compute_part_number', store=True)
|
||||
is_incoming_material = fields.Boolean('客供料', related='sale_order_line_id.is_incoming_material', store=True)
|
||||
supply_method = fields.Selection([
|
||||
('automation', "自动化产线加工"),
|
||||
('manual', "人工线下加工"),
|
||||
('purchase', "外购"),
|
||||
('outsourcing', "委外加工"),
|
||||
], string='供货方式', related='sale_order_line_id.supply_method', store=True)
|
||||
product_uom_qty = fields.Float(
|
||||
string="需求数量",
|
||||
related='sale_order_line_id.product_uom_qty', store=True)
|
||||
deadline_of_delivery = fields.Date('客户交期', related='sale_order_line_id.delivery_end_date', store=True)
|
||||
inventory_quantity_auto_apply = fields.Float(
|
||||
string="成品库存",
|
||||
compute='_compute_inventory_quantity_auto_apply'
|
||||
)
|
||||
qty_delivered = fields.Float(
|
||||
"交货数量", related='sale_order_line_id.qty_delivered')
|
||||
qty_to_deliver = fields.Float(
|
||||
"待交货数量", related='sale_order_line_id.qty_to_deliver')
|
||||
model_long = fields.Char('尺寸(mm)', compute='_compute_model_long')
|
||||
blank_type = fields.Selection([('圆料', '圆料'), ('方料', '方料')], string='坯料分类',
|
||||
related='product_id.blank_type')
|
||||
embryo_long = fields.Char('坯料尺寸(mm)', compute='_compute_embryo_long')
|
||||
materials_id = fields.Char('材料', compute='_compute_materials_id', store=True)
|
||||
model_machining_precision = fields.Selection(selection=_get_machining_precision, string='精度',
|
||||
related='product_id.model_machining_precision')
|
||||
model_process_parameters_ids = fields.Many2many('sf.production.process.parameter',
|
||||
'plan_process_parameter_rel',
|
||||
string='表面工艺',
|
||||
compute='_compute_model_process_parameters_ids'
|
||||
, store=True
|
||||
)
|
||||
product_remark = fields.Char("产品备注", related='product_id.model_remark')
|
||||
order_code = fields.Char('E-SHOP订单号', related='sale_order_id.order_code')
|
||||
order_state = fields.Selection(
|
||||
string='订单状态',
|
||||
related='sale_order_line_id.state')
|
||||
route_id = fields.Many2one('stock.route', string='路线', related='sale_order_line_id.route_id', store=True)
|
||||
contract_date = fields.Date('合同日期', related='sale_order_id.contract_date')
|
||||
date_order = fields.Datetime('下单日期', related='sale_order_id.date_order')
|
||||
contract_code = fields.Char('合同号', related='sale_order_id.contract_code', store=True)
|
||||
plan_remark = fields.Text("计划备注")
|
||||
material_check = fields.Selection([
|
||||
('0', "未齐套"),
|
||||
('1', "已齐套"),
|
||||
], string='投料齐套检查', compute='_compute_material_check', store=True)
|
||||
processing_time = fields.Char('程序工时', readonly=True)
|
||||
planned_start_date = fields.Date('计划开工日期')
|
||||
actual_start_date = fields.Datetime('实际开工日期', compute='_compute_actual_start_date', store=True)
|
||||
actual_end_date = fields.Datetime('实际完工日期', compute='_compute_actual_end_date', store=True)
|
||||
print_count = fields.Char('打印次数', default='T0C0', readonly=True)
|
||||
sequence = fields.Integer('序号')
|
||||
|
||||
hide_action_open_mrp_production = fields.Boolean(
|
||||
string='显示待工艺确认按钮',
|
||||
compute='_compute_hid_button',
|
||||
default=False
|
||||
)
|
||||
|
||||
hide_action_purchase_orders = fields.Boolean(
|
||||
string='显示采购按钮',
|
||||
compute='_compute_hide_action_purchase_orders',
|
||||
default=False
|
||||
)
|
||||
|
||||
hide_action_stock_picking = fields.Boolean(
|
||||
string='显示调拨单按钮',
|
||||
compute='_compute_hide_action_stock_picking',
|
||||
default=False
|
||||
)
|
||||
|
||||
hide_action_outsourcing_stock_picking = fields.Boolean(
|
||||
string='委外显示调拨单按钮',
|
||||
compute='_compute_hide_action_stock_picking',
|
||||
default=False
|
||||
)
|
||||
|
||||
hide_action_view_programming = fields.Boolean(
|
||||
string='显示编程单按钮',
|
||||
compute='_compute_hid_button',
|
||||
default=False
|
||||
)
|
||||
|
||||
outsourcing_purchase_request = fields.Char('委外采购申请单')
|
||||
|
||||
@api.depends('sale_order_id.state', 'sale_order_id.mrp_production_ids.schedule_state', 'sale_order_id.order_line',
|
||||
'sale_order_id.mrp_production_ids.state')
|
||||
def _compute_status(self):
|
||||
for record in self:
|
||||
if record.sale_order_id:
|
||||
sale_order_state = record.sale_order_id.state
|
||||
if sale_order_state in ('draft', 'sent', 'supply method'):
|
||||
record.status = '20' # 待确认
|
||||
if record.supply_method in ('purchase', 'outsourcing') and sale_order_state in (
|
||||
'sale', 'processing', 'physical_distribution', 'delivered',
|
||||
'done') and sale_order_state != 'cancel':
|
||||
record.status = '60' # 已下达
|
||||
if record.supply_method in ('automation', 'manual'):
|
||||
if sale_order_state in (
|
||||
'sale', 'processing', 'physical_distribution', 'delivered',
|
||||
'done') and sale_order_state != 'cancel':
|
||||
record.status = '30' # 需求确认
|
||||
# 检查所有制造订单的排程单状态,有一个为待排程状态,就为待下达生产
|
||||
pending_productions = record.sale_order_id.mrp_production_ids.filtered(
|
||||
lambda p: p.state == 'confirmed' and p.product_id.id == record.product_id.id
|
||||
)
|
||||
if pending_productions:
|
||||
record.status = '50' # 待下达生产
|
||||
# 检查所有制造订单的排程单状态
|
||||
if record.sale_order_id.mrp_production_ids:
|
||||
product_productions = record.sale_order_id.mrp_production_ids.filtered(
|
||||
lambda p: p.product_id.id == record.product_id.id
|
||||
)
|
||||
if product_productions and all(order.schedule_state != '未排' for order in product_productions):
|
||||
record.status = '60' # 已下达
|
||||
if sale_order_state == 'cancel' or not record.sale_order_line_id:
|
||||
record.status = '100' # 取消
|
||||
|
||||
@api.depends('sale_order_line_id.product_id.name')
|
||||
def _compute_sale_order_line_number(self):
|
||||
for line in self:
|
||||
if line.product_id:
|
||||
line.sale_order_line_number = line.sale_order_line_id.product_id.name[-1]
|
||||
else:
|
||||
line.sale_order_line_number = None
|
||||
|
||||
@api.depends('product_id.part_number', 'product_id.model_name')
|
||||
def _compute_part_number(self):
|
||||
for line in self:
|
||||
if line.product_id:
|
||||
if line.product_id.part_number:
|
||||
line.part_number = line.product_id.part_number
|
||||
else:
|
||||
if line.product_id.model_name:
|
||||
line.part_number = line.product_id.model_name.rsplit('.', 1)[0]
|
||||
else:
|
||||
line.part_number = None
|
||||
|
||||
@api.depends('product_id.length', 'product_id.width', 'product_id.height')
|
||||
def _compute_model_long(self):
|
||||
for line in self:
|
||||
if line.product_id:
|
||||
line.model_long = f"{line.product_id.length}*{line.product_id.width}*{line.product_id.height}"
|
||||
else:
|
||||
line.model_long = None
|
||||
|
||||
@api.depends('product_id.model_long', 'product_id.model_width', 'product_id.model_height', 'product_id.blank_type')
|
||||
def _compute_embryo_long(self):
|
||||
for line in self:
|
||||
if line.product_id:
|
||||
if line.product_id.blank_type == '圆料':
|
||||
line.embryo_long = f"Ø{round(line.product_id.model_width, 3)}*{round(line.product_id.model_long, 3)}"
|
||||
else:
|
||||
line.embryo_long = f"{round(line.product_id.model_long, 3)}*{round(line.product_id.model_width, 3)}*{round(line.product_id.model_height, 3)}"
|
||||
else:
|
||||
line.embryo_long = None
|
||||
|
||||
@api.depends('product_id.materials_id')
|
||||
def _compute_materials_id(self):
|
||||
for line in self:
|
||||
if line.product_id:
|
||||
line.materials_id = f"{line.product_id.materials_id.name}/{line.product_id.materials_type_id.name}"
|
||||
else:
|
||||
line.materials_id = None
|
||||
|
||||
@api.depends('product_id.model_process_parameters_ids')
|
||||
def _compute_model_process_parameters_ids(self):
|
||||
for line in self:
|
||||
if line.product_id and line.product_id.model_process_parameters_ids:
|
||||
line.model_process_parameters_ids = [(6, 0, line.product_id.model_process_parameters_ids.ids)]
|
||||
else:
|
||||
line.model_process_parameters_ids = [(5, 0, 0)]
|
||||
|
||||
def _compute_inventory_quantity_auto_apply(self):
|
||||
location_id = self.env['stock.location'].search([('name', '=', '成品存货区')], limit=1).id
|
||||
product_ids = self.mapped('product_id').ids
|
||||
if product_ids:
|
||||
quant_data = self.env['stock.quant'].read_group(
|
||||
domain=[
|
||||
('product_id', 'in', product_ids),
|
||||
('location_id', '=', location_id)
|
||||
],
|
||||
fields=['product_id', 'inventory_quantity_auto_apply'],
|
||||
groupby=['product_id']
|
||||
)
|
||||
quantity_map = {item['product_id'][0]: item['inventory_quantity_auto_apply'] for item in quant_data}
|
||||
else:
|
||||
quantity_map = {}
|
||||
for line in self:
|
||||
if line.product_id:
|
||||
line.inventory_quantity_auto_apply = quantity_map.get(line.product_id.id, 0.0)
|
||||
else:
|
||||
line.inventory_quantity_auto_apply = 0.0
|
||||
|
||||
@api.depends('sale_order_id.mrp_production_ids.workorder_ids.date_start')
|
||||
def _compute_actual_start_date(self):
|
||||
for record in self:
|
||||
if record.sale_order_id and record.sale_order_id.mrp_production_ids:
|
||||
manufacturing_orders = record.sale_order_id.mrp_production_ids.filtered(
|
||||
lambda mo: mo.product_id == record.product_id)
|
||||
if manufacturing_orders:
|
||||
start_dates = [
|
||||
workorder.date_start for mo in manufacturing_orders
|
||||
for workorder in mo.workorder_ids if workorder.date_start
|
||||
]
|
||||
record.actual_start_date = min(start_dates) if start_dates else None
|
||||
else:
|
||||
record.actual_start_date = None
|
||||
else:
|
||||
record.actual_start_date = None
|
||||
|
||||
@api.depends('sale_order_id.mrp_production_ids.workorder_ids.state',
|
||||
'sale_order_id.mrp_production_ids.workorder_ids.date_finished')
|
||||
def _compute_actual_end_date(self):
|
||||
for record in self:
|
||||
if record.sale_order_id and record.sale_order_id.mrp_production_ids:
|
||||
manufacturing_orders = record.sale_order_id.mrp_production_ids.filtered(
|
||||
lambda mo: mo.product_id == record.product_id)
|
||||
finished_orders = manufacturing_orders.filtered(lambda mo: mo.state == 'done')
|
||||
sum_product_qty = sum(finished_orders.mapped('product_qty'))
|
||||
if finished_orders and float_compare(sum_product_qty, record.product_uom_qty,
|
||||
precision_rounding=record.product_id.uom_id.rounding) >= 0:
|
||||
end_dates = [
|
||||
workorder.date_finished for mo in finished_orders
|
||||
for workorder in mo.workorder_ids if workorder.date_finished
|
||||
]
|
||||
record.actual_end_date = max(end_dates) if end_dates else None
|
||||
else:
|
||||
record.actual_end_date = None
|
||||
else:
|
||||
record.actual_end_date = None
|
||||
|
||||
@api.depends('sale_order_id.mrp_production_ids.move_raw_ids.reserved_availability')
|
||||
def _compute_material_check(self):
|
||||
for record in self:
|
||||
if record.sale_order_id and record.sale_order_id.mrp_production_ids:
|
||||
manufacturing_orders = record.sale_order_id.mrp_production_ids.filtered(
|
||||
lambda mo: mo.product_id == record.product_id)
|
||||
|
||||
if manufacturing_orders and manufacturing_orders.move_raw_ids:
|
||||
# 获取完成的制造订单
|
||||
done_manufacturing = manufacturing_orders.filtered(lambda mo: mo.state == 'done')
|
||||
product_qty = sum(done_manufacturing.mapped('product_qty'))
|
||||
# 需求数量-完成数量
|
||||
product_uom_qty = record.product_uom_qty - product_qty
|
||||
total_reserved_availability = sum(manufacturing_orders.mapped('move_raw_ids.reserved_availability'))
|
||||
if float_compare(total_reserved_availability, product_uom_qty,
|
||||
precision_rounding=record.product_id.uom_id.rounding) >= 0:
|
||||
record.material_check = '1' # 已齐套
|
||||
else:
|
||||
record.material_check = '0' # 未齐套
|
||||
else:
|
||||
record.material_check = None
|
||||
else:
|
||||
record.material_check = None
|
||||
|
||||
@api.constrains('planned_start_date')
|
||||
def _check_planned_start_date(self):
|
||||
for record in self:
|
||||
if record.planned_start_date and record.planned_start_date < fields.Date.today():
|
||||
raise ValidationError("计划开工日期必须大于或等于今天。")
|
||||
|
||||
def release_production_order(self):
|
||||
if not self.planned_start_date:
|
||||
raise ValidationError("请先填写计划开工日期")
|
||||
pro_plan_list = self.env['sf.production.plan'].search(
|
||||
[('product_id', '=', self.product_id.id), ('state', '=', 'draft')])
|
||||
sf_production_line = self.env['sf.production.line'].sudo().search(
|
||||
[('name', '=', '1#CNC自动生产线')], limit=1)
|
||||
if sf_production_line:
|
||||
now = datetime.now()
|
||||
time_part = (now + timedelta(hours=2)).time()
|
||||
date_part = fields.Date.from_string(self.planned_start_date)
|
||||
date_planned_start = datetime.combine(date_part, time_part)
|
||||
pro_plan_list.production_line_id = sf_production_line.id
|
||||
pro_plan_list.date_planned_start = date_planned_start
|
||||
self._do_production_schedule(pro_plan_list)
|
||||
|
||||
def _do_production_schedule(self, pro_plan_list):
|
||||
for pro_plan in pro_plan_list:
|
||||
pro_plan.do_production_schedule()
|
||||
|
||||
|
||||
def button_action_print(self):
|
||||
return {
|
||||
'res_model': 'sf.demand.plan.print.wizard',
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _("打印"),
|
||||
'domain': [('demand_plan_id', 'in', self.ids)],
|
||||
'views': [[self.env.ref('sf_demand_plan.action_plan_print_tree').id, 'list']],
|
||||
'target': 'new',
|
||||
}
|
||||
|
||||
@api.depends('sale_order_id.mrp_production_ids.state', 'sale_order_id.mrp_production_ids.programming_state')
|
||||
def _compute_hid_button(self):
|
||||
for record in self:
|
||||
mrp_production_ids = record.sale_order_id.mrp_production_ids.filtered(
|
||||
lambda p: p.state == 'technology_to_confirmed' and p.product_id.id == record.product_id.id
|
||||
)
|
||||
record.hide_action_open_mrp_production = bool(mrp_production_ids) and record.supply_method in (
|
||||
'automation', 'manual')
|
||||
programming_mrp_production_ids = record.sale_order_id.mrp_production_ids.filtered(
|
||||
lambda p: p.programming_state == '编程中' and p.product_id.id == record.product_id.id
|
||||
)
|
||||
record.hide_action_view_programming = bool(programming_mrp_production_ids)
|
||||
|
||||
def _compute_hide_action_purchase_orders(self):
|
||||
for record in self:
|
||||
record.hide_action_purchase_orders = False
|
||||
outsourcing_purchase_request = []
|
||||
if record.supply_method in ('automation',
|
||||
'manual') and record.material_check == '0' and not record.sale_order_line_id.is_incoming_material:
|
||||
mrp_production = record.sale_order_id.mrp_production_ids.filtered(
|
||||
lambda p: p.product_id.id == record.product_id.id
|
||||
).sorted(key=lambda p: p.id)
|
||||
if mrp_production:
|
||||
raw_materials = mrp_production.mapped('move_raw_ids.product_id')
|
||||
if raw_materials:
|
||||
purchase_orders = self.env['purchase.order'].sudo().search([
|
||||
('state', '=', 'purchase'),
|
||||
('order_line.product_id', 'in', raw_materials.ids)
|
||||
])
|
||||
total_purchase_quantity = sum(
|
||||
sum(
|
||||
order.order_line.filtered(
|
||||
lambda line: line.product_id in raw_materials
|
||||
).mapped('product_qty')
|
||||
)
|
||||
for order in purchase_orders
|
||||
)
|
||||
if float_compare(total_purchase_quantity, record.product_uom_qty,
|
||||
precision_rounding=record.product_id.uom_id.rounding) == -1:
|
||||
pr_ids = self.env['purchase.request'].sudo().search(
|
||||
[('line_ids.product_id', 'in', raw_materials.ids), ('state', '!=', 'done')])
|
||||
outsourcing_purchase_request.extend(pr_ids.ids)
|
||||
elif record.supply_method in ('purchase', 'outsourcing'):
|
||||
purchase_orders = self.env['purchase.order'].sudo().search([
|
||||
('state', 'in', ('purchase', 'done')),
|
||||
('order_line.product_id', '=', record.product_id.id)
|
||||
])
|
||||
total_purchase_quantity = sum(
|
||||
sum(
|
||||
order.order_line.filtered(
|
||||
lambda line: line.product_id in record.product_id
|
||||
).mapped('product_qty')
|
||||
)
|
||||
for order in purchase_orders
|
||||
)
|
||||
|
||||
if float_compare(total_purchase_quantity, record.product_uom_qty,
|
||||
precision_rounding=record.product_id.uom_id.rounding) == -1:
|
||||
pr_ids = self.env['purchase.request'].sudo().search(
|
||||
[('origin', 'like', record.sale_order_id.name), ('state', '!=', 'done')])
|
||||
outsourcing_purchase_request.extend(pr_ids.ids)
|
||||
if record.supply_method == 'outsourcing' and not record.sale_order_line_id.is_incoming_material:
|
||||
bom_line_ids = record.product_id.bom_ids.bom_line_ids
|
||||
if bom_line_ids:
|
||||
# BOM_数量
|
||||
total_product_qty = sum(line.product_qty for line in bom_line_ids)
|
||||
bom_product_ids = bom_line_ids.mapped('product_id')
|
||||
product_purchase_orders = self.env['purchase.order'].sudo().search([
|
||||
('state', 'in', ('purchase', 'done')),
|
||||
('order_line.product_id', 'in', bom_product_ids.ids)
|
||||
])
|
||||
# 购订单_数量
|
||||
total_outsourcing_purchase_quantity = sum(
|
||||
sum(
|
||||
order.order_line.filtered(
|
||||
lambda line: line.product_id in bom_product_ids
|
||||
).mapped('product_qty')
|
||||
)
|
||||
for order in product_purchase_orders
|
||||
)
|
||||
quantity = total_outsourcing_purchase_quantity / total_product_qty
|
||||
if float_compare(quantity, record.product_uom_qty,
|
||||
precision_rounding=record.product_id.uom_id.rounding) == -1:
|
||||
purchase_request = self.env['purchase.request'].sudo().search(
|
||||
[('line_ids.product_id', 'in', bom_product_ids.ids),
|
||||
('line_ids.purchase_state', 'not in', ('purchase', 'done')), ('state', '!=', 'done')])
|
||||
outsourcing_purchase_request.extend(purchase_request.ids)
|
||||
record.outsourcing_purchase_request = json.dumps(outsourcing_purchase_request)
|
||||
if outsourcing_purchase_request:
|
||||
record.hide_action_purchase_orders = True
|
||||
|
||||
@api.depends('sale_order_id.mrp_production_ids.picking_ids.state', 'sale_order_id.picking_ids.state')
|
||||
def _compute_hide_action_stock_picking(self):
|
||||
for record in self:
|
||||
record.hide_action_stock_picking = False
|
||||
record.hide_action_outsourcing_stock_picking = False
|
||||
if record.supply_method in ('automation', 'manual'):
|
||||
manufacturing_orders = record.sale_order_id.mrp_production_ids.filtered(
|
||||
lambda p: p.product_id.id == record.product_id.id
|
||||
)
|
||||
record.hide_action_stock_picking = bool(manufacturing_orders.mapped('picking_ids').filtered(
|
||||
lambda p: p.state == 'assigned'))
|
||||
elif record.supply_method in ('purchase', 'outsourcing'):
|
||||
assigned_picking_ids = record.sale_order_id.picking_ids.filtered(
|
||||
lambda
|
||||
p: p.state == 'assigned' and p.picking_type_id.name != '发料出库' and p.move_line_ids.product_id in record.product_id)
|
||||
if record.supply_method == 'outsourcing':
|
||||
outsourcing_assigned_picking_ids = record.get_outsourcing_picking_ids()
|
||||
record.hide_action_outsourcing_stock_picking = outsourcing_assigned_picking_ids
|
||||
record.hide_action_stock_picking = assigned_picking_ids or outsourcing_assigned_picking_ids
|
||||
else:
|
||||
record.hide_action_stock_picking = assigned_picking_ids
|
||||
|
||||
def get_outsourcing_picking_ids(self):
|
||||
order_ids = self.env['purchase.order'].sudo().search(
|
||||
[('order_line.product_id', 'in', self.product_id.ids),
|
||||
('purchase_type', '=', 'outsourcing')])
|
||||
outsourcing_picking_ids = order_ids._get_subcontracting_resupplies()
|
||||
outsourcing_assigned_picking_ids = outsourcing_picking_ids.filtered(lambda p: p.state == 'assigned')
|
||||
return outsourcing_assigned_picking_ids
|
||||
|
||||
def action_open_sale_order(self):
|
||||
self.ensure_one()
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'sale.order',
|
||||
'res_id': self.sale_order_id.id,
|
||||
'view_mode': 'form',
|
||||
}
|
||||
|
||||
def action_open_mrp_production(self):
|
||||
self.ensure_one()
|
||||
mrp_production_ids = self.sale_order_id.mrp_production_ids.filtered(
|
||||
lambda p: p.state == 'technology_to_confirmed' and p.product_id.id == self.product_id.id
|
||||
)
|
||||
action = {
|
||||
'res_model': 'mrp.production',
|
||||
'type': 'ir.actions.act_window',
|
||||
}
|
||||
if len(mrp_production_ids) == 1:
|
||||
action.update({
|
||||
'view_mode': 'form',
|
||||
'res_id': mrp_production_ids.id,
|
||||
})
|
||||
else:
|
||||
action.update({
|
||||
'name': _("制造订单列表"),
|
||||
'domain': [('id', 'in', mrp_production_ids.ids)],
|
||||
'view_mode': 'tree,form',
|
||||
})
|
||||
return action
|
||||
|
||||
def action_view_purchase_request(self):
|
||||
self.ensure_one()
|
||||
pr_ids = self.env['purchase.request'].sudo().search(
|
||||
[('id', 'in', ast.literal_eval(self.outsourcing_purchase_request))])
|
||||
action = {
|
||||
'res_model': 'purchase.request',
|
||||
'type': 'ir.actions.act_window',
|
||||
}
|
||||
if len(pr_ids) == 1:
|
||||
action.update({
|
||||
'view_mode': 'form',
|
||||
'res_id': pr_ids[0].id,
|
||||
})
|
||||
else:
|
||||
action.update({
|
||||
'name': _("采购申请"),
|
||||
'domain': [('id', 'in', pr_ids.ids)],
|
||||
'view_mode': 'tree,form',
|
||||
})
|
||||
return action
|
||||
|
||||
def action_view_stock_picking(self):
|
||||
self.ensure_one()
|
||||
action = self.env["ir.actions.actions"]._for_xml_id("stock.action_picking_tree_all")
|
||||
picking_ids = None
|
||||
if self.supply_method in ('automation', 'manual'):
|
||||
mrp_production_ids = self.sale_order_id.mrp_production_ids.filtered(
|
||||
lambda p: p.product_id.id == self.product_id.id
|
||||
)
|
||||
picking_ids = mrp_production_ids.mapped('picking_ids').filtered(
|
||||
lambda p: p.state == 'assigned')
|
||||
elif self.supply_method in ('purchase', 'outsourcing'):
|
||||
picking_ids = self.sale_order_id.picking_ids.filtered(
|
||||
lambda
|
||||
p: p.state == 'assigned' and p.picking_type_id.name != '发料出库' and p.move_line_ids.product_id in self.product_id)
|
||||
if self.supply_method == 'outsourcing' and self.hide_action_outsourcing_stock_picking:
|
||||
picking_ids = picking_ids.union(self.get_outsourcing_picking_ids())
|
||||
if picking_ids:
|
||||
if len(picking_ids) > 1:
|
||||
action['domain'] = [('id', 'in', picking_ids.ids)]
|
||||
elif picking_ids:
|
||||
action['res_id'] = picking_ids.id
|
||||
action['views'] = [(self.env.ref('stock.view_picking_form').id, 'form')]
|
||||
if 'views' in action:
|
||||
action['views'] += [(state, view) for state, view in action['views'] if view != 'form']
|
||||
return action
|
||||
|
||||
def action_view_programming(self):
|
||||
self.ensure_one()
|
||||
programming_mrp_production_ids = self.sale_order_id.mrp_production_ids.filtered(
|
||||
lambda p: p.programming_state == '编程中' and p.product_id.id == self.product_id.id
|
||||
).mapped('programming_no')
|
||||
if programming_mrp_production_ids:
|
||||
programming_no = list(set(programming_mrp_production_ids))
|
||||
numbers_str = "、".join(programming_no)
|
||||
raise ValidationError(f"编程单号:{numbers_str},请去云平台处理")
|
||||
6
sf_demand_plan/security/ir.model.access.csv
Normal file
6
sf_demand_plan/security/ir.model.access.csv
Normal file
@@ -0,0 +1,6 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_sf_production_demand_plan,sf.production.demand.plan,model_sf_production_demand_plan,base.group_user,1,0,0,0
|
||||
access_sf_production_demand_plan_for_dispatch,sf.production.demand.plan for dispatch,model_sf_production_demand_plan,sf_base.group_plan_dispatch,1,1,0,0
|
||||
|
||||
access_sf_demand_plan_print_wizard,sf.demand.plan.print.wizard,model_sf_demand_plan_print_wizard,base.group_user,1,0,0,0
|
||||
access_sf_demand_plan_print_wizard_for_dispatch,sf.demand.plan.print.wizard for dispatch,model_sf_demand_plan_print_wizard,sf_base.group_plan_dispatch,1,1,0,0
|
||||
|
256
sf_demand_plan/static/src/js/print_demand.js
Normal file
256
sf_demand_plan/static/src/js/print_demand.js
Normal file
@@ -0,0 +1,256 @@
|
||||
odoo.define('sf_demand.print_demand', function (require) {
|
||||
"use strict";
|
||||
|
||||
var ListController = require('web.ListController');
|
||||
var ListRenderer = require('web.ListRenderer');
|
||||
var ListView = require('web.ListView');
|
||||
var viewRegistry = require('web.view_registry');
|
||||
var { url } = require("@web/core/utils/urls")
|
||||
|
||||
var CustomListRenderer = ListRenderer.extend({
|
||||
_render: function () {
|
||||
var self = this;
|
||||
this.getParent()?.$buttons.hide();
|
||||
|
||||
return this._super.apply(this, arguments).then(function () {
|
||||
if(!self.state.data || !self.state.data.length) return
|
||||
// 添加图片预览容器到页面左侧
|
||||
if (!$('.table-image-preview-container').length) {
|
||||
self.$el.parent().addClass('custom-table-image-container')
|
||||
self.$el.before(
|
||||
`<div class="custom-preview-container">
|
||||
<img class="table-image-preview-container" src="" />
|
||||
<iframe class="table-image-preview-container" src=""/>
|
||||
</div>`
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
if(!$('.denmand_set').length) {
|
||||
|
||||
const checked = self.getParent().radioCheck || 'all'
|
||||
|
||||
self.$el.prepend(`
|
||||
<form class="denmand_set">
|
||||
<span>更多设置:</span>
|
||||
<input type="radio" id="male" name="set" value="图纸">
|
||||
<label for="male">图纸</label>
|
||||
<input type="radio" id="female" name="set" value="程序单">
|
||||
<label for="female">程序单</label>
|
||||
<input type="radio" id="other" name="set" value="all" >
|
||||
<label for="other">图纸/程序单</label>
|
||||
</form>
|
||||
`)
|
||||
setTimeout(() => {
|
||||
$(`input[name=set][value=${checked}]`).prop('checked', true)
|
||||
$('.denmand_set').trigger('click')
|
||||
}, 100);
|
||||
self.$el.prepend(`
|
||||
<div class="print-button-container" style="margin-bottom:10px;">
|
||||
<button class="btn btn-primary o_print_custom">
|
||||
<i class="fa fa-print"></i> 打印
|
||||
</button>
|
||||
<button class="btn btn-secondary o_cancel_custom">
|
||||
取消
|
||||
</button>
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
});
|
||||
},
|
||||
start: function() {
|
||||
setTimeout(() => {
|
||||
this.$el.find('.o_data_row').eq(0).trigger('click')
|
||||
this.getParent().$el?.find('.o_cp_top_right,.o_cp_bottom').hide()
|
||||
}, 500);
|
||||
return this._super();
|
||||
},
|
||||
events: _.extend({}, ListRenderer.prototype.events, {
|
||||
'click .o_data_row': '_onCustomRowClick',
|
||||
'click .o_print_custom': '_onPrintClick',
|
||||
'click .o_cancel_custom': '_onCancelClick',
|
||||
'click .denmand_set': '_onDenmandChange',
|
||||
}),
|
||||
_onCancelClick() {
|
||||
this.getParent()?.getParent()?.dialogs.closeAll()
|
||||
},
|
||||
_onCustomRowClick: async function (ev) {
|
||||
var self = this;
|
||||
var $row = $(ev.currentTarget);
|
||||
var index = $row.index();
|
||||
var data = this.state.data[index];
|
||||
if(data.fileData?.fileUrl) {
|
||||
|
||||
} else {
|
||||
data.fileData = { }
|
||||
if(data.res_id) {
|
||||
// 正确获取 ORM 服务的方式
|
||||
// var orm = this.getParent().getParent().env.services
|
||||
const key = data.data.type == 1 ? 'machining_drawings' : 'cnc_worksheet'
|
||||
const attachment = await this._rpc({
|
||||
model: 'ir.binary',
|
||||
method: 'attachment_info',
|
||||
args: [
|
||||
data.model,
|
||||
data.res_id,
|
||||
key
|
||||
],
|
||||
})
|
||||
|
||||
if (attachment) {
|
||||
Object.assign(data.fileData, attachment)
|
||||
this._getTypeInfo(attachment.mimetype, data.fileData)
|
||||
}
|
||||
const fileUrl = this.getFileUrl(data.fileData.attachment_type, data, key)
|
||||
data.fileData.fileUrl = fileUrl
|
||||
}
|
||||
}
|
||||
$('.table-image-preview-container').hide()
|
||||
if(data.fileData.attachment_type == 'iframe') {
|
||||
|
||||
$('iframe.table-image-preview-container').attr('src', decodeURIComponent(data.fileData.fileUrl) ).show()
|
||||
} else {
|
||||
$('img.table-image-preview-container').attr('src', data.fileData.fileUrl).show()
|
||||
}
|
||||
},
|
||||
getSelectedIds: function() {
|
||||
return this.state.data.filter(_ => !_.hide).map(_ => {
|
||||
return _.data.id
|
||||
})
|
||||
},
|
||||
_onPrintClick(e) {
|
||||
var print_ids = this.getSelectedIds();
|
||||
this._rpc({
|
||||
model: 'sf.demand.plan.print.wizard',
|
||||
method: 'demand_plan_print',
|
||||
args: [ print_ids ] ,
|
||||
// context: this.state.getContext()
|
||||
}).then((e) => {
|
||||
|
||||
this.getParent()?.getParent()?.env.services?.notification.notify( {
|
||||
type: 'info',
|
||||
message: e.message,
|
||||
})
|
||||
// self.do_notify("成功", "打印任务已发送到打印机");
|
||||
}).catch(function(error) {
|
||||
console.error("打印错误:", error);
|
||||
// self.do_warn("打印失败", error.data.message || "未知错误");
|
||||
}).finally(e => {
|
||||
this.getParent().reload()
|
||||
})
|
||||
|
||||
},
|
||||
_onDenmandChange(e) {
|
||||
const isChecked = $(e.currentTarget).find('input:checked').val()
|
||||
this.getParent().radioCheck = isChecked
|
||||
this.$el.find('tbody').find('.o_data_row').show()
|
||||
|
||||
this.state.data.forEach(_ => {
|
||||
_.hide = false
|
||||
})
|
||||
const self = this
|
||||
if(!isChecked || isChecked == 'all') return
|
||||
this.$el.find('tbody').children('.o_data_row').each(function() {
|
||||
if($(this).find('td[name=type]').text() != isChecked) {
|
||||
const i = $(this).index()
|
||||
|
||||
self.state.data[i].hide = true
|
||||
$(this).hide()
|
||||
}
|
||||
})
|
||||
},
|
||||
getFileUrl(attachment_type, data, key) {
|
||||
let fileUrl
|
||||
switch (attachment_type) {
|
||||
case 'image':
|
||||
const timer = +new Date()
|
||||
fileUrl = url("/web/image", {
|
||||
model: data.model,
|
||||
id: data.res_id,
|
||||
field: key,
|
||||
unique: '',
|
||||
timer
|
||||
});
|
||||
|
||||
break;
|
||||
case 'iframe':
|
||||
|
||||
const iframe_file_url = encodeURIComponent(
|
||||
url("/web/content", {
|
||||
model: data.model,
|
||||
id: data.res_id,
|
||||
field: key,
|
||||
})
|
||||
);
|
||||
fileUrl = `${this.state.attachment_base}?file=${iframe_file_url}`;
|
||||
case 'unknown':
|
||||
fileUrl = encodeURIComponent(
|
||||
url("/web/content", {
|
||||
model: data.model,
|
||||
id: data.res_id,
|
||||
field: key,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return fileUrl
|
||||
},
|
||||
_getTypeInfo(type, data) {
|
||||
switch (type) {
|
||||
case 'application/pdf':
|
||||
data.attachment_base = `/web/static/lib/pdfjs/web/viewer.html`;
|
||||
data.attachment_type = 'iframe'
|
||||
break;
|
||||
case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
|
||||
data.attachment_base = `/jikimo_attachment_viewer/static/lib/docxjs/viewer.html`;
|
||||
data.attachment_type = 'iframe'
|
||||
break;
|
||||
case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet':
|
||||
data.attachment_base = `/jikimo_attachment_viewer/static/lib/exceljs/viewer.html`;
|
||||
data.attachment_type = 'iframe'
|
||||
break;
|
||||
case 'application/vnd.openxmlformats-officedocument.presentationml.presentation':
|
||||
data.attachment_base = '';
|
||||
data.attachment_type = 'unknown'
|
||||
break;
|
||||
case 'image/png':
|
||||
case 'image/jpeg':
|
||||
case 'image/jpg':
|
||||
data.attachment_base = ''
|
||||
data.attachment_type = 'image'
|
||||
break;
|
||||
default:
|
||||
data.attachment_base = ''
|
||||
data.attachment_type = 'unknown'
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var CustomListController = ListController.extend({
|
||||
// 可以保留或移除,根据是否需要处理自定义事件
|
||||
// 处理打印操作
|
||||
// rpc.query({
|
||||
// model: 'sf.demand.plan.print.wizard',
|
||||
// method: 'demand_plan_print',
|
||||
// args: [recordIds]
|
||||
// }).then(function(result) {
|
||||
// self.do_notify("成功", "打印任务已发送到打印机");
|
||||
// // 刷新视图显示最新状态
|
||||
// self.reload();
|
||||
// }).catch(function(error) {
|
||||
// self.do_warn("打印失败", error.data.message || "发生未知错误");
|
||||
// });
|
||||
});
|
||||
|
||||
var PrintDemand = ListView.extend({
|
||||
config: _.extend({}, ListView.prototype.config, {
|
||||
Renderer: CustomListRenderer,
|
||||
Controller: CustomListController,
|
||||
}),
|
||||
});
|
||||
|
||||
viewRegistry.add('print_demand', PrintDemand);
|
||||
|
||||
return PrintDemand;
|
||||
});
|
||||
84
sf_demand_plan/static/src/scss/style.css
Normal file
84
sf_demand_plan/static/src/scss/style.css
Normal file
@@ -0,0 +1,84 @@
|
||||
.demand_plan_tree .o_list_table_ungrouped th:not(.o_list_record_selector,.row_no,[data-name=sequence]) {
|
||||
min-width: 98px !important;
|
||||
}
|
||||
|
||||
.demand_plan_tree .o_list_table_grouped th:not(.o_list_record_selector,.row_no,[data-name=sequence]) {
|
||||
width: 98px !important;
|
||||
}
|
||||
|
||||
.demand_plan_tree .o_list_table_ungrouped {
|
||||
min-width: 1900px;
|
||||
}
|
||||
|
||||
|
||||
.o_selected_row {
|
||||
background-color: #e6f7ff !important;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.custom-table-image-container {
|
||||
display: flex;
|
||||
height: calc(95vh - 254px);
|
||||
gap: 20px;
|
||||
position: relative;
|
||||
th.o_list_record_selector, td.o_list_record_selector{
|
||||
display: none;
|
||||
}
|
||||
.custom-preview-container, .print_demand {
|
||||
flex: 1;
|
||||
|
||||
max-width: 49%;
|
||||
tbody {
|
||||
tr:not(.o_data_row) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
tfoot {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.print_demand {
|
||||
.table-responsive {
|
||||
width: 100%;
|
||||
overflow-x: auto!important;
|
||||
}
|
||||
}
|
||||
.custom-preview-container {
|
||||
background-color: #dadce0;
|
||||
padding: 20px;
|
||||
img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
.o_print_custom, .o_cancel_custom {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
}
|
||||
.o_print_custom {
|
||||
right: 66px;
|
||||
}
|
||||
}
|
||||
|
||||
.denmand_set {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 50px;
|
||||
> span {
|
||||
font-weight: bold;
|
||||
}
|
||||
input {
|
||||
margin-left: 30px;
|
||||
}
|
||||
label {
|
||||
margin-left: 5px;
|
||||
}
|
||||
input,label {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
125
sf_demand_plan/views/demand_plan.xml
Normal file
125
sf_demand_plan/views/demand_plan.xml
Normal file
@@ -0,0 +1,125 @@
|
||||
<odoo>
|
||||
<record id="view_sf_production_demand_plan_tree" model="ir.ui.view">
|
||||
<field name="name">sf.production.demand.plan.tree</field>
|
||||
<field name="model">sf.production.demand.plan</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="需求计划" default_order="sequence desc,id desc" editable="bottom"
|
||||
class="demand_plan_tree">
|
||||
<header>
|
||||
<button string="打印" name="button_action_print" type="object"
|
||||
class="btn-primary"/>
|
||||
</header>
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="id" optional="hide"/>
|
||||
<field name="priority"/>
|
||||
<field name="status"/>
|
||||
<field name="customer_name"/>
|
||||
<field name="order_remark"/>
|
||||
<field name="glb_url" optional="hide"/>
|
||||
<field name="product_id"/>
|
||||
<field name="model_id" optional="hide"/>
|
||||
<field name="part_name"/>
|
||||
<field name="part_number"/>
|
||||
<field name="is_incoming_material"/>
|
||||
<field name="supply_method"/>
|
||||
<field name="product_uom_qty"/>
|
||||
<field name="deadline_of_delivery"/>
|
||||
<field name="inventory_quantity_auto_apply"/>
|
||||
<field name="qty_delivered"/>
|
||||
<field name="qty_to_deliver"/>
|
||||
<field name="model_long"/>
|
||||
<field name="blank_type" optional="hide"/>
|
||||
<field name="embryo_long"/>
|
||||
<field name="materials_id"/>
|
||||
<field name="model_machining_precision"/>
|
||||
<field name="model_process_parameters_ids" widget="many2many_tags"/>
|
||||
<field name="product_remark" optional="hide"/>
|
||||
<field name="order_code" optional="hide"/>
|
||||
<field name="sale_order_id" optional="hide"/>
|
||||
<field name="sale_order_line_number" optional="hide"/>
|
||||
<field name="order_state"/>
|
||||
<field name="route_id" optional="hide"/>
|
||||
<field name="contract_date"/>
|
||||
<field name="date_order"/>
|
||||
<field name="contract_code"/>
|
||||
<field name="plan_remark"/>
|
||||
<field name="processing_time"/>
|
||||
<field name="material_check" optional="hide"/>
|
||||
<field name="hide_action_open_mrp_production" invisible="1"/>
|
||||
<field name="hide_action_purchase_orders" invisible="1"/>
|
||||
<field name="hide_action_stock_picking" invisible="1"/>
|
||||
<field name="hide_action_view_programming" invisible="1"/>
|
||||
<button name="action_open_sale_order" type="object" string="供货方式待确认" class="btn-secondary"
|
||||
attrs="{'invisible': [('supply_method', '!=', False)]}"/>
|
||||
<button name="action_open_mrp_production" type="object" string="待工艺确认" class="btn-secondary"
|
||||
attrs="{'invisible': [('hide_action_open_mrp_production', '=', False)]}"/>
|
||||
<button name="action_view_purchase_request" type="object" string="采购申请" class="btn-secondary"
|
||||
attrs="{'invisible': [('hide_action_purchase_orders', '=', False)]}"/>
|
||||
<button name="action_view_stock_picking" type="object" string="调拨单" class="btn-secondary"
|
||||
attrs="{'invisible': [('hide_action_stock_picking', '=', False)]}"/>
|
||||
<button name="action_view_programming" type="object" string="编程单" class="btn-secondary"
|
||||
attrs="{'invisible': [('hide_action_view_programming', '=', False)]}"/>
|
||||
<field name="planned_start_date"/>
|
||||
<field name="actual_start_date"/>
|
||||
<field name="actual_end_date"/>
|
||||
<field name="create_date" optional="hide" string="创建时间"/>
|
||||
<field name="create_uid" optional="hide" string="创建人"/>
|
||||
<field name="write_date" string="更新时间"/>
|
||||
<field name="write_uid" optional="hide" string="更新人"/>
|
||||
<field name="print_count"/>
|
||||
<button name="release_production_order" type="object" string="下达生产" class="btn-primary"
|
||||
attrs="{'invisible': ['|',('status', '!=', '50'), ('supply_method', 'not in', ['automation', 'manual'])]}"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_sf_production_demand_plan_search" model="ir.ui.view">
|
||||
<field name="name">sf.production.demand.plan.search</field>
|
||||
<field name="model">sf.production.demand.plan</field>
|
||||
<field name="arch" type="xml">
|
||||
<search>
|
||||
<field name="order_remark"/>
|
||||
<field name="product_id"/>
|
||||
<field name="part_name"/>
|
||||
<field name="part_number"/>
|
||||
<field name="customer_name"/>
|
||||
<field name="supply_method"/>
|
||||
<field name="materials_id"/>
|
||||
<field name="model_process_parameters_ids"/>
|
||||
<field name="plan_remark"/>
|
||||
<field name="contract_code"/>
|
||||
<group expand="0" string="Group By">
|
||||
<filter name="group_by_priority" string="优先级" domain="[]" context="{'group_by': 'priority'}"/>
|
||||
<filter name="group_by_status" string="状态" domain="[]" context="{'group_by': 'status'}"/>
|
||||
<filter name="group_by_customer_name" string="客户" domain="[]" context="{'group_by': 'customer_name'}"/>
|
||||
<filter name="group_by_is_incoming_material" string="客供料" domain="[]"
|
||||
context="{'group_by': 'is_incoming_material'}"/>
|
||||
<filter name="group_by_supply_method" string="供货方式" domain="[]"
|
||||
context="{'group_by': 'supply_method'}"/>
|
||||
<filter name="group_by_deadline_of_delivery" string="客户交期" domain="[]"
|
||||
context="{'group_by': 'deadline_of_delivery'}"/>
|
||||
<filter name="group_by_materials_id" string="材料" domain="[]"
|
||||
context="{'group_by': 'materials_id'}"/>
|
||||
<filter name="group_by_contract_code" string="合同号" domain="[]"
|
||||
context="{'group_by': 'contract_code'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="sf_production_demand_plan_action" model="ir.actions.act_window">
|
||||
<field name="name">需求计划</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">sf.production.demand.plan</field>
|
||||
<field name="view_mode">tree</field>
|
||||
</record>
|
||||
|
||||
|
||||
<menuitem
|
||||
id="demand_plan_menu"
|
||||
name="需求计划"
|
||||
sequence="140"
|
||||
action="sf_production_demand_plan_action"
|
||||
parent="sf_plan.sf_production_plan_menu"
|
||||
/>
|
||||
</odoo>
|
||||
1
sf_demand_plan/wizard/__init__.py
Normal file
1
sf_demand_plan/wizard/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import sf_demand_plan_print_wizard
|
||||
95
sf_demand_plan/wizard/sf_demand_plan_print_wizard.py
Normal file
95
sf_demand_plan/wizard/sf_demand_plan_print_wizard.py
Normal file
@@ -0,0 +1,95 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import logging
|
||||
from odoo import models, fields, api, _
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SfDemandPlanPrintWizard(models.TransientModel):
|
||||
_name = 'sf.demand.plan.print.wizard'
|
||||
_description = u'打印向导'
|
||||
|
||||
demand_plan_id = fields.Many2one('sf.production.demand.plan', string='需求计划ID')
|
||||
product_id = fields.Many2one(
|
||||
comodel_name='product.product',
|
||||
related='demand_plan_id.product_id',
|
||||
string='产品', store=True, index=True)
|
||||
model_id = fields.Char('模型ID')
|
||||
filename_url = fields.Char('文件名/URL')
|
||||
type = fields.Selection([
|
||||
('1', '图纸'),
|
||||
('2', '程序单'),
|
||||
], string='类型')
|
||||
status = fields.Selection([
|
||||
('not_start', '未开始'),
|
||||
('success', '成功'),
|
||||
('fail', '失败'),
|
||||
], string='状态', default='not_start')
|
||||
machining_drawings = fields.Binary('2D加工图纸', related='product_id.machining_drawings', store=True)
|
||||
|
||||
cnc_worksheet = fields.Binary('程序单')
|
||||
|
||||
@api.model
|
||||
def demand_plan_print(self, print_ids):
|
||||
plan_print_ids = self.env['sf.demand.plan.print.wizard'].sudo().search(
|
||||
[('id', 'in', print_ids)])
|
||||
if not plan_print_ids:
|
||||
return {'message': '记录不存在'}
|
||||
success_records = []
|
||||
failed_records = []
|
||||
for record in plan_print_ids:
|
||||
pdf_data = record.machining_drawings if record.type == '1' else record.cnc_worksheet
|
||||
if pdf_data:
|
||||
try:
|
||||
# 执行打印
|
||||
self.env['jikimo.printing'].sudo().print_pdf(pdf_data)
|
||||
record.status = 'success'
|
||||
t_part, c_part = record.demand_plan_id.print_count.split('C')
|
||||
t_num = int(t_part[1:])
|
||||
c_num = int(c_part)
|
||||
if record.type == '1':
|
||||
t_num += 1
|
||||
elif record.type == '2':
|
||||
c_num += 1
|
||||
record.demand_plan_id.print_count = f"T{t_num}C{c_num}"
|
||||
success_records.append({
|
||||
'filename_url': record.filename_url,
|
||||
})
|
||||
except Exception as e:
|
||||
record.status = 'fail'
|
||||
_logger.error(f"文件{record.filename_url}打印失败: {str(e)}")
|
||||
failed_records.append({
|
||||
'filename_url': record.filename_url,
|
||||
})
|
||||
if failed_records:
|
||||
message = f"成功打印 {len(success_records)} 个文件,失败 {len(failed_records)} 个"
|
||||
else:
|
||||
message = f"所有 {len(success_records)} 个文件打印成功"
|
||||
return {'message': message}
|
||||
|
||||
|
||||
class MrpWorkorder(models.Model):
|
||||
_inherit = 'mrp.workorder'
|
||||
|
||||
def write(self, vals):
|
||||
res = super(MrpWorkorder, self).write(vals)
|
||||
for record in self:
|
||||
if 'cnc_worksheet' in vals:
|
||||
demand_plan_print = self.env['sf.demand.plan.print.wizard'].sudo().search(
|
||||
[('model_id', '=', record.model_id), ('type', '=', '2')])
|
||||
if demand_plan_print:
|
||||
self.env['sf.demand.plan.print.wizard'].sudo().write(
|
||||
{'cnc_worksheet': record.cnc_worksheet, 'filename_url': record.cnc_worksheet_name})
|
||||
else:
|
||||
demand_plan = self.env['sf.production.demand.plan'].sudo().search(
|
||||
[('product_id', '=', record.product_id.id)])
|
||||
if demand_plan:
|
||||
wizard_vals = {
|
||||
'demand_plan_id': demand_plan.id,
|
||||
'model_id': demand_plan.model_id,
|
||||
'type': '2',
|
||||
'cnc_worksheet': record.cnc_worksheet,
|
||||
'filename_url': record.cnc_worksheet_name
|
||||
}
|
||||
self.env['sf.demand.plan.print.wizard'].sudo().create(wizard_vals)
|
||||
return res
|
||||
17
sf_demand_plan/wizard/sf_demand_plan_print_wizard_view.xml
Normal file
17
sf_demand_plan/wizard/sf_demand_plan_print_wizard_view.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<odoo>
|
||||
<record id="action_plan_print_tree" model="ir.ui.view">
|
||||
<field name="name">sf.demand.plan.print.wizard.tree</field>
|
||||
<field name="model">sf.demand.plan.print.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="打印" class="print_demand" js_class="print_demand">
|
||||
<field name="model_id"/>
|
||||
<field name="filename_url"/>
|
||||
<field name="type"/>
|
||||
<field name="machining_drawings" attrs="{'column_invisible': True }"/>
|
||||
<field name="cnc_worksheet" attrs="{'column_invisible': True }"/>
|
||||
<field name="status"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -14,10 +14,12 @@
|
||||
<field name="name">原材料</field>
|
||||
<field name="type">原材料</field>
|
||||
</record>
|
||||
|
||||
<record id="product_category_surface_technics_sf" model="product.category">
|
||||
<field name="name">表面工艺</field>
|
||||
<field name="type">表面工艺</field>
|
||||
<field name="parent_id" ref="sf_manufacturing.product_category_outsource_process"/>
|
||||
<field name="property_cost_method">fifo</field>
|
||||
<field name="property_valuation">manual_periodic</field>
|
||||
</record>
|
||||
|
||||
<record id="product_category_cutting_tool_sf" model="product.category">
|
||||
|
||||
@@ -17,7 +17,7 @@ class ResProductCategory(models.Model):
|
||||
class ResProductProduct(models.Model):
|
||||
_inherit = 'product.product'
|
||||
|
||||
single_manufacturing = fields.Boolean(string="单个制造")
|
||||
# single_manufacturing = fields.Boolean(string="单个制造")
|
||||
is_bfm = fields.Boolean('业务平台是否自动创建', default=False)
|
||||
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ class ResProductTemplate(models.Model):
|
||||
model_name = fields.Char('模型名称')
|
||||
categ_type = fields.Selection(
|
||||
[("成品", "成品"), ("胚料", "胚料"), ("原材料", "原材料")], string='产品的类别', related='categ_id.type', store=True)
|
||||
blank_type = fields.Selection([('圆料', '圆料'), ('方料', '方料')], string='坯料分类')
|
||||
model_long = fields.Float('模型长[mm]', digits=(16, 3))
|
||||
model_width = fields.Float('模型宽[mm]', digits=(16, 3))
|
||||
model_height = fields.Float('模型高[mm]', digits=(16, 3))
|
||||
@@ -44,7 +45,7 @@ class ResProductTemplate(models.Model):
|
||||
else:
|
||||
return self.env.ref('sf_dlm.product_uom_cubic_millimeter')
|
||||
|
||||
# model_file = fields.Binary('模型文件')
|
||||
model_file = fields.Binary('模型文件')
|
||||
|
||||
# 胚料的库存路线设置
|
||||
# def _get_routes(self, route_type):
|
||||
@@ -72,14 +73,20 @@ class ResProductTemplate(models.Model):
|
||||
copy_product_id.product_tmpl_id.active = True
|
||||
model_type = self.env['sf.model.type'].search([], limit=1)
|
||||
attachment = self.attachment_create(item['model_name'], item['model_data'])
|
||||
# 判断参数中是否包含 坯料尺寸(长、宽、高)
|
||||
blank_bool = any(value is not None and value != 0 for value in (
|
||||
item.get('blank_length'), item.get('blank_width'), item.get('blank_height'))) if all(
|
||||
key in item for key in ('blank_length', 'blank_width', 'blank_height')) else False
|
||||
vals = {
|
||||
'name': '%s-%s-%s' % ('P', order_id.name, i),
|
||||
'model_long': item['model_long'] + model_type.embryo_tolerance,
|
||||
'model_width': item['model_width'] + model_type.embryo_tolerance,
|
||||
'model_height': item['model_height'] + model_type.embryo_tolerance,
|
||||
'model_volume': (item['model_long'] + model_type.embryo_tolerance) * (
|
||||
item['model_width'] + model_type.embryo_tolerance) * (
|
||||
item['model_height'] + model_type.embryo_tolerance),
|
||||
'blank_type': item.get('blank_type'),
|
||||
'model_long': item.get('blank_length') if blank_bool else item['model_long'] + model_type.embryo_tolerance,
|
||||
'model_width': item.get('blank_width') if blank_bool else item['model_width'] + model_type.embryo_tolerance,
|
||||
'model_height': item.get('blank_height') if blank_bool else item['model_height'] + model_type.embryo_tolerance,
|
||||
'model_volume': ((item['model_long'] + model_type.embryo_tolerance) *
|
||||
(item['model_width'] + model_type.embryo_tolerance) *
|
||||
(item['model_height'] + model_type.embryo_tolerance)) if not blank_bool else (
|
||||
item.get('blank_length') * item.get('blank_width') * item.get('blank_height')),
|
||||
'product_model_type_id': model_type.id,
|
||||
'model_processing_panel': 'R',
|
||||
'model_machining_precision': item['model_machining_precision'],
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
from . import models
|
||||
@@ -11,6 +11,7 @@
|
||||
'website': 'https://www.sf.jikimo.com',
|
||||
'depends': ['sf_sale', 'sf_dlm', 'sf_manufacturing', 'jikimo_attachment_viewer'],
|
||||
'data': [
|
||||
'data/sequence.xml',
|
||||
'data/stock_data.xml',
|
||||
'views/product_template_management_view.xml',
|
||||
],
|
||||
|
||||
10
sf_dlm_management/data/sequence.xml
Normal file
10
sf_dlm_management/data/sequence.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<odoo>
|
||||
<data>
|
||||
<record id="sequence_production_process_parameter" model="ir.sequence">
|
||||
<field name="name">工艺可选参数编码序列</field>
|
||||
<field name="code">sf.production.process.parameter</field>
|
||||
<field name="padding">3</field>
|
||||
<field name="number_increment">1</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -1,2 +1,4 @@
|
||||
# from . import product_template
|
||||
# from . import product_supplierinfo
|
||||
from . import sf_production_common
|
||||
from . import mrp_routing_workcenter
|
||||
16
sf_dlm_management/models/mrp_routing_workcenter.py
Normal file
16
sf_dlm_management/models/mrp_routing_workcenter.py
Normal file
@@ -0,0 +1,16 @@
|
||||
# import logging
|
||||
# from odoo import fields, models, api
|
||||
# from odoo.exceptions import UserError
|
||||
# from odoo.tools import str2bool
|
||||
|
||||
|
||||
# class ResMrpRoutingWorkcenter(models.Model):
|
||||
# _inherit = 'mrp.routing.workcenter'
|
||||
# def init(self):
|
||||
# super(ResMrpRoutingWorkcenter, self).init()
|
||||
# # 在模块初始化时触发计算字段的更新
|
||||
# records = self.search([])
|
||||
# if str2bool(self.env['ir.config_parameter'].get_param('sf.production.process.parameter.is_init_workcenter',default='False')):
|
||||
# return
|
||||
# records.optional_process_parameters_date()
|
||||
# self.env['ir.config_parameter'].set_param('sf.production.process.parameter.is_init_workcenter', True)
|
||||
85
sf_dlm_management/models/sf_production_common.py
Normal file
85
sf_dlm_management/models/sf_production_common.py
Normal file
@@ -0,0 +1,85 @@
|
||||
# # -*- coding: utf-8 -*-
|
||||
# import logging
|
||||
# from odoo import fields, models, api
|
||||
# from odoo.exceptions import UserError
|
||||
# from odoo.tools import str2bool
|
||||
|
||||
|
||||
# class SfProductionProcessParameter(models.Model):
|
||||
# _inherit = 'sf.production.process.parameter'
|
||||
|
||||
|
||||
# @api.model
|
||||
# def create(self, vals):
|
||||
# # if vals.get('code', '/') == '/' or vals.get('code', '/') is False:
|
||||
# # vals['code'] = '101'+self.routing_id.code +self.env['ir.sequence'].next_by_code('sf.production.process.parameter')
|
||||
# if vals.get('routing_id'):
|
||||
# # vals['gain_way'] = '外协'
|
||||
# routing_id = self.env['mrp.routing.workcenter'].browse(vals.get('routing_id'))
|
||||
# if routing_id.surface_technics_id and not vals.get('process_id'):
|
||||
# vals['process_id'] = routing_id.surface_technics_id.id
|
||||
# if vals.get('code', '/') == '/' or vals.get('code', '/') is False:
|
||||
# vals['code'] = '101' + routing_id.code + self.env['ir.sequence'].next_by_code(
|
||||
# 'sf.production.process.parameter')
|
||||
# obj = super(SfProductionProcessParameter, self).create(vals)
|
||||
# return obj
|
||||
# def create_service_product(self):
|
||||
# service_categ = self.env.ref(
|
||||
# 'sf_dlm.product_category_surface_technics_sf').sudo()
|
||||
|
||||
# product_name = f"{self.process_id.name}_{self.name}"
|
||||
# product_id = self.env['product.template'].search(
|
||||
# [("name", '=', product_name)])
|
||||
# if product_id:
|
||||
# product_id.server_product_process_parameters_id = self.id
|
||||
# else:
|
||||
# res_partner = self.env['res.partner'].search([('name','=','湖南傲派自动化设备有限公司')])
|
||||
# self.env['product.template'].create({
|
||||
# 'detailed_type': 'service',
|
||||
# 'name': product_name,
|
||||
# 'invoice_policy': 'delivery',
|
||||
# 'categ_id': service_categ.id,
|
||||
# 'description': f"基于{self.name}创建的服务产品",
|
||||
# 'sale_ok': True, # 可销售
|
||||
# 'purchase_ok': True, # 可采购
|
||||
# 'server_product_process_parameters_id': self.id,
|
||||
# 'seller_ids': [(0, 0, {
|
||||
# # 'delay': 1,
|
||||
# 'partner_id': res_partner.id,
|
||||
# 'price': 1, })],
|
||||
# })
|
||||
|
||||
# def create_work_center(self):
|
||||
# production_process_parameter = self
|
||||
# if not production_process_parameter.process_id:
|
||||
# return
|
||||
# if not production_process_parameter.routing_id:
|
||||
# workcenter_id = self.env['mrp.routing.workcenter'].search(
|
||||
# [("surface_technics_id", '=', production_process_parameter.process_id.id)])
|
||||
# if not workcenter_id:
|
||||
# outsourcing_work_center = self.env['mrp.workcenter'].search(
|
||||
# [("name", '=', '外协工作中心')])
|
||||
# routing_id = self.env['mrp.routing.workcenter'].create({
|
||||
# 'workcenter_ids': [(6, 0, outsourcing_work_center.ids)],
|
||||
# 'routing_tag': 'special',
|
||||
# 'routing_type': '表面工艺',
|
||||
# 'is_outsource': True,
|
||||
# 'surface_technics_id': production_process_parameter.process_id.id,
|
||||
# 'name': production_process_parameter.process_id.name,
|
||||
# })
|
||||
# production_process_parameter.routing_id = routing_id.id
|
||||
# else:
|
||||
# production_process_parameter.routing_id = workcenter_id.id
|
||||
|
||||
# def init(self):
|
||||
# super(SfProductionProcessParameter, self).init()
|
||||
# # 在模块初始化时触发计算字段的更新
|
||||
# records = self.search([])
|
||||
# if str2bool(self.env['ir.config_parameter'].get_param('sf.production.process.parameter.is_init_process',
|
||||
# default='False')):
|
||||
# return
|
||||
# for record in records:
|
||||
# if not record.outsourced_service_products:
|
||||
# record.create_service_product()
|
||||
# record.create_work_center()
|
||||
# self.env['ir.config_parameter'].set_param('sf.production.process.parameter.is_init_process', True)
|
||||
@@ -16,15 +16,21 @@
|
||||
<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='glb_url' 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='manual_quotation' attrs="{'invisible':[('glb_url', '=', False)]}"/>
|
||||
<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)]}"/>
|
||||
<field name="model_name" invisible="1"/>
|
||||
<field name="upload_model_file" widget="many2many_binary" attrs="{'invisible': [('upload_model_file', '=', False)]}"/>
|
||||
<field name="model_url"
|
||||
widget="binary_download"
|
||||
filename_field="model_name"
|
||||
attrs="{'invisible': ['|', '|',('categ_type', '!=', '成品'),('categ_type', '=', False),('model_url', '=', False)]}"/>
|
||||
<field name="model_file" widget="Viewer3D" string="模型" readonly="1" force_save="1"
|
||||
attrs="{'invisible': ['|','|', ('categ_type', '!=', '成品'),('categ_type', '=', False),('model_file', '=', False)]}"/>
|
||||
<field name="glb_url" widget="Viewer3D" string="模型" readonly="1" force_save="1"
|
||||
attrs="{'invisible': ['|','|', ('categ_type', '!=', '成品'),('categ_type', '=', False),('glb_url', '=', False)]}"/>
|
||||
<field name='cutting_tool_type' invisible="1"/>
|
||||
<field name="fixture_material_type" invisible="1"/>
|
||||
<field name="embryo_model_type_id" string="模型类型" options="{'no_create': True}"
|
||||
@@ -33,9 +39,9 @@
|
||||
attrs="{'invisible': [('categ_type', 'not in', ['成品','坯料', '原材料'])],'readonly': [('id', '!=', False)]}"/>
|
||||
<field name="materials_type_id" string="型号" placeholder="请选择" options="{'no_create': True}"
|
||||
attrs="{'invisible': [('categ_type', 'not in', ['成品','坯料', '原材料'])],'readonly': [('id', '!=', False)]}"/>
|
||||
<field name="server_product_process_parameters_id" string="表面工艺参数"
|
||||
<field name="server_product_process_parameters_id" string="工艺参数"
|
||||
options="{'no_create': True}"
|
||||
attrs="{'invisible': ['|',('categ_type', '!=', '表面工艺'),('categ_type', '=', False)]}"/>
|
||||
attrs="{'invisible': ['|',('detailed_type', '!=', 'service'),('detailed_type', '=', False)]}"/>
|
||||
<field name="cutting_tool_material_id" class="custom_required"
|
||||
options="{'no_create': True}"
|
||||
attrs="{'invisible': [('categ_type', '!=', '刀具')],'required': [('categ_type', '=', '刀具')],'readonly': [('id', '!=', False)]}"
|
||||
@@ -68,6 +74,7 @@
|
||||
</field>
|
||||
<xpath expr="//field[@name='uom_id']" position="before">
|
||||
<field name="is_manual_processing" attrs="{'invisible': [('categ_type', 'not in', ['成品', '坯料'])], 'readonly': True}" />
|
||||
<field name="auto_machining" attrs="{'invisible': [('categ_type', 'not in', ['成品', '坯料'])], 'readonly': True}" />
|
||||
</xpath>
|
||||
<xpath expr="//label[@for='volume']" position="before">
|
||||
<label for="length" string="尺寸"
|
||||
@@ -88,7 +95,7 @@
|
||||
<page string="加工参数">
|
||||
<group>
|
||||
<group string="模型">
|
||||
<label for="model_long" string="尺寸[mm]"/>
|
||||
<label for="model_long" string="坯料尺寸[mm]"/>
|
||||
<div class="o_address_format">
|
||||
<label for="model_long" string="长"/>
|
||||
<field name="model_long" class="o_address_zip"/>
|
||||
@@ -97,6 +104,7 @@
|
||||
<label for="model_height" string="高"/>
|
||||
<field name="model_height" class="o_address_zip"/>
|
||||
</div>
|
||||
<field name="blank_type" readonly="1"/>
|
||||
<field name="model_volume" string="体积[mm³]"/>
|
||||
<field name="product_model_type_id" string="模型类型"/>
|
||||
<field name="model_processing_panel" placeholder="例如R,U" string="加工面板"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user