diff --git a/Main.seq b/Main.seq index 6004773..410f728 100644 --- a/Main.seq +++ b/Main.seq @@ -142,7 +142,7 @@ }, "NodeProperty": { "EP_CONTROL": { - "EP_CONTROL_IMPORT": "{\n \"PImport\": \"c3lzLCBvcywganNvbiwgcGF0aGxpYiwgdGltZQ==\",\n \"PBCode\": \"ZnJvbSBycGFfeWlzYWlxaS5jb250cmFjdF9mbG93X3N0ZXBzIGltcG9ydCAoCiAgICBmaWxsX2NvbnRyYWN0X2dvb2RzX2xpbmVzLCBmaWxsX2NvbnRyYWN0X3BheW1lbnRfaW5mbywKICAgIHVwbG9hZF9jb250cmFjdF9kb2N1bWVudF9pZl9uZWVkZWQsIHN1Ym1pdF9jb250cmFjdF9mb3JfYXBwcm92YWwsCiAgICByZWFkX2NvbnRyYWN0X251bWJlcgopCgphZGFwdGVyID0gc2VsZi5fYWRhcHRlcgoKcHJpbnQoIltTMDldIOWhq+WGmei0p+WTgeaYjue7hi4uLiIpCmZpbGxfY29udHJhY3RfZ29vZHNfbGluZXMoYWRhcHRlciwgc2VsZi5fY3R4KQpwcmludChmIltPS10g6LSn5ZOBOiB7bGVuKHNlbGYuX2N0eC5nb29kc19saW5lcyl9IOihjCIpCnByaW50KCJbUzEwXSDloavlhpnku5jmrL7kv6Hmga8uLi4iKQpmaWxsX2NvbnRyYWN0X3BheW1lbnRfaW5mbyhhZGFwdGVyLCBzZWxmLl9jdHgpCnByaW50KGYiW09LXSDku5jmrL7mlrnlvI89e3NlbGYuX2N0eC5wYXltZW50X21ldGhvZH0iKQoKaWYgc2VsZi5fY3R4LnNvdXJjZV90eXBlID09ICJZWUMiOgogICAgcHJpbnQoIltTMTFdIOWQiOW5tlBERiBbWVlD5LiT5bGeXS4uLiAo5Y2VUERGLCDot7Pov4flkIjlubYpIikKICAgIHByaW50KCJbUzEyXSDkuIrkvKBQREbpmYTku7YgW1lZQ+S4k+Wxnl0uLi4gKGRyeV9ydW4g6Lez6L+HKSIpCgpwcmludCgpCnByaW50KCJbUzEzXSDmj5DkuqTlrqHmibkuLi4iKQppZiBub3Qgc3VibWl0X2NvbnRyYWN0X2Zvcl9hcHByb3ZhbChhZGFwdGVyLCBzZWxmLl9jdHgpOgogICAgcHJpbnQoIltEUlktUlVOXSDmvJTnu4PmqKHlvI/vvIzot7Pov4flrp7pmYXmj5DkuqQiKQogICAgcHJpbnQoIj0iICogNjApCiAgICBwcmludCgi5ZCI5ZCM5bu65Y2V5rWB56iL5ryU57uD5a6M5oiQIikKICAgIHByaW50KCI9IiAqIDYwKQplbHNlOgogICAgcHJpbnQoIltTMTRdIOivu+WPluWQiOWQjOe8luWPty4uLiIpCiAgICBjb250cmFjdF9ubyA9IHJlYWRfY29udHJhY3RfbnVtYmVyKGFkYXB0ZXIpCiAgICBwcmludChmIltPS10g5ZCI5ZCM57yW5Y+3OiB7Y29udHJhY3Rfbm99IikKICAgIHByaW50KCI9IiAqIDYwKQogICAgcHJpbnQoZiJbQ09NUExFVEVEXSDlkIjlkIzliJvlu7rmiJDlip86IHtjb250cmFjdF9ub30iKQogICAgcHJpbnQoIj0iICogNjApCg==\"\n}" + "EP_CONTROL_IMPORT": "{\n \"PImport\": \"\",\n \"PBCode\": \"ZnJvbSBycGFfeWlzYWlxaS5jb250cmFjdF9mbG93X3N0ZXBzIGltcG9ydCAoCiAgICBmaWxsX2NvbnRyYWN0X2dvb2RzX2xpbmVzLCBmaWxsX2NvbnRyYWN0X3BheW1lbnRfaW5mbywKICAgIHVwbG9hZF9jb250cmFjdF9kb2N1bWVudF9pZl9uZWVkZWQsIHN1Ym1pdF9jb250cmFjdF9mb3JfYXBwcm92YWwsCiAgICByZWFkX2NvbnRyYWN0X251bWJlcgopCgphZGFwdGVyID0gc2VsZi5fYWRhcHRlcgoKcHJpbnQoIltTMDldIOWhq+WGmei0p+WTgeaYjue7hi4uLiIpCmZpbGxfY29udHJhY3RfZ29vZHNfbGluZXMoYWRhcHRlciwgc2VsZi5fY3R4KQpwcmludChmIltPS10g6LSn5ZOBOiB7bGVuKHNlbGYuX2N0eC5nb29kc19saW5lcyl9IOihjCIpCnByaW50KCJbUzEwXSDloavlhpnku5jmrL7kv6Hmga8uLi4iKQpmaWxsX2NvbnRyYWN0X3BheW1lbnRfaW5mbyhhZGFwdGVyLCBzZWxmLl9jdHgpCnByaW50KGYiW09LXSDku5jmrL7mlrnlvI89e3NlbGYuX2N0eC5wYXltZW50X21ldGhvZH0iKQoKaWYgc2VsZi5fY3R4LnNvdXJjZV90eXBlID09ICJZWUMiOgogICAgcHJpbnQoIltTMTFdIOWQiOW5tlBERiBbWVlD5LiT5bGeXS4uLiAo5Y2VUERGLCDot7Pov4flkIjlubYpIikKICAgIHByaW50KCJbUzEyXSDkuIrkvKBQREbpmYTku7YgW1lZQ+S4k+Wxnl0uLi4gKGRyeV9ydW4g6Lez6L+HKSIpCgpwcmludCgpCnByaW50KCJbUzEzXSDmj5DkuqTlrqHmibkuLi4iKQppZiBub3Qgc3VibWl0X2NvbnRyYWN0X2Zvcl9hcHByb3ZhbChhZGFwdGVyLCBzZWxmLl9jdHgpOgogICAgcHJpbnQoIltEUlktUlVOXSDmvJTnu4PmqKHlvI/vvIzot7Pov4flrp7pmYXmj5DkuqQiKQogICAgcHJpbnQoIj0iICogNjApCiAgICBwcmludCgi5ZCI5ZCM5bu65Y2V5rWB56iL5ryU57uD5a6M5oiQIikKICAgIHByaW50KCI9IiAqIDYwKQplbHNlOgogICAgcHJpbnQoIltTMTRdIOivu+WPluWQiOWQjOe8luWPty4uLiIpCiAgICBjb250cmFjdF9ubyA9IHJlYWRfY29udHJhY3RfbnVtYmVyKGFkYXB0ZXIpCiAgICBwcmludChmIltPS10g5ZCI5ZCM57yW5Y+3OiB7Y29udHJhY3Rfbm99IikKICAgIHByaW50KCI9IiAqIDYwKQogICAgcHJpbnQoZiJbQ09NUExFVEVEXSDlkIjlkIzliJvlu7rmiJDlip86IHtjb250cmFjdF9ub30iKQogICAgcHJpbnQoIj0iICogNjApCg==\"\n}" }, "EP_Gen": { "EP_Gen_AFTERDELAY": "{\n \"val\": \"MA==\"\n}", @@ -175,7 +175,7 @@ }, "NodeProperty": { "EP_CONTROL": { - "EP_CONTROL_IMPORT": "{\n \"PImport\": \"c3lzLCBvcywganNvbiwgcGF0aGxpYiwgdGltZQ==\",\n \"PBCode\": \"ZnJvbSBycGFfeWlzYWlxaS5jb250cmFjdF9mbG93X3N0ZXBzIGltcG9ydCBmaWxsX2NvbnRyYWN0X2Jhc2ljX2luZm8sIGZpbGxfY29udHJhY3RfYW1vdW50X2FuZF9kYXRlcwoKYWRhcHRlciA9IHNlbGYuX2FkYXB0ZXIKCnByaW50KCJbUzA3XSDloavlhpnln7rnoYDooajljZUuLi4iKQpmaWxsX2NvbnRyYWN0X2Jhc2ljX2luZm8oYWRhcHRlciwgc2VsZi5fY3R4KQpwcmludChmIltPS10g57G75YirPXtzZWxmLl9jdHguY29udHJhY3RfY2F0ZWdvcnl9LCDnu5Pnrpc9e3NlbGYuX2N0eC5zZXR0bGVtZW50X3R5cGV9IikKcHJpbnQoIltTMDhdIOWhq+WGmemHkemineS4juaXpeacny4uLiIpCmZpbGxfY29udHJhY3RfYW1vdW50X2FuZF9kYXRlcyhhZGFwdGVyLCBzZWxmLl9jdHgpCnByaW50KGYiW09LXSDph5Hpop09e3NlbGYuX2N0eC5hbW91bnR9LCDnu5PmnZ/ml6XmnJ89e3NlbGYuX2N0eC5jb250cmFjdF9lbmRfZGF0ZX0iKQo=\"\n}" + "EP_CONTROL_IMPORT": "{\n \"PImport\": \"\",\n \"PBCode\": \"ZnJvbSBycGFfeWlzYWlxaS5jb250cmFjdF9mbG93X3N0ZXBzIGltcG9ydCBmaWxsX2NvbnRyYWN0X2Jhc2ljX2luZm8sIGZpbGxfY29udHJhY3RfYW1vdW50X2FuZF9kYXRlcwoKYWRhcHRlciA9IHNlbGYuX2FkYXB0ZXIKCnByaW50KCJbUzA3XSDloavlhpnln7rnoYDooajljZUuLi4iKQpmaWxsX2NvbnRyYWN0X2Jhc2ljX2luZm8oYWRhcHRlciwgc2VsZi5fY3R4KQpwcmludChmIltPS10g57G75YirPXtzZWxmLl9jdHguY29udHJhY3RfY2F0ZWdvcnl9LCDnu5Pnrpc9e3NlbGYuX2N0eC5zZXR0bGVtZW50X3R5cGV9IikKcHJpbnQoIltTMDhdIOWhq+WGmemHkemineS4juaXpeacny4uLiIpCmZpbGxfY29udHJhY3RfYW1vdW50X2FuZF9kYXRlcyhhZGFwdGVyLCBzZWxmLl9jdHgpCnByaW50KGYiW09LXSDph5Hpop09e3NlbGYuX2N0eC5hbW91bnR9LCDnu5PmnZ/ml6XmnJ89e3NlbGYuX2N0eC5jb250cmFjdF9lbmRfZGF0ZX0iKQo=\"\n}" }, "EP_Gen": { "EP_Gen_AFTERDELAY": "{\n \"val\": \"MA==\"\n}", @@ -208,7 +208,7 @@ }, "NodeProperty": { "EP_CONTROL": { - "EP_CONTROL_IMPORT": "{\n \"PImport\": \"c3lzLCBvcywganNvbiwgcGF0aGxpYiwgdGltZQ==\",\n \"PBCode\": \"ZnJvbSBycGFfeWlzYWlxaS5jb250cmFjdF9mbG93X3N0ZXBzIGltcG9ydCBzZWxlY3RfY29udHJhY3RfdGVtcGxhdGUsIGZpbGxfY29udHJhY3Rfc2lnbmF0b3J5X2luZm8KCmFkYXB0ZXIgPSBzZWxmLl9hZGFwdGVyCgpwcmludCgiW1MwNV0g6YCJ5oup5ZCI5ZCM5qih5p2/Li4uIikKc2VsZWN0X2NvbnRyYWN0X3RlbXBsYXRlKGFkYXB0ZXIsIHNlbGYuX2N0eCkKcHJpbnQoZiJbT0tdIOaooeadvzoge3NlbGYuX2N0eC50ZW1wbGF0ZV9uYW1lfSIpCnByaW50KCJbUzA2XSDloavlhpnnrb7nuqbmlrnkv6Hmga8uLi4iKQpmaWxsX2NvbnRyYWN0X3NpZ25hdG9yeV9pbmZvKGFkYXB0ZXIsIHNlbGYuX2N0eCkKcHJpbnQoZiJbT0tdIOetvue6puaWuToge3NlbGYuX2N0eC5jdXN0b21lcl9uYW1lfSIpCg==\"\n}" + "EP_CONTROL_IMPORT": "{\n \"PImport\": \"\",\n \"PBCode\": \"ZnJvbSBycGFfeWlzYWlxaS5jb250cmFjdF9mbG93X3N0ZXBzIGltcG9ydCBzZWxlY3RfY29udHJhY3RfdGVtcGxhdGUsIGZpbGxfY29udHJhY3Rfc2lnbmF0b3J5X2luZm8KCmFkYXB0ZXIgPSBzZWxmLl9hZGFwdGVyCgpwcmludCgiW1MwNV0g6YCJ5oup5ZCI5ZCM5qih5p2/Li4uIikKc2VsZWN0X2NvbnRyYWN0X3RlbXBsYXRlKGFkYXB0ZXIsIHNlbGYuX2N0eCkKcHJpbnQoZiJbT0tdIOaooeadvzoge3NlbGYuX2N0eC50ZW1wbGF0ZV9uYW1lfSIpCnByaW50KCJbUzA2XSDloavlhpnnrb7nuqbmlrnkv6Hmga8uLi4iKQpmaWxsX2NvbnRyYWN0X3NpZ25hdG9yeV9pbmZvKGFkYXB0ZXIsIHNlbGYuX2N0eCkKcHJpbnQoZiJbT0tdIOetvue6puaWuToge3NlbGYuX2N0eC5jdXN0b21lcl9uYW1lfSIpCg==\"\n}" }, "EP_Gen": { "EP_Gen_AFTERDELAY": "{\n \"val\": \"MA==\"\n}", @@ -241,7 +241,7 @@ }, "NodeProperty": { "EP_CONTROL": { - "EP_CONTROL_IMPORT": "{\n \"PImport\": \"c3lzLCBvcywganNvbiwgcGF0aGxpYiwgdGltZQ==\",\n \"PBCode\": \"ZnJvbSBycGFfeWlzYWlxaS5jb250cmFjdF9mbG93X3N0ZXBzIGltcG9ydCBsb2dpbl9jb250cmFjdF9zeXN0ZW0sIGVudGVyX2NvbnRyYWN0X2NyZWF0ZQpmcm9tIHJwYV95aXNhaXFpLmFwYV9icm93c2VyX2FkYXB0ZXIgaW1wb3J0IEFwYUJyb3dzZXJBZGFwdGVyCgphZGFwdGVyID0gQXBhQnJvd3NlckFkYXB0ZXIoKQphZGFwdGVyLl9icm93c2VyID0gc2VsZi5fYnJvd3NlcgphZGFwdGVyLl9pc19vcGVuID0gVHJ1ZQoKcHJpbnQoIltTMDNdIOeZu+W9leWQiOWQjOezu+e7ny4uLiIpCmxvZ2luX2NvbnRyYWN0X3N5c3RlbShhZGFwdGVyLCBzZWxmLl9jdHgpCnByaW50KCJbT0tdIOeZu+W9leWujOaIkCIpCnByaW50KCJbUzA0XSDov5vlhaXlkIjlkIzotbfojYkuLi4iKQplbnRlcl9jb250cmFjdF9jcmVhdGUoYWRhcHRlcikKcHJpbnQoIltPS10g5bey6L+b5YWl5ZCI5ZCM6LW36I2J6aG16Z2iIikKc2VsZi5fYWRhcHRlciA9IGFkYXB0ZXIK\"\n}" + "EP_CONTROL_IMPORT": "{\n \"PImport\": \"\",\n \"PBCode\": \"ZnJvbSBycGFfeWlzYWlxaS5jb250cmFjdF9mbG93X3N0ZXBzIGltcG9ydCBsb2dpbl9jb250cmFjdF9zeXN0ZW0sIGVudGVyX2NvbnRyYWN0X2NyZWF0ZQoKYWRhcHRlciA9IHNlbGYuX2FkYXB0ZXIKcHJpbnQoIltTMDNdIOeZu+W9leWQiOWQjOezu+e7ny4uLiIpCmxvZ2luX2NvbnRyYWN0X3N5c3RlbShhZGFwdGVyLCBzZWxmLl9jdHgpCnByaW50KCJbT0tdIOeZu+W9leWujOaIkCIpCnByaW50KCJbUzA0XSDov5vlhaXlkIjlkIzotbfojYkuLi4iKQplbnRlcl9jb250cmFjdF9jcmVhdGUoYWRhcHRlcikKcHJpbnQoIltPS10g5bey6L+b5YWl5ZCI5ZCM6LW36I2J6aG16Z2iIikK\"\n}" }, "EP_Gen": { "EP_Gen_AFTERDELAY": "{\n \"val\": \"MA==\"\n}", @@ -274,12 +274,12 @@ }, "NodeProperty": { "EP_CONTROL": { - "EP_CONTROL_IMPORT": "{\n \"PImport\": \"c3lzLCBvcywganNvbiwgcGF0aGxpYiwgdGltZQ==\",\n \"PBCode\": \"ZnJvbSB1YnBhLmlvYmplY3QuYnJvd3NlciBpbXBvcnQgQnJvd3Nlcgpmcm9tIHJwYV95aXNhaXFpLmNvbnRyYWN0X2Zsb3dfc3RlcHMgaW1wb3J0IG9wZW5fY29udHJhY3Rfc3lzdGVtCgpwcmludCgiW1MwMl0g5omT5byAIEVkZ2Ug5rWP6KeI5ZmoLi4uIikKcHJpbnQoZiIgIFVSTDoge3NlbGYuX2N0eC5jb250cmFjdF91cmx9IikKYnJvd3NlciA9IEJyb3dzZXIoQnJvd3Nlci5BcHBOYW1lLmVkZ2UpCmJyb3dzZXIub3Blbih1cmw9c2VsZi5fY3R4LmNvbnRyYWN0X3VybCwgdGltZW91dD0zMCkKc2VsZi5fYnJvd3NlciA9IGJyb3dzZXIKcHJpbnQoIltPS10g5rWP6KeI5Zmo5bey5omT5byAIikK\"\n}" + "EP_CONTROL_IMPORT": "{\n \"PImport\": \"\",\n \"PBCode\": \"ZnJvbSBycGFfeWlzYWlxaS5hcGFfYnJvd3Nlcl9hZGFwdGVyIGltcG9ydCBBcGFCcm93c2VyQWRhcHRlcgoKcHJpbnQoIltTMDJdIOWQr+WKqCBDaHJvbWUgKyDmiZPlvIDlkIjlkIzns7vnu58uLi4iKQpwcmludChmIiAgVVJMOiB7c2VsZi5fY3R4LmNvbnRyYWN0X3VybH0iKQphZGFwdGVyID0gQXBhQnJvd3NlckFkYXB0ZXIodXJsPXNlbGYuX2N0eC5jb250cmFjdF91cmwpCmFkYXB0ZXIub3Blbl91cmwoc2VsZi5fY3R4LmNvbnRyYWN0X3VybCkKc2VsZi5fYWRhcHRlciA9IGFkYXB0ZXIKcHJpbnQoIltPS10gQ2hyb21lIOW3suaJk+W8gOW5tuWvvOiIquWIsOWQiOWQjOezu+e7nyIpCg==\"\n}" }, "EP_Gen": { "EP_Gen_AFTERDELAY": "{\n \"val\": \"MA==\"\n}", "EP_Gen_BEFOREDELAY": "{\n \"val\": \"MA==\"\n}", - "EP_Gen_NOTE": "{\n \"val\": \"5omT5byARWRnZea1j+iniOWZqOW5tuWvvOiIquWIsOWQiOWQjOezu+e7nw==\"\n}" + "EP_Gen_NOTE": "{\n \"val\": \"U2VsZW5pdW3miZPlvIBDaHJvbWXmtY/op4jlmajlubblr7zoiKrliLDlkIjlkIzns7vnu58=\"\n}" } }, "NodeUI": { @@ -307,7 +307,7 @@ }, "NodeProperty": { "EP_CONTROL": { - "EP_CONTROL_IMPORT": "{\n \"PImport\": \"c3lzLCBvcywganNvbiwgcGF0aGxpYiwgdGltZQ==\",\n \"PBCode\": \"aW1wb3J0IHN5cywgb3MsIGpzb24sIHRpbWUKZnJvbSBwYXRobGliIGltcG9ydCBQYXRoCgpfQ09ERVNfRElSID0gUGF0aChyJ2Q6XFJQQVxBdXRvUnBhXGNvZGVzJykKaWYgc3RyKF9DT0RFU19ESVIpIG5vdCBpbiBzeXMucGF0aDoKICAgIHN5cy5wYXRoLmluc2VydCgwLCBzdHIoX0NPREVTX0RJUikpCgpmcm9tIHJwYV95aXNhaXFpLmNvbnRyYWN0X2Zsb3dfc3RlcHMgaW1wb3J0IENvbnRyYWN0Rmxvd0NvbnRleHQsIENvbnRyYWN0R29vZHNMaW5lCgpwcmludCgiPSIgKiA2MCkKcHJpbnQoIuWQiOWQjOW7uuWNlSBDT05UUkFDVF9DUkVBVEUgLSDliJ3lp4vljJYiKQpwcmludCgiPSIgKiA2MCkKCnNlbGYuX2N0eCA9IENvbnRyYWN0Rmxvd0NvbnRleHQoCiAgICBjb250cmFjdF91cmw9Imh0dHA6Ly9jb250cmFjdC10ZXN0Lmd6bXBjLmNvbTo4MDgwLyIsCiAgICB1c2VybmFtZT0idGVzdDAwMiIsIHBhc3N3b3JkPSIxIiwKICAgIHN1cHBsaWVyX25hbWU9Iua1i+ivleS+m+W6lOWVhiIsCiAgICBvcmRlcl9kYXRlPSIyMDI2LTA4LTEyIiwKICAgIHNvdXJjZV90eXBlPSJZWUMiLCB0ZW1wbGF0ZV90eXBlPSJOT05fRk9STUFUIiwKICAgIHRlbXBsYXRlX25hbWU9IumdnuagvOW8jy0t6YeH6LSt5Y2P6K6uIiwKICAgIGN1c3RvbWVyX2NvZGU9IkNVU1QtMDAxIiwgY3VzdG9tZXJfbmFtZT0i5rWL6K+V5L6b5bqU5ZWGIiwKICAgIGNvbnRyYWN0X2NhdGVnb3J5PSLph4fotK3ljY/orq4iLCBzZXR0bGVtZW50X3R5cGU9IuS7mOasviIsCiAgICBjb250cmFjdF9iYWNrZ3JvdW5kPSLoh6rliqjljJbmtYvor5XlkIjlkIzotbfojYkiLCBjb250cmFjdF9uYXR1cmU9IuS4u+WQiOWQjCIsCiAgICBhbW91bnRfY3VycmVuY3k9IuS6uuawkeW4gSIsIGFtb3VudD0iNTAwMDAuMDAiLAogICAgY29udHJhY3RfZW5kX2RhdGU9IjIwMjYtMTItMzEiLAogICAgaXNfbWFqb3JfY29udHJhY3Q9IuWQpiIsIGlzX3ByZXBheW1lbnQ9IuWQpiIsCiAgICBiZWxvbmdfeWVhcj0iMjAyNiIsIHBheW1lbnRfbWV0aG9kPSLotKbmnJ/mlK/ku5giLAogICAgcGF5bWVudF9kYXlzPSIzMCIsIGNvbnRyYWN0X3N0YXR1cz0i5q2j5byP5ZCI5ZCMIiwKICAgIGNvbnRhaW5zX2Jsb29kX3Byb2R1Y3RzPSLlkKYiLAogICAgc3VibWl0X21vZGU9ImRyeV9ydW4iLAogICAgZ29vZHNfbGluZXM9WwogICAgICAgIENvbnRyYWN0R29vZHNMaW5lKAogICAgICAgICAgICBvcGVyYXRpb25fY29kZT0iQUREIiwgZ29vZHNfY29kZT0iWVAtMDAxIiwKICAgICAgICAgICAgZ29vZHNfbmFtZT0i5rWL6K+V6I2v5ZOBQSIsIGFwcHJvdmFsX25vPSLlm73oja/lh4blrZdIMDAwMDAwMDEiLAogICAgICAgICAgICBzcGVjPSI1MG1nKjMw54mHIiwgdW5pdD0i55uSIiwgcXVhbnRpdHk9MTAuMCwKICAgICAgICApLAogICAgXSwKICAgIG1lcmdlZF9wZGZfcGF0aD1Ob25lLAopCgpwcmludChmIltPS10g5LiK5LiL5paH5bCx57uqOiBzb3VyY2U9e3NlbGYuX2N0eC5zb3VyY2VfdHlwZX0iKQpwcmludChmIltPS10g5qih5p2/OiB7c2VsZi5fY3R4LnRlbXBsYXRlX25hbWV9IikKcHJpbnQoZiJbT0tdIOi0p+WTgeihjOaVsDoge2xlbihzZWxmLl9jdHguZ29vZHNfbGluZXMpfSIpCg==\"\n}" + "EP_CONTROL_IMPORT": "{\n \"PImport\": \"\",\n \"PBCode\": \"aW1wb3J0IHN5cywgb3MsIGpzb24sIHRpbWUKZnJvbSBwYXRobGliIGltcG9ydCBQYXRoCgpfQ09ERVNfRElSID0gUGF0aChyJ2Q6XFJQQVxBdXRvUnBhXGNvZGVzJykKaWYgc3RyKF9DT0RFU19ESVIpIG5vdCBpbiBzeXMucGF0aDoKICAgIHN5cy5wYXRoLmluc2VydCgwLCBzdHIoX0NPREVTX0RJUikpCgpmcm9tIHJwYV95aXNhaXFpLmNvbnRyYWN0X2Zsb3dfc3RlcHMgaW1wb3J0IENvbnRyYWN0Rmxvd0NvbnRleHQsIENvbnRyYWN0R29vZHNMaW5lCgpwcmludCgiPSIgKiA2MCkKcHJpbnQoIuWQiOWQjOW7uuWNlSBDT05UUkFDVF9DUkVBVEUgLSDliJ3lp4vljJYiKQpwcmludCgiPSIgKiA2MCkKCnNlbGYuX2N0eCA9IENvbnRyYWN0Rmxvd0NvbnRleHQoCiAgICBjb250cmFjdF91cmw9Imh0dHA6Ly9jb250cmFjdC10ZXN0Lmd6bXBjLmNvbTo4MDgwLyIsCiAgICB1c2VybmFtZT0idGVzdDAwMiIsIHBhc3N3b3JkPSIxIiwKICAgIHN1cHBsaWVyX25hbWU9Iua1i+ivleS+m+W6lOWVhiIsCiAgICBvcmRlcl9kYXRlPSIyMDI2LTA4LTEyIiwKICAgIHNvdXJjZV90eXBlPSJZWUMiLCB0ZW1wbGF0ZV90eXBlPSJOT05fRk9STUFUIiwKICAgIHRlbXBsYXRlX25hbWU9IumdnuagvOW8jy0t6YeH6LSt5Y2P6K6uIiwKICAgIGN1c3RvbWVyX2NvZGU9IkNVU1QtMDAxIiwgY3VzdG9tZXJfbmFtZT0i5rWL6K+V5L6b5bqU5ZWGIiwKICAgIGNvbnRyYWN0X2NhdGVnb3J5PSLph4fotK3ljY/orq4iLCBzZXR0bGVtZW50X3R5cGU9IuS7mOasviIsCiAgICBjb250cmFjdF9iYWNrZ3JvdW5kPSLoh6rliqjljJbmtYvor5XlkIjlkIzotbfojYkiLCBjb250cmFjdF9uYXR1cmU9IuS4u+WQiOWQjCIsCiAgICBhbW91bnRfY3VycmVuY3k9IuS6uuawkeW4gSIsIGFtb3VudD0iNTAwMDAuMDAiLAogICAgY29udHJhY3RfZW5kX2RhdGU9IjIwMjYtMTItMzEiLAogICAgaXNfbWFqb3JfY29udHJhY3Q9IuWQpiIsIGlzX3ByZXBheW1lbnQ9IuWQpiIsCiAgICBiZWxvbmdfeWVhcj0iMjAyNiIsIHBheW1lbnRfbWV0aG9kPSLotKbmnJ/mlK/ku5giLAogICAgcGF5bWVudF9kYXlzPSIzMCIsIGNvbnRyYWN0X3N0YXR1cz0i5q2j5byP5ZCI5ZCMIiwKICAgIGNvbnRhaW5zX2Jsb29kX3Byb2R1Y3RzPSLlkKYiLAogICAgc3VibWl0X21vZGU9ImRyeV9ydW4iLAogICAgZ29vZHNfbGluZXM9WwogICAgICAgIENvbnRyYWN0R29vZHNMaW5lKAogICAgICAgICAgICBvcGVyYXRpb25fY29kZT0iQUREIiwgZ29vZHNfY29kZT0iWVAtMDAxIiwKICAgICAgICAgICAgZ29vZHNfbmFtZT0i5rWL6K+V6I2v5ZOBQSIsIGFwcHJvdmFsX25vPSLlm73oja/lh4blrZdIMDAwMDAwMDEiLAogICAgICAgICAgICBzcGVjPSI1MG1nKjMw54mHIiwgdW5pdD0i55uSIiwgcXVhbnRpdHk9MTAuMCwKICAgICAgICApLAogICAgXSwKICAgIG1lcmdlZF9wZGZfcGF0aD1Ob25lLAopCgpwcmludChmIltPS10g5LiK5LiL5paH5bCx57uqOiBzb3VyY2U9e3NlbGYuX2N0eC5zb3VyY2VfdHlwZX0iKQpwcmludChmIltPS10g5qih5p2/OiB7c2VsZi5fY3R4LnRlbXBsYXRlX25hbWV9IikKcHJpbnQoZiJbT0tdIOi0p+WTgeihjOaVsDoge2xlbihzZWxmLl9jdHguZ29vZHNfbGluZXMpfSIpCg==\"\n}" }, "EP_Gen": { "EP_Gen_AFTERDELAY": "{\n \"val\": \"MA==\"\n}", @@ -352,7 +352,7 @@ } ], "SeqTag": "Main", - "ViewCenterPx": "866.266", - "ViewCenterPy": "755.823", - "ViewScaling": "1.331" + "ViewCenterPx": "1361.61", + "ViewCenterPy": "613.591", + "ViewScaling": "0.751315" } diff --git a/Project.pro b/Project.pro index 0640a63..df171da 100644 --- a/Project.pro +++ b/Project.pro @@ -5,7 +5,7 @@ "ProInfo": { "CodeCustomCount": "0", "ProAbsolutePath": "D:/RPA/AutoRpa", - "ProChange": "2026/08/12 13:57:25", + "ProChange": "2026/08/13 10:38:45", "ProColorRef": "4", "ProCreate": "2026/08/12 10:13:31", "ProDesc": "", diff --git a/build_seq.py b/build_seq.py index 71324e0..0097384 100644 --- a/build_seq.py +++ b/build_seq.py @@ -1,23 +1,16 @@ # coding=utf-8 """ -构建 Main.seq - 合同建单流程(多节点可视化 + 真实浏览器) +构建 Main.seq - 合同建单流程(Chrome浏览器版本) """ import json import base64 def b64_apa(val): - """将值编码为 APA val 格式""" return json.dumps({"val": base64.b64encode(str(val).encode()).decode()}, indent=4) def b64_raw(val): - """纯 base64 编码""" return base64.b64encode(str(val).encode()).decode() -# ============================================================ -# 各步骤代码 -# ============================================================ - -# S01: 初始化环境 s01_body = """\ import sys, os, json, time from pathlib import Path @@ -64,38 +57,29 @@ print(f"[OK] 模板: {self._ctx.template_name}") print(f"[OK] 货品行数: {len(self._ctx.goods_lines)}") """ -# S02: 打开浏览器 s02_body = """\ -from ubpa.iobject.browser import Browser -from rpa_yisaiqi.contract_flow_steps import open_contract_system - -print("[S02] 打开 Edge 浏览器...") -print(f" URL: {self._ctx.contract_url}") -browser = Browser(Browser.AppName.edge) -browser.open(url=self._ctx.contract_url, timeout=30) -self._browser = browser -print("[OK] 浏览器已打开") -""" - -# S03: 登录 + 进入起草 -s03_body = """\ -from rpa_yisaiqi.contract_flow_steps import login_contract_system, enter_contract_create from rpa_yisaiqi.apa_browser_adapter import ApaBrowserAdapter -adapter = ApaBrowserAdapter() -adapter._browser = self._browser -adapter._is_open = True +print("[S02] 启动 Chrome + 打开合同系统...") +print(f" URL: {self._ctx.contract_url}") +adapter = ApaBrowserAdapter(url=self._ctx.contract_url) +adapter.open_url(self._ctx.contract_url) +self._adapter = adapter +print("[OK] Chrome 已打开并导航到合同系统") +""" +s03_body = """\ +from rpa_yisaiqi.contract_flow_steps import login_contract_system, enter_contract_create + +adapter = self._adapter print("[S03] 登录合同系统...") login_contract_system(adapter, self._ctx) print("[OK] 登录完成") print("[S04] 进入合同起草...") enter_contract_create(adapter) print("[OK] 已进入合同起草页面") -self._adapter = adapter """ -# S04: 选择模板 + 签约方信息 s04_body = """\ from rpa_yisaiqi.contract_flow_steps import select_contract_template, fill_contract_signatory_info @@ -109,7 +93,6 @@ fill_contract_signatory_info(adapter, self._ctx) print(f"[OK] 签约方: {self._ctx.customer_name}") """ -# S05: 基础表单 + 金额日期 s05_body = """\ from rpa_yisaiqi.contract_flow_steps import fill_contract_basic_info, fill_contract_amount_and_dates @@ -123,7 +106,6 @@ fill_contract_amount_and_dates(adapter, self._ctx) print(f"[OK] 金额={self._ctx.amount}, 结束日期={self._ctx.contract_end_date}") """ -# S06: 货品明细 + 付款信息 + 提交 s06_body = """\ from rpa_yisaiqi.contract_flow_steps import ( fill_contract_goods_lines, fill_contract_payment_info, @@ -160,33 +142,18 @@ else: print("=" * 60) """ -# ============================================================ -# 节点定义 -# ============================================================ steps = [ - ("S01", "S01 初始化 & 上下文构建", s01_body, "设置环境变量并构建YYC测试上下文"), - ("S02", "S02 打开Edge浏览器", s02_body, "打开Edge浏览器并导航到合同系统"), - ("S03", "S03-S04 登录 & 进入起草", s03_body, "登录系统并进入合同起草页面"), - ("S04", "S05-S06 模板选择 & 签约方", s04_body, "选择模板并填写签约方信息"), - ("S05", "S07-S08 基础表单 & 金额日期", s05_body, "填写合同类别、金额、日期"), - ("S06", "S09-S15 货品/付款/提交", s06_body, "填写货品明细、付款信息并提交"), + ("S01 初始化 & 上下文构建", s01_body, "设置环境变量并构建YYC测试上下文"), + ("S02 启动Chrome", s02_body, "Selenium打开Chrome浏览器并导航到合同系统"), + ("S03-S04 登录 & 进入起草", s03_body, "登录系统并进入合同起草页面"), + ("S05-S06 模板选择 & 签约方", s04_body, "选择模板并填写签约方信息"), + ("S07-S08 基础表单 & 金额日期", s05_body, "填写合同类别、金额、日期"), + ("S09-S15 货品/付款/提交", s06_body, "填写货品明细、付款信息并提交"), ] -def make_code_import_props(pimport, pbody): - """创建 EP_CONTROL_IMPORT 的 JSON 字符串""" - inner = json.dumps({ - "PImport": pimport, - "PBCode": pbody - }, indent=4, ensure_ascii=False) - return inner - -# ============================================================ -# 构建 Main.seq -# ============================================================ nodes = [] lines = [] -# 开始节点 begin_tag = "20260812100000" nodes.append({ "NodePanelData": { @@ -207,14 +174,13 @@ start_x = 500 start_y = 200 space_y = 120 -for i, (sid, title, body, note) in enumerate(steps): +for i, (title, body, note) in enumerate(steps): tag = f"20260812{200000 + i:06d}" py = start_y + i * space_y - - # Encode as raw base64 for PBCode/PImport - pimport_b64 = b64_raw("sys, os, json, pathlib, time") + + pimport_b64 = b64_raw("") pbody_b64 = b64_raw(body) - + props = { "EP_CONTROL": { "EP_CONTROL_IMPORT": json.dumps({"PImport": pimport_b64, "PBCode": pbody_b64}, indent=4) @@ -225,7 +191,7 @@ for i, (sid, title, body, note) in enumerate(steps): "EP_Gen_NOTE": b64_apa(note) } } - + nodes.append({ "NodePanelData": { "ExpandedGroups": [], "InputProperty": {"mouse_x": 0, "mouse_y": 0}, @@ -239,7 +205,7 @@ for i, (sid, title, body, note) in enumerate(steps): "NodeTitle": title } }) - + lines.append({ "LineColor": "255, 255, 255", "LineNote": "", "LineType": 0, @@ -256,7 +222,7 @@ for i, (sid, title, body, note) in enumerate(steps): }, "LineTag": f"20260812{300000 + i:06d}" }) - + prev_tag = tag center_y = start_y + len(steps) * space_y // 2 @@ -273,6 +239,5 @@ main_seq = { with open(r"d:\RPA\AutoRpa\Main.seq", "w", encoding="utf-8") as f: json.dump(main_seq, f, ensure_ascii=False, indent=4) -print(f"Main.seq 已生成: {len(nodes)} 节点, {len(lines)} 连线") for n in nodes: print(f" [{n['NodeUI']['NodeSubType'].replace('EnumWidget_','').replace('_Code_Custom','CODE').replace('_Begin','START')}] {n['NodeUI']['NodeTitle']}") diff --git a/codes/GlobalFun.py b/codes/GlobalFun.py index 1fd5a5b..d4ff247 100644 --- a/codes/GlobalFun.py +++ b/codes/GlobalFun.py @@ -1,149 +1,22 @@ # coding=utf-8 # 编译日期:2026-08-12 10:13:31 # 版权所有:www.i-search.com.cn -# -# ============================================================================ -# 合同建单 (CONTRACT_CREATE) RPA 流程 - 全局函数 -# ============================================================================ -# 在 APA Studio 的 Python 脚本节点中调用: -# 1. 拖一个 "Python 脚本块" 到画布 -# 2. 从 codes/rpa_yisaiqi 导入需要的函数 -# 3. 示例见下方 __main__ 块 -# -# 流程图节点清单 (15步 + 4异常分支): -# 参考: codes/rpa_yisaiqi/flows/contract_create_flow.json -# ============================================================================ - -import json -import os -import sys -from pathlib import Path - -# 确保 codes 目录在 sys.path 中,以便 APA Python 3.8 能导入 rpa_yisaiqi -_CODES_DIR = Path(__file__).resolve().parent # .../AutoRpa/codes/ -if str(_CODES_DIR) not in sys.path: - sys.path.insert(0, str(_CODES_DIR)) -def verify_module_imports(): - """验证所有模块可以正常导入(不依赖 APA ubpa SDK)。""" - results = {} - modules = [ - ("callback_client", "rpa_yisaiqi.callback_client", "StateCenterClient"), - ("contract_flow_steps", "rpa_yisaiqi.contract_flow_steps", "ContractFlowContext"), - ("contract_draft", "rpa_yisaiqi.contract_draft", "ContractDraft"), - ("pdf_utils", "rpa_yisaiqi.pdf_utils", "ensure_merged_pdf"), - ("contract_create_node", "rpa_yisaiqi.contract_create_node", "ContractCreateInput"), - ] - for name, module_path, class_name in modules: - try: - mod = __import__(module_path, fromlist=[class_name]) - getattr(mod, class_name) - results[name] = "OK" - except Exception as e: - results[name] = f"FAIL: {e}" - return results -def build_yyc_dry_run_context(): - """构建 YYC 来源的 dry-run 流程上下文(不提交合同)。""" - from rpa_yisaiqi.contract_flow_steps import ContractFlowContext, ContractGoodsLine - - return ContractFlowContext( - contract_url="http://contract-test.gzmpc.com:8080/", - username="test002", - password="1", - supplier_name="测试供应商", - order_date="2026-08-12", - source_type="YYC", - template_type="NON_FORMAT", - template_name="非格式--采购协议", - customer_code="CUST-001", - customer_name="测试供应商", - contract_category="采购协议", - settlement_type="付款", - contract_background="自动化测试合同起草", - contract_nature="主合同", - amount_currency="人民币", - amount="50000.00", - contract_end_date="2026-12-31", - is_major_contract="否", - is_prepayment="否", - belong_year="2026", - payment_method="账期支付", - payment_days="30", - contract_status="正式合同", - contains_blood_products="否", - submit_mode="dry_run", - goods_lines=[ - ContractGoodsLine( - operation_code="ADD", goods_code="YP-001", - goods_name="测试药品A", approval_no="国药准字H00000001", - spec="50mg*30片", unit="盒", quantity=10.0, - ), - ], - merged_pdf_path=None, - ) -def load_draft_from_json(json_path): - """从 JSON 文件加载合同起草数据。""" - from rpa_yisaiqi.contract_draft import load_contract_draft - return load_contract_draft(json_path) -def run_contract_flow_with_apa_browser(browser, ctx): - """ - 在 APA Studio 中使用真实的 ubpa 浏览器适配器执行流程。 - - browser: 实现了 BrowserAdapter 协议的对象 (如 ubpa 的 iedge/ichrome_firefox) - ctx: ContractFlowContext 实例 - - 返回合同编号字符串(dry_run 模式返回空字符串)。 - """ - from rpa_yisaiqi.contract_flow_steps import run_contract_create_flow - return run_contract_create_flow(browser, ctx) -# ============================================================================ -# APA Studio 集成说明: -# -# 方式一: Python 脚本块(推荐) -# 在 APA Studio 画布拖一个 "Python 脚本块",写入: -# -# import sys -# sys.path.insert(0, r"d:\RPA\AutoRpa\codes") -# from GlobalFun import build_yyc_dry_run_context, run_contract_flow_with_apa_browser -# from rpa_yisaiqi.contract_flow_steps import BrowserAdapter -# -# ctx = build_yyc_dry_run_context() -# # browser 由 APA Studio 提供 (如 ubpa 网页对象) -# contract_no = run_contract_flow_with_apa_browser(browser, ctx) -# -# 方式二: 原生活动拖拽 (15个节点) -# 按 codes/rpa_yisaiqi/flows/contract_create_flow.json 的顺序, -# 在画布上依次拖拽: 打开网页 → 输入文本 → 鼠标点击 → ... -# 每个节点对应 contract_flow_steps.py 中的一个函数。 -# ============================================================================ + + + + # 测试运行或调试以上全局函数入口 if __name__ == '__main__': - print("GlobalFun 模块导入验证:") - results = verify_module_imports() - for name, status in results.items(): - print(f" [{status}] {name}") - - print("\n构建 YYC dry-run 上下文:") - ctx = build_yyc_dry_run_context() - print(f" supplier_name={ctx.supplier_name}") - print(f" source_type={ctx.source_type}") - print(f" template_name={ctx.template_name}") - print(f" submit_mode={ctx.submit_mode}") - print(f" goods_lines={len(ctx.goods_lines)} 条") - - print("\n加载 YYC JSON 示例:") - draft = load_draft_from_json( - r"d:\RPA\AutoRpa\codes\rpa_yisaiqi\examples\contract_draft_yyc.json" - ) - print(f" supplier={draft.supplier_name}, source={draft.source_type}") + pass \ No newline at end of file diff --git a/codes/Main.py b/codes/Main.py index c10b429..46adf91 100644 --- a/codes/Main.py +++ b/codes/Main.py @@ -1,5 +1,5 @@ # coding=utf-8 -# 编译日期:2026-08-12 11:59:19 +# 编译日期:2026-08-13 09:38:51 # 版权所有:www.i-search.com.cn import ubpa.init_input as iinput from ubpa.base_util import StdOutHook, ExceptionHandler @@ -43,7 +43,120 @@ class AutoRpa: def Main(self): #插入代码块#1 - self.__logger.dlogs(job_no=self.job_no,logmsg='Flow:Main,StepNodeTag:20260812200000,Title:插入代码块#1,Note:合同建单完整流程 - Edge浏览器自动化') + self.__logger.dlogs(job_no=self.job_no,logmsg='Flow:Main,StepNodeTag:20260812200000,Title:插入代码块#1,Note:设置环境变量并构建YYC测试上下文') + import sys, os, json, time + from pathlib import Path + _CODES_DIR = Path(r'd:\RPA\AutoRpa\codes') + if str(_CODES_DIR) not in sys.path: + sys.path.insert(0, str(_CODES_DIR)) + from rpa_yisaiqi.contract_flow_steps import ContractFlowContext, ContractGoodsLine + print("=" * 60) + print("合同建单 CONTRACT_CREATE - 初始化") + print("=" * 60) + self._ctx = ContractFlowContext( + contract_url="http://contract-test.gzmpc.com:8080/", + username="test002", password="1", + supplier_name="测试供应商", + order_date="2026-08-12", + source_type="YYC", template_type="NON_FORMAT", + template_name="非格式--采购协议", + customer_code="CUST-001", customer_name="测试供应商", + contract_category="采购协议", settlement_type="付款", + contract_background="自动化测试合同起草", contract_nature="主合同", + amount_currency="人民币", amount="50000.00", + contract_end_date="2026-12-31", + is_major_contract="否", is_prepayment="否", + belong_year="2026", payment_method="账期支付", + payment_days="30", contract_status="正式合同", + contains_blood_products="否", + submit_mode="dry_run", + goods_lines=[ + ContractGoodsLine( + operation_code="ADD", goods_code="YP-001", + goods_name="测试药品A", approval_no="国药准字H00000001", + spec="50mg*30片", unit="盒", quantity=10.0, + ), + ], + merged_pdf_path=None, + ) + print(f"[OK] 上下文就绪: source={self._ctx.source_type}") + print(f"[OK] 模板: {self._ctx.template_name}") + print(f"[OK] 货品行数: {len(self._ctx.goods_lines)}") + + #插入代码块#1 + self.__logger.dlogs(job_no=self.job_no,logmsg='Flow:Main,StepNodeTag:20260812200001,Title:插入代码块#1,Note:Selenium打开Chrome浏览器并导航到合同系统') + from rpa_yisaiqi.apa_browser_adapter import ApaBrowserAdapter + print("[S02] 启动 Chrome + 打开合同系统...") + print(f" URL: {self._ctx.contract_url}") + adapter = ApaBrowserAdapter(url=self._ctx.contract_url) + adapter.open_url(self._ctx.contract_url) + self._adapter = adapter + print("[OK] Chrome 已打开并导航到合同系统") + + #插入代码块#1 + self.__logger.dlogs(job_no=self.job_no,logmsg='Flow:Main,StepNodeTag:20260812200002,Title:插入代码块#1,Note:登录系统并进入合同起草页面') + from rpa_yisaiqi.contract_flow_steps import login_contract_system, enter_contract_create + adapter = self._adapter + print("[S03] 登录合同系统...") + login_contract_system(adapter, self._ctx) + print("[OK] 登录完成") + print("[S04] 进入合同起草...") + enter_contract_create(adapter) + print("[OK] 已进入合同起草页面") + + #插入代码块#1 + self.__logger.dlogs(job_no=self.job_no,logmsg='Flow:Main,StepNodeTag:20260812200003,Title:插入代码块#1,Note:选择模板并填写签约方信息') + from rpa_yisaiqi.contract_flow_steps import select_contract_template, fill_contract_signatory_info + adapter = self._adapter + print("[S05] 选择合同模板...") + select_contract_template(adapter, self._ctx) + print(f"[OK] 模板: {self._ctx.template_name}") + print("[S06] 填写签约方信息...") + fill_contract_signatory_info(adapter, self._ctx) + print(f"[OK] 签约方: {self._ctx.customer_name}") + + #插入代码块#1 + self.__logger.dlogs(job_no=self.job_no,logmsg='Flow:Main,StepNodeTag:20260812200004,Title:插入代码块#1,Note:填写合同类别、金额、日期') + from rpa_yisaiqi.contract_flow_steps import fill_contract_basic_info, fill_contract_amount_and_dates + adapter = self._adapter + print("[S07] 填写基础表单...") + fill_contract_basic_info(adapter, self._ctx) + print(f"[OK] 类别={self._ctx.contract_category}, 结算={self._ctx.settlement_type}") + print("[S08] 填写金额与日期...") + fill_contract_amount_and_dates(adapter, self._ctx) + print(f"[OK] 金额={self._ctx.amount}, 结束日期={self._ctx.contract_end_date}") + + #插入代码块#1 + self.__logger.dlogs(job_no=self.job_no,logmsg='Flow:Main,StepNodeTag:20260812200005,Title:插入代码块#1,Note:填写货品明细、付款信息并提交') + from rpa_yisaiqi.contract_flow_steps import ( + fill_contract_goods_lines, fill_contract_payment_info, + upload_contract_document_if_needed, submit_contract_for_approval, + read_contract_number + ) + adapter = self._adapter + print("[S09] 填写货品明细...") + fill_contract_goods_lines(adapter, self._ctx) + print(f"[OK] 货品: {len(self._ctx.goods_lines)} 行") + print("[S10] 填写付款信息...") + fill_contract_payment_info(adapter, self._ctx) + print(f"[OK] 付款方式={self._ctx.payment_method}") + if self._ctx.source_type == "YYC": + print("[S11] 合并PDF [YYC专属]... (单PDF, 跳过合并)") + print("[S12] 上传PDF附件 [YYC专属]... (dry_run 跳过)") + print() + print("[S13] 提交审批...") + if not submit_contract_for_approval(adapter, self._ctx): + print("[DRY-RUN] 演练模式,跳过实际提交") + print("=" * 60) + print("合同建单流程演练完成") + print("=" * 60) + else: + print("[S14] 读取合同编号...") + contract_no = read_contract_number(adapter) + print(f"[OK] 合同编号: {contract_no}") + print("=" * 60) + print(f"[COMPLETED] 合同创建成功: {contract_no}") + print("=" * 60) if __name__ == '__main__': diff --git a/codes/rpa_yisaiqi/__pycache__/apa_browser_adapter.cpython-38.pyc b/codes/rpa_yisaiqi/__pycache__/apa_browser_adapter.cpython-38.pyc new file mode 100644 index 0000000..6b60130 Binary files /dev/null and b/codes/rpa_yisaiqi/__pycache__/apa_browser_adapter.cpython-38.pyc differ diff --git a/codes/rpa_yisaiqi/__pycache__/contract_flow_steps.cpython-38.pyc b/codes/rpa_yisaiqi/__pycache__/contract_flow_steps.cpython-38.pyc index 3955441..33559a1 100644 Binary files a/codes/rpa_yisaiqi/__pycache__/contract_flow_steps.cpython-38.pyc and b/codes/rpa_yisaiqi/__pycache__/contract_flow_steps.cpython-38.pyc differ diff --git a/codes/rpa_yisaiqi/apa_browser_adapter.py b/codes/rpa_yisaiqi/apa_browser_adapter.py index edd5947..23971a2 100644 --- a/codes/rpa_yisaiqi/apa_browser_adapter.py +++ b/codes/rpa_yisaiqi/apa_browser_adapter.py @@ -1,184 +1,236 @@ # coding=utf-8 """ -APA (艺赛旗) BrowserAdapter 实现 -将 contract_flow_steps.BrowserAdapter 协议映射到 ubpa/iedge 浏览器 API +APA (艺赛旗) Selenium Chrome BrowserAdapter 实现 +通过 Selenium WebDriver 直接操控 Chrome,不依赖 APA 浏览器模块 """ -from __future__ import annotations -import json import time -import base64 - - -def _make_apa_attr_map(css_selector: str) -> str: - """将 CSS 选择器转换为 APA attrMap JSON 格式""" - hierarchy = { - "hasReachedRelativeAncestor": "false", - "nodeHierarchyInfo": [ - { - "isPresentInSelector": 1, - "otherAttributes": {}, - "selectorInfo": { - "attributes": { - "css-selector": css_selector, - "tag": "", - "xpath": "" - }, - "index": 0, - "tagName": "" - } - } - ] - } - return json.dumps(hierarchy, ensure_ascii=False) class ApaBrowserAdapter: - """APA Studio Edge 浏览器适配器,实现 BrowserAdapter 协议""" + """Selenium Chrome 浏览器适配器,实现 BrowserAdapter 协议""" def __init__(self, url: str = ""): - from ubpa.iobject.browser import Browser - self._browser = Browser(Browser.AppName.edge) self._url = url + self._driver = None self._is_open = False + def _get_driver(self): + """创建或获取 Chrome WebDriver""" + if self._driver is not None: + return self._driver + + from selenium import webdriver + from selenium.webdriver.chrome.service import Service + from selenium.webdriver.chrome.options import Options + from webdriver_manager.chrome import ChromeDriverManager + + options = Options() + # 不自动关闭浏览器,方便调试 + options.add_experimental_option("detach", True) + # 避免被检测为自动化 + options.add_experimental_option("excludeSwitches", ["enable-automation"]) + options.add_experimental_option("useAutomationExtension", False) + # 基本参数 + options.add_argument("--disable-blink-features=AutomationControlled") + options.add_argument("--no-first-run") + options.add_argument("--no-default-browser-check") + + service = Service(ChromeDriverManager().install()) + self._driver = webdriver.Chrome(service=service, options=options) + return self._driver + def _ensure_open(self): - if not self._is_open: - from ubpa.iobject.browser import Browser - self._browser.open(url=self._url, timeout=30) - self._is_open = True + if self._driver is None: + driver = self._get_driver() + if self._url: + driver.get(self._url) + self._is_open = True + + # ---- 内部查找方法 ---- + + def _find(self, selector: str, timeout: int = 5): + """ + 智能查找元素:支持 CSS、XPath、text=、label=、aria=、title=、:contains() + """ + from selenium.webdriver.common.by import By + from selenium.common.exceptions import NoSuchElementException + import re + + # 1. text=xxx → 查找包含对应文本的元素 + if selector.startswith("text="): + text_val = selector[5:].strip('"').strip("'") + xpath = f'//*[contains(text(),"{text_val}")]' + return self._driver.find_element(By.XPATH, xpath) + + # 2. label=xxx → 找到对应标签的输入框 + if selector.startswith("label="): + text_val = selector[6:].strip('"').strip("'") + xpath = f'//label[contains(text(),"{text_val}")]/following-sibling::*[1][self::input or self::select or self::textarea]' + try: + return self._driver.find_element(By.XPATH, xpath) + except NoSuchElementException: + # Fallback: find the label's "for" attribute + xpath2 = f'//label[contains(text(),"{text_val}")]/@for' + for_id = self._driver.find_element(By.XPATH, xpath2).get_attribute("for") if False else None + if for_id: + return self._driver.find_element(By.ID, for_id) + raise + + # 3. aria=xxx → aria-label 属性 + if selector.startswith("aria="): + text_val = selector[5:].strip('"').strip("'") + return self._driver.find_element(By.CSS_SELECTOR, f'[aria-label="{text_val}"]') + + # 4. title=xxx → title 属性 + if selector.startswith("title="): + text_val = selector[6:].strip('"').strip("'") + return self._driver.find_element(By.CSS_SELECTOR, f'[title="{text_val}"]') + + # 5. :contains() → 转 XPath + if ":contains(" in selector: + m = re.match(r'(.+?):contains\("(.+?)"\)', selector) + if m: + tag_part, text_val = m.groups() + xpath = f'//{tag_part}[contains(.,"{text_val}")]' + return self._driver.find_element(By.XPATH, xpath) + + # 6. 显式 XPath(以 / 或 // 开头)优先按 XPath 查找 + if selector.startswith("//") or selector.startswith("/"): + return self._driver.find_element(By.XPATH, selector) + + # 7. 标准 CSS selector + return self._driver.find_element(By.CSS_SELECTOR, selector) # ---- BrowserAdapter 协议实现 ---- def open_url(self, url: str) -> None: self._url = url - self._ensure_open() + self._get_driver().get(url) + self._is_open = True - def fill_first(self, selectors: list[str], value: str) -> None: + def _set_value_via_js(self, el, value: str) -> None: + """通过原生 value setter + 事件注入设置值,兼容 LUI/React 受控组件""" + self._driver.execute_script( + """ + var el = arguments[0]; + var value = arguments[1]; + var proto = el.tagName === 'TEXTAREA' + ? window.HTMLTextAreaElement.prototype + : window.HTMLInputElement.prototype; + var setter = Object.getOwnPropertyDescriptor(proto, 'value').set; + setter.call(el, value); + el.dispatchEvent(new Event('input', { bubbles: true })); + el.dispatchEvent(new Event('change', { bubbles: true })); + el.dispatchEvent(new Event('blur', { bubbles: true })); + """, + el, + str(value), + ) + + def fill_first(self, selectors: list, value: str) -> None: self._ensure_open() - import ubpa.iedge as iedge last_error = None for sel in selectors: try: - attr_map = _make_apa_attr_map(sel) - iedge.set_element_val( - attrMap=attr_map, title="*", url="*", - text=str(value), waitfor=5, program="msedge.exe" - ) + el = self._find(sel) + # 先尝试原生输入;LUI 受控组件用 JS 注入兜底 + try: + el.clear() + el.send_keys(str(value)) + except Exception: + pass + if el.get_attribute("value") != str(value): + self._set_value_via_js(el, str(value)) time.sleep(0.3) return except Exception as e: last_error = e continue - raise RuntimeError( - f"fill_first failed for all selectors {selectors}: {last_error}" - ) + raise RuntimeError(f"fill_first failed: {selectors}, last: {last_error}") - def click_first(self, selectors: list[str]) -> None: + def click_first(self, selectors: list) -> None: self._ensure_open() - import ubpa.iedge as iedge last_error = None for sel in selectors: try: - attr_map = _make_apa_attr_map(sel) - iedge.do_click_pos( - attrMap=attr_map, title="*", url="*", - waitfor=5, program="msedge.exe" - ) + el = self._find(sel) + # LUI/委托事件用 JS 点击最可靠 + self._driver.execute_script("arguments[0].click()", el) time.sleep(0.5) return except Exception as e: last_error = e continue - raise RuntimeError( - f"click_first failed for all selectors {selectors}: {last_error}" - ) + raise RuntimeError(f"click_first failed: {selectors}, last: {last_error}") def wait_for_text(self, text: str, timeout_seconds: int = 15) -> None: - import ubpa.iedge as iedge - elapsed = 0 - interval = 1 - while elapsed < timeout_seconds: - try: - # 尝试获取页面文本匹配 - result = iedge.get_element_val( - attrMap=_make_apa_attr_map(f"text={text}"), - title="*", url="*", waitfor=3, program="msedge.exe" - ) - if result: - return - except Exception: - pass - time.sleep(interval) - elapsed += interval - raise TimeoutError(f"wait_for_text '{text}' timed out after {timeout_seconds}s") - - def select_first(self, selectors: list[str], value: str) -> None: self._ensure_open() - import ubpa.iedge as iedge + from selenium.webdriver.support.ui import WebDriverWait + from selenium.webdriver.support import expected_conditions as EC + from selenium.webdriver.common.by import By + try: + WebDriverWait(self._driver, timeout_seconds).until( + EC.text_to_be_present_in_element((By.TAG_NAME, "body"), text) + ) + except Exception as e: + raise TimeoutError(f"wait_for_text '{text}' timed out: {e}") + + def select_first(self, selectors: list, value: str) -> None: + self._ensure_open() + from selenium.webdriver.support.ui import Select last_error = None for sel in selectors: try: - attr_map = _make_apa_attr_map(sel) - iedge.set_element_selected_item( - attrMap=attr_map, title="*", url="*", - itemText=value, itemMode="text", - waitfor=5, program="msedge.exe" - ) + el = self._find(sel) + Select(el).select_by_visible_text(value) time.sleep(0.3) return except Exception as e: last_error = e continue - raise RuntimeError( - f"select_first failed for all selectors {selectors}: {last_error}" - ) + raise RuntimeError(f"select_first failed: {selectors}, last: {last_error}") - def upload_file_first(self, selectors: list[str], file_path: str) -> None: + def upload_file_first(self, selectors: list, file_path: str) -> None: self._ensure_open() - import ubpa.iedge as iedge last_error = None for sel in selectors: try: - attr_map = _make_apa_attr_map(sel) - iedge.set_element_val( - attrMap=attr_map, title="*", url="*", - text=file_path, waitfor=5, program="msedge.exe" - ) + el = self._find(sel) + el.send_keys(file_path) time.sleep(1) return except Exception as e: last_error = e continue - raise RuntimeError( - f"upload_file_first failed for all selectors {selectors}: {last_error}" - ) + raise RuntimeError(f"upload_file_first failed: {selectors}, last: {last_error}") - def read_first(self, selectors: list[str], value_name: str = "") -> str: + def read_first(self, selectors: list, value_name: str = "") -> str: self._ensure_open() - import ubpa.iedge as iedge last_error = None for sel in selectors: try: - attr_map = _make_apa_attr_map(sel) - result = iedge.get_element_val( - attrMap=attr_map, title="*", url="*", - waitfor=5, program="msedge.exe" - ) + el = self._find(sel) + result = el.text or el.get_attribute("value") or "" if result: - return str(result) + return result except Exception as e: last_error = e continue - raise RuntimeError( - f"read_first failed for all selectors {selectors}: {last_error}" - ) + raise RuntimeError(f"read_first failed: {selectors}, last: {last_error}") + + def read_body_text(self) -> str: + self._ensure_open() + try: + return self._driver.find_element("tag name", "body").text + except Exception: + return "" def screenshot(self, name: str) -> str: import os save_dir = os.path.join(os.path.dirname(__file__), "screenshots") os.makedirs(save_dir, exist_ok=True) path = os.path.join(save_dir, f"{name}.png") - # APA browser screenshot capability - placeholder - print(f" [screenshot] {name} -> {path}") + if self._driver: + self._driver.save_screenshot(path) return path diff --git a/codes/rpa_yisaiqi/callback_client.py b/codes/rpa_yisaiqi/callback_client.py index 41479f1..decff95 100644 --- a/codes/rpa_yisaiqi/callback_client.py +++ b/codes/rpa_yisaiqi/callback_client.py @@ -7,7 +7,7 @@ from urllib.error import HTTPError from urllib.parse import quote from urllib.request import Request, urlopen -JsonDict = dict +JsonDict = dict[str, Any] Opener = Callable[[Request, float], Any] diff --git a/codes/rpa_yisaiqi/contract_flow_steps.py b/codes/rpa_yisaiqi/contract_flow_steps.py index bc0b0cf..5b96447 100644 --- a/codes/rpa_yisaiqi/contract_flow_steps.py +++ b/codes/rpa_yisaiqi/contract_flow_steps.py @@ -78,6 +78,56 @@ def run_contract_create_flow(browser: BrowserAdapter, ctx: ContractFlowContext) return read_contract_number(browser) +# 合同起草页(模板选择)直达 URL,绕过 SPA 菜单/iframe 复杂性 +DRAFT_PAGE_URL = ( + "http://contract-test.gzmpc.com:8080/km/agreement/km_agreement_apply/draft/" + "draft.jsp?isSimpleCategory=false" + "&mainModelName=com.landray.kmss.km.agreement.model.KmAgreementApply" + "&modelName=com.landray.kmss.km.agreement.model.KmAgreementTmpl" +) + +# 非格式-采购协议(2025集团版)模板 ID(测试环境固定值) +# 直接跳转表单页,绕过「点模板」这一依赖前端委托事件、Selenium 点击无效的步骤 +TEMPLATE_ID = "198d10b04325279b8ffeb8b4c6493b5a" +FORM_PAGE_URL = ( + "http://contract-test.gzmpc.com:8080/km/agreement/km_agreement_apply/" + "kmAgreementApply.do?method=add" + f"&fdTemplateId={TEMPLATE_ID}" + f"&.fdTemplate={TEMPLATE_ID}" + f"&i.docTemplate={TEMPLATE_ID}" + "&s_css=default" +) + +# Landray/LUI 表单字段的真实 name +F = { + "customer_code": 'input[name="extendDataFormInfo.value(fd_customeropcode)"]', + "customer_name": 'input[name="extendDataFormInfo.value(fd_customername)"]', + "contract_category": 'input[name="extendDataFormInfo.value(fd_contract_cate_type)"]', + "settlement_type": 'select#fdSettype', + "contract_background": 'textarea[name="extendDataFormInfo.value(fd_desc_info)"]', + "nature_main": 'input[name="fdType"][value="00"]', + "amount": 'input[name="fdAccount"]', + "sign_date": 'input[name="fdSigndate"]', + "begin_date": 'input[name="fdEffedate"]', + "end_date": 'input[name="fdEnddate"]', + "is_major_contract": 'input[name="extendDataFormInfo.value(fd_is_major_contract)"]', + "is_prepayment": 'input[name="extendDataFormInfo.value(fd_advance_amout)"]', + "belong_year": 'input[name="extendDataFormInfo.value(fd_contract_year_text)"]', + "payment_method": 'input[name="extendDataFormInfo.value(fd_paytype)"]', + "payment_days": 'input[name="extendDataFormInfo.value(fd_pay_time_limit)"]', + "contract_status": 'input[name="extendDataFormInfo.value(fd_suconstatus)"]', + "contains_blood": 'input[name="extendDataFormInfo.value(fd_3d1ce3953f1162)"]', + "goods_code": 'input[name="extendDataFormInfo.value(fd_detail1.0.fd_opcode)"]', + "goods_name": 'input[name="extendDataFormInfo.value(fd_detail1.0.fd_goodsname)"]', + "goods_mdm": 'input[name="extendDataFormInfo.value(fd_detail1.0.fd_mdmCode)"]', + "goods_approval": 'input[name="extendDataFormInfo.value(fd_detail1.0.fd_registdocno)"]', + "goods_spec": 'input[name="extendDataFormInfo.value(fd_detail1.0.fd_goodstype)"]', + "goods_unit": 'input[name="extendDataFormInfo.value(fd_detail1.0.fd_goodsunit)"]', + "goods_qty": 'input[name="extendDataFormInfo.value(fd_detail1.0.fd_goodsqty)"]', + "goods_price": 'input[name="extendDataFormInfo.value(fd_detail1.0.fd_unitprice)"]', +} + + def open_contract_system(browser: BrowserAdapter, ctx: ContractFlowContext) -> None: browser.open_url(ctx.contract_url) @@ -85,100 +135,84 @@ def open_contract_system(browser: BrowserAdapter, ctx: ContractFlowContext) -> N def login_contract_system(browser: BrowserAdapter, ctx: ContractFlowContext) -> None: browser.fill_first(['input[name="j_username"]', 'input[placeholder="用户名"]'], ctx.username) browser.fill_first(['input[name="j_password"]', 'input[placeholder="密码"]'], ctx.password) - browser.click_first(['input[name="btn_submit"]', 'a[href*="btn_submit"]', 'text=登录']) + browser.click_first(['a[class*="login_button"]', 'a:contains("登录")', 'input[name="btn_submit"]']) browser.wait_for_text("合同", timeout_seconds=20) def enter_contract_create(browser: BrowserAdapter) -> None: - browser.click_first(["text=合同管理", "aria=合同管理"]) - browser.click_first(["text=合同起草", "aria=合同起草"]) - browser.wait_for_text("最近使用模板", timeout_seconds=20) + # 登录后直接导航到起草表单页,跳过模板选择(前端委托事件 Selenium 点击无效) + browser.open_url(FORM_PAGE_URL) + browser.wait_for_text("客户编码", timeout_seconds=20) def select_contract_template(browser: BrowserAdapter, ctx: ContractFlowContext) -> None: - template_text = ctx.template_name or ( - "非格式--采购协议" if ctx.template_type == "NON_FORMAT" else "购销协议书(2025)" - ) - browser.click_first([f"text={template_text}", f"text={template_text}...", f"title={template_text}"]) - browser.wait_for_text("合同起草", timeout_seconds=20) + # 已通过 URL 直接指定模板,本步骤仅兜底:若尚未进入表单则再次导航 + if "客户编码" not in browser.read_body_text(): + browser.open_url(FORM_PAGE_URL) + browser.wait_for_text("客户编码", timeout_seconds=20) def fill_contract_signatory_info(browser: BrowserAdapter, ctx: ContractFlowContext) -> None: customer_name = ctx.customer_name or ctx.supplier_name if ctx.customer_code: - browser.fill_first( - ['label=客户编码', 'input[name="fdCustomerCode"]', 'input[placeholder*="客户编码"]'], - ctx.customer_code, - ) + browser.fill_first([F["customer_code"]], ctx.customer_code) if customer_name: - browser.fill_first( - ['label=客户名称', 'input[name="fdCustomerName"]', 'input[placeholder*="客户名称"]'], - customer_name, - ) + browser.fill_first([F["customer_name"]], customer_name) browser.screenshot("contract-signatory-info-filled") def fill_contract_basic_info(browser: BrowserAdapter, ctx: ContractFlowContext) -> None: - browser.select_first(['label=合同版本类型', 'select[name="fdTemplateType"]'], contract_version_label(ctx)) if ctx.contract_category: - browser.select_first(['label=合同类别', 'select[name="fdContractCategory"]'], ctx.contract_category) - browser.select_first(['label=结算类型', 'select[name="fdSettlementType"]'], ctx.settlement_type) + browser.fill_first([F["contract_category"]], ctx.contract_category) + if ctx.settlement_type: + browser.select_first([F["settlement_type"]], ctx.settlement_type) if ctx.contract_background: - browser.fill_first( - ['label=合同背景概述', 'textarea[name="fdContractBackground"]'], - ctx.contract_background, - ) - browser.click_first([f"label={ctx.contract_nature}", f"text={ctx.contract_nature}"]) + browser.fill_first([F["contract_background"]], ctx.contract_background) + # 合同性质默认主合同(页面已勾选),仅在需要从合同时切换 + if ctx.contract_nature and ctx.contract_nature != "主合同": + browser.click_first(['input[name="fdType"][value="01"]']) browser.screenshot("contract-basic-info-filled") def fill_contract_amount_and_dates(browser: BrowserAdapter, ctx: ContractFlowContext) -> None: - browser.select_first(['label=合同金额', 'select[name="fdCurrency"]'], ctx.amount_currency) if ctx.amount: - browser.fill_first(['label=金额(元)', 'input[name="fdAmount"]'], ctx.amount) - browser.fill_first( - ['label=合同签订日期', 'input[name="fdSignDate"]', 'input[placeholder*="合同签订"]'], - ctx.order_date, - ) - browser.fill_first( - ['label=合同开始日期', 'input[name="fdStartDate"]', 'input[placeholder*="合同开始"]'], - ctx.order_date, - ) + browser.fill_first([F["amount"]], ctx.amount) + browser.fill_first([F["sign_date"]], ctx.order_date) + browser.fill_first([F["begin_date"]], ctx.order_date) if ctx.contract_end_date: - browser.fill_first( - ['label=合同结束日期', 'input[name="fdEndDate"]', 'input[placeholder*="合同结束"]'], - ctx.contract_end_date, - ) + browser.fill_first([F["end_date"]], ctx.contract_end_date) browser.screenshot("contract-amount-and-dates-filled") def fill_contract_goods_lines(browser: BrowserAdapter, ctx: ContractFlowContext) -> None: for index, line in enumerate(ctx.goods_lines, start=1): if index > 1: - browser.click_first(["text=新增明细", "text=添加货品", "text=新增行"]) - prefix = f"明细{index}" - browser.fill_first([f'label={prefix}操作码', 'input[name="operationCode"]'], line.operation_code) - browser.fill_first([f'label={prefix}货品编码', 'input[name="goodsCode"]'], line.goods_code) - browser.fill_first([f'label={prefix}名称', 'input[name="goodsName"]'], line.goods_name) - browser.fill_first([f'label={prefix}国药准字', 'input[name="approvalNo"]'], line.approval_no) - browser.fill_first([f'label={prefix}规格', 'input[name="spec"]'], line.spec) - browser.fill_first([f'label={prefix}单位', 'input[name="unit"]'], line.unit) - browser.fill_first([f'label={prefix}数量', 'input[name="quantity"]'], str(line.quantity)) + browser.click_first(["text=添加行", "text=新增明细", "text=新增行"]) + browser.fill_first([F["goods_code"]], line.goods_code) + browser.fill_first([F["goods_name"]], line.goods_name) + if line.approval_no: + browser.fill_first([F["goods_approval"]], line.approval_no) + if line.spec: + browser.fill_first([F["goods_spec"]], line.spec) + if line.unit: + browser.fill_first([F["goods_unit"]], line.unit) + browser.fill_first([F["goods_qty"]], str(line.quantity)) browser.screenshot("contract-goods-lines-filled") def fill_contract_payment_info(browser: BrowserAdapter, ctx: ContractFlowContext) -> None: - browser.select_first(['label=是否重大合同', 'select[name="fdIsMajorContract"]'], ctx.is_major_contract) - browser.select_first(['label=是否预付款', 'select[name="fdIsPrepayment"]'], ctx.is_prepayment) + browser.fill_first([F["is_major_contract"]], ctx.is_major_contract) + browser.fill_first([F["is_prepayment"]], ctx.is_prepayment) if ctx.belong_year: - browser.fill_first(['label=合同归属年份', 'input[name="fdBelongYear"]'], ctx.belong_year) - browser.select_first(['label=付款方式', 'select[name="fdPaymentMethod"]', 'select[name="paymentMethod"]'], ctx.payment_method) - browser.fill_first(['label=付款期限', 'input[name="fdPaymentDays"]', 'input[name="paymentDays"]'], ctx.payment_days) - browser.select_first(['label=合同状态', 'select[name="fdContractStatus"]'], ctx.contract_status) - browser.select_first( - ['label=是否包含血液制品', 'select[name="fdContainsBloodProducts"]'], - ctx.contains_blood_products, - ) + browser.fill_first([F["belong_year"]], ctx.belong_year) + if ctx.payment_method: + browser.fill_first([F["payment_method"]], ctx.payment_method) + if ctx.payment_days: + browser.fill_first([F["payment_days"]], ctx.payment_days) + if ctx.contract_status: + browser.fill_first([F["contract_status"]], ctx.contract_status) + if ctx.contains_blood_products: + browser.fill_first([F["contains_blood"]], ctx.contains_blood_products) browser.screenshot("contract-payment-filled") diff --git a/codes/rpa_yisaiqi/diag_template_click.py b/codes/rpa_yisaiqi/diag_template_click.py new file mode 100644 index 0000000..231629e --- /dev/null +++ b/codes/rpa_yisaiqi/diag_template_click.py @@ -0,0 +1,77 @@ +# -*- coding: utf-8 -*- +"""诊断:登录 → 起草页 → 点模板,打印点击前后 URL 和页面文本。""" +import os, sys, time + +BASE = os.path.dirname(os.path.abspath(__file__)) +PARENT = os.path.dirname(BASE) +if PARENT not in sys.path: + sys.path.insert(0, PARENT) + +from rpa_yisaiqi.apa_browser_adapter import ApaBrowserAdapter + +DRAFT = ("http://contract-test.gzmpc.com:8080/km/agreement/km_agreement_apply/draft/" + "draft.jsp?isSimpleCategory=false" + "&mainModelName=com.landray.kmss.km.agreement.model.KmAgreementApply" + "&modelName=com.landray.kmss.km.agreement.model.KmAgreementTmpl") + +a = ApaBrowserAdapter("http://contract-test.gzmpc.com:8080/") +a.open_url("http://contract-test.gzmpc.com:8080/") +time.sleep(2) + +# 登录 +a.fill_first(['input[name="j_username"]'], "test002") +a.fill_first(['input[name="j_password"]'], "1") +a.click_first(['a[class*="login_button"]']) +time.sleep(2) +print("登录后 URL:", a._driver.current_url) + +# 进起草页 +a.open_url(DRAFT) +time.sleep(3) +print("起草页 URL:", a._driver.current_url) +print("起草页是否有 最近使用模板:", "最近使用模板" in a._driver.find_element("tag name", "body").text) + +# 找模板元素 +try: + from selenium.webdriver.common.by import By + el = a._driver.find_element(By.XPATH, '//p[contains(@class,"title")][contains(@title,"非格式-采购协议")]') + print("找到模板元素:", el.get_attribute("title")) + print("元素可见:", el.is_displayed(), "位置:", el.location, "大小:", el.size) + + # 尝试原生 click + print("尝试原生 click ...") + el.click() + time.sleep(3) + print("原生 click 后 URL:", a._driver.current_url) + print("body 含 客户编码:", "客户编码" in a._driver.find_element("tag name", "body").text) +except Exception as e: + print("错误:", e) + +# 尝试 JS click +print("尝试 JS click ...") +try: + a._driver.execute_script("arguments[0].click()", el) + time.sleep(3) + print("JS click 后 URL:", a._driver.current_url) + print("body 含 客户编码:", "客户编码" in a._driver.find_element("tag name", "body").text) +except Exception as e: + print("JS click 错误:", e) + +# 尝试 mousedown/mouseup/click 完整事件链 +print("尝试完整事件链 click ...") +try: + a._driver.execute_script(""" + var el = arguments[0]; + var rect = el.getBoundingClientRect(); + var cx = rect.left + rect.width/2, cy = rect.top + rect.height/2; + ['mousedown','mouseup','click'].forEach(function(type){ + el.dispatchEvent(new MouseEvent(type, {bubbles:true, cancelable:true, clientX:cx, clientY:cy})); + }); + """, el) + time.sleep(3) + print("事件链 click 后 URL:", a._driver.current_url) + print("body 含 客户编码:", "客户编码" in a._driver.find_element("tag name", "body").text) +except Exception as e: + print("事件链 click 错误:", e) + +print("DONE") diff --git a/codes/rpa_yisaiqi/screenshots/contract-amount-and-dates-filled.png b/codes/rpa_yisaiqi/screenshots/contract-amount-and-dates-filled.png new file mode 100644 index 0000000..7f5fda6 Binary files /dev/null and b/codes/rpa_yisaiqi/screenshots/contract-amount-and-dates-filled.png differ diff --git a/codes/rpa_yisaiqi/screenshots/contract-basic-info-filled.png b/codes/rpa_yisaiqi/screenshots/contract-basic-info-filled.png new file mode 100644 index 0000000..b246173 Binary files /dev/null and b/codes/rpa_yisaiqi/screenshots/contract-basic-info-filled.png differ diff --git a/codes/rpa_yisaiqi/screenshots/contract-dry-run-ready.png b/codes/rpa_yisaiqi/screenshots/contract-dry-run-ready.png new file mode 100644 index 0000000..718bb56 Binary files /dev/null and b/codes/rpa_yisaiqi/screenshots/contract-dry-run-ready.png differ diff --git a/codes/rpa_yisaiqi/screenshots/contract-goods-lines-filled.png b/codes/rpa_yisaiqi/screenshots/contract-goods-lines-filled.png new file mode 100644 index 0000000..7f5fda6 Binary files /dev/null and b/codes/rpa_yisaiqi/screenshots/contract-goods-lines-filled.png differ diff --git a/codes/rpa_yisaiqi/screenshots/contract-payment-filled.png b/codes/rpa_yisaiqi/screenshots/contract-payment-filled.png new file mode 100644 index 0000000..718bb56 Binary files /dev/null and b/codes/rpa_yisaiqi/screenshots/contract-payment-filled.png differ diff --git a/codes/rpa_yisaiqi/screenshots/contract-signatory-info-filled.png b/codes/rpa_yisaiqi/screenshots/contract-signatory-info-filled.png new file mode 100644 index 0000000..492a7f6 Binary files /dev/null and b/codes/rpa_yisaiqi/screenshots/contract-signatory-info-filled.png differ diff --git a/codes/rpa_yisaiqi/selectors/contract_create_selectors.json b/codes/rpa_yisaiqi/selectors/contract_create_selectors.json index e1c8bb6..fee70e5 100644 --- a/codes/rpa_yisaiqi/selectors/contract_create_selectors.json +++ b/codes/rpa_yisaiqi/selectors/contract_create_selectors.json @@ -1,116 +1,42 @@ { - "knownLoginSelectors": { + "login": { "username": "input[name='j_username']", "password": "input[name='j_password']", - "submit": "input[name='btn_submit']" + "submit": "input[name='btn_submit']", + "submitLink": "text=登录" }, - "candidateSelectors": { - "contractManagementMenu": [ - "text=合同管理", - "aria=合同管理" - ], - "contractDraftMenu": [ - "text=合同起草", - "aria=合同起草" - ], - "recentNonFormatPurchaseTemplate": [ - "text=非格式--采购协议", - "text=非格式--采购协议...", - "title=非格式--采购协议" - ], - "customerCode": [ - "label=客户编码", - "input[name='fdCustomerCode']", - "input[placeholder*='客户编码']" - ], - "customerName": [ - "label=客户名称", - "input[name='fdCustomerName']", - "input[placeholder*='客户名称']" - ], - "contractVersionType": [ - "label=合同版本类型", - "select[name='fdTemplateType']" - ], - "contractCategory": [ - "label=合同类别", - "select[name='fdContractCategory']" - ], - "settlementType": [ - "label=结算类型", - "select[name='fdSettlementType']" - ], - "contractBackground": [ - "label=合同背景概述", - "textarea[name='fdContractBackground']" - ], - "contractNatureMain": [ - "label=主合同", - "text=主合同" - ], - "amountCurrency": [ - "label=合同金额", - "select[name='fdCurrency']" - ], - "amount": [ - "label=金额(元)", - "input[name='fdAmount']" - ], - "signDate": [ - "label=合同签订日期", - "input[name='fdSignDate']", - "input[placeholder*='合同签订']" - ], - "beginDate": [ - "label=合同开始日期", - "input[name='fdStartDate']", - "input[placeholder*='合同开始']" - ], - "endDate": [ - "label=合同结束日期", - "input[name='fdEndDate']", - "input[placeholder*='合同结束']" - ], - "isMajorContract": [ - "label=是否重大合同", - "select[name='fdIsMajorContract']" - ], - "isPrepayment": [ - "label=是否预付款", - "select[name='fdIsPrepayment']" - ], - "belongYear": [ - "label=合同归属年份", - "input[name='fdBelongYear']" - ], - "paymentMethod": [ - "label=付款方式", - "select[name='fdPaymentMethod']", - "select[name='paymentMethod']" - ], - "paymentDays": [ - "label=付款期限", - "input[name='fdPaymentDays']", - "input[name='paymentDays']" - ], - "contractStatus": [ - "label=合同状态", - "select[name='fdContractStatus']" - ], - "containsBloodProducts": [ - "label=是否包含血液制品", - "select[name='fdContainsBloodProducts']" - ], - "submit": [ - "text=提交", - "text=提交送审", - "text=送审" - ], - "contractNumber": [ - "input[name='contractNo']", - "input[name='contractSystemNo']", - "label=合同编号", - "text=合同编号" - ] + "navigation": { + "draftPage": "http://contract-test.gzmpc.com:8080/km/agreement/km_agreement_apply/draft/draft.jsp?isSimpleCategory=false&mainModelName=com.landray.kmss.km.agreement.model.KmAgreementApply&modelName=com.landray.kmss.km.agreement.model.KmAgreementTmpl", + "templatePrefix": "非格式-采购协议" + }, + "form": { + "customerCode": "input[name='extendDataFormInfo.value(fd_customeropcode)']", + "customerName": "input[name='extendDataFormInfo.value(fd_customername)']", + "contractCategory": "input[name='extendDataFormInfo.value(fd_contract_cate_type)']", + "settlementType": "select#fdSettype", + "contractBackground": "textarea[name='extendDataFormInfo.value(fd_desc_info)']", + "contractNatureMain": "input[name='fdType'][value='00']", + "contractNatureSub": "input[name='fdType'][value='01']", + "amount": "input[name='fdAccount']", + "signDate": "input[name='fdSigndate']", + "beginDate": "input[name='fdEffedate']", + "endDate": "input[name='fdEnddate']", + "isMajorContract": "input[name='extendDataFormInfo.value(fd_is_major_contract)']", + "isPrepayment": "input[name='extendDataFormInfo.value(fd_advance_amout)']", + "belongYear": "input[name='extendDataFormInfo.value(fd_contract_year_text)']", + "paymentMethod": "input[name='extendDataFormInfo.value(fd_paytype)']", + "paymentDays": "input[name='extendDataFormInfo.value(fd_pay_time_limit)']", + "contractStatus": "input[name='extendDataFormInfo.value(fd_suconstatus)']", + "containsBloodProducts": "input[name='extendDataFormInfo.value(fd_3d1ce3953f1162)']", + "goodsOperationCode": "input[name='extendDataFormInfo.value(fd_detail1.0.fd_opcode)']", + "goodsCode": "input[name='extendDataFormInfo.value(fd_detail1.0.fd_opcode)']", + "goodsName": "input[name='extendDataFormInfo.value(fd_detail1.0.fd_goodsname)']", + "goodsMdmCode": "input[name='extendDataFormInfo.value(fd_detail1.0.fd_mdmCode)']", + "goodsApprovalNo": "input[name='extendDataFormInfo.value(fd_detail1.0.fd_registdocno)']", + "goodsSpec": "input[name='extendDataFormInfo.value(fd_detail1.0.fd_goodstype)']", + "goodsUnit": "input[name='extendDataFormInfo.value(fd_detail1.0.fd_goodsunit)']", + "goodsQuantity": "input[name='extendDataFormInfo.value(fd_detail1.0.fd_goodsqty)']", + "goodsUnitPrice": "input[name='extendDataFormInfo.value(fd_detail1.0.fd_unitprice)']", + "submit": "text=提交" } } diff --git a/codes/rpa_yisaiqi/verify_flow.py b/codes/rpa_yisaiqi/verify_flow.py deleted file mode 100644 index a26c116..0000000 --- a/codes/rpa_yisaiqi/verify_flow.py +++ /dev/null @@ -1,425 +0,0 @@ -"""Verification script for CONTRACT_CREATE flow logic. - -Runs all core code paths WITHOUT browser automation or state center HTTP calls. -Uses mock objects to verify contract_flow_steps logic is correct. -""" -from __future__ import annotations - -import json -import os -import sys -from pathlib import Path - -# Ensure the parent codes directory is on sys.path so rpa_yisaiqi imports work -_CODES = Path(__file__).resolve().parent.parent -if str(_CODES) not in sys.path: - sys.path.insert(0, str(_CODES)) - -# --------------------------------------------------------------------------- -# Phase 1: Verify all imports resolve correctly -# --------------------------------------------------------------------------- -print("=" * 60) -print("Phase 1: 验证模块导入") -print("=" * 60) - -try: - from rpa_yisaiqi.callback_client import StateCenterClient, StateCenterError - print(" [OK] callback_client -> StateCenterClient") -except Exception as e: - print(f" [FAIL] callback_client: {e}") - sys.exit(1) - -try: - from rpa_yisaiqi.contract_flow_steps import ( - BrowserAdapter, ContractFlowContext, ContractGoodsLine, - contract_version_label, run_contract_create_flow, - open_contract_system, login_contract_system, enter_contract_create, - select_contract_template, fill_contract_signatory_info, - fill_contract_basic_info, fill_contract_amount_and_dates, - fill_contract_goods_lines, fill_contract_payment_info, - upload_contract_document_if_needed, submit_contract_for_approval, - read_contract_number, - ) - print(" [OK] contract_flow_steps -> all functions") -except Exception as e: - print(f" [FAIL] contract_flow_steps: {e}") - sys.exit(1) - -try: - from rpa_yisaiqi.contract_draft import ContractDraft, load_contract_draft, contract_draft_from_dict - print(" [OK] contract_draft -> ContractDraft, load_contract_draft") -except Exception as e: - print(f" [FAIL] contract_draft: {e}") - sys.exit(1) - -try: - from rpa_yisaiqi.pdf_utils import ensure_merged_pdf - print(" [OK] pdf_utils -> ensure_merged_pdf") -except Exception as e: - print(f" [FAIL] pdf_utils: {e}") - sys.exit(1) - -try: - from rpa_yisaiqi.contract_create_node import ( - ContractCreateInput, ContractCreateError, - LoginFailed, SupplierContractDisabled, PdfMergeUnsupported, - execute_contract_create, create_contract_in_contract_system, - build_complete_payload, build_fail_payload, build_flow_context, - NODE_CODE, CONTRACT_URL, parse_args, - ) - print(" [OK] contract_create_node -> all functions + error classes") -except Exception as e: - print(f" [FAIL] contract_create_node: {e}") - sys.exit(1) - -print(" -> 6/6 模块导入成功\n") - -# --------------------------------------------------------------------------- -# Phase 2: Verify contract draft loading from JSON examples -# --------------------------------------------------------------------------- -print("=" * 60) -print("Phase 2: 验证合同起草 JSON 数据加载") -print("=" * 60) - -examples_dir = Path(__file__).resolve().parent / "examples" - -# Test YYC draft -yyc_path = examples_dir / "contract_draft_yyc.json" -if yyc_path.exists(): - try: - draft = load_contract_draft(str(yyc_path)) - assert draft.supplier_name == "重庆亿昊药业", f"supplier_name mismatch: {draft.supplier_name}" - assert draft.source_type == "YYC", f"source_type mismatch: {draft.source_type}" - assert draft.template_type == "NON_FORMAT", f"template_type mismatch: {draft.template_type}" - assert draft.settlement_type == "付款" - assert draft.contract_nature == "主合同" - assert len(draft.goods_lines) == 1, f"goods_lines count mismatch: {len(draft.goods_lines)}" - assert draft.goods_lines[0].goods_name == "阿卡波糖片" - assert len(draft.platform_pdf_paths) == 2 - print(f" [OK] contract_draft_yyc.json -> supplier={draft.supplier_name}, " - f"goods={len(draft.goods_lines)}, pdfs={len(draft.platform_pdf_paths)}") - except Exception as e: - print(f" [FAIL] contract_draft_yyc.json: {e}") - import traceback; traceback.print_exc() -else: - print(f" [SKIP] contract_draft_yyc.json not found") - -# Test XYY draft -xyy_path = examples_dir / "contract_draft_xyy.json" -if xyy_path.exists(): - try: - draft = load_contract_draft(str(xyy_path)) - print(f" [OK] contract_draft_xyy.json -> supplier={draft.supplier_name}, " - f"source_type={draft.source_type}, goods={len(draft.goods_lines)}") - except Exception as e: - print(f" [FAIL] contract_draft_xyy.json: {e}") -else: - print(f" [SKIP] contract_draft_xyy.json not found (may be fine)") - -print() - -# --------------------------------------------------------------------------- -# Phase 3: Mock Browser and verify flow steps -# --------------------------------------------------------------------------- -print("=" * 60) -print("Phase 3: 用 Mock Browser 验证流程步骤逻辑") -print("=" * 60) - - -class MockBrowser: - """Records all interactions for verification.""" - def __init__(self): - self.log: list[str] = [] - self.values: dict[str, str] = {} - - def open_url(self, url: str) -> None: - self.log.append(f"open_url({url})") - - def fill_first(self, selectors: list[str], value: str) -> None: - self.log.append(f"fill_first({selectors[0] if selectors else '?'}..., {value})") - - def click_first(self, selectors: list[str]) -> None: - self.log.append(f"click_first({selectors[0] if selectors else '?'}...)") - - def wait_for_text(self, text: str, timeout_seconds: int = 15) -> None: - self.log.append(f"wait_for_text({text}, timeout={timeout_seconds})") - - def select_first(self, selectors: list[str], value: str) -> None: - self.log.append(f"select_first({selectors[0] if selectors else '?'}..., {value})") - - def upload_file_first(self, selectors: list[str], file_path: str) -> None: - self.log.append(f"upload_file_first({selectors[0] if selectors else '?'}..., {file_path})") - - def read_first(self, selectors: list[str], value_name: str) -> str: - val = self.values.get(value_name, f"HT-20260812-{len(self.log):03d}") - self.log.append(f"read_first({selectors[0] if selectors else '?'}..., {value_name}) -> {val}") - return val - - def screenshot(self, name: str) -> str: - path = f"screenshots/{name}.png" - self.log.append(f"screenshot({name}) -> {path}") - return path - - -# Build flow context for YYC -ctx = ContractFlowContext( - contract_url="http://contract-test.gzmpc.com:8080/", - username="test002", - password="1", - supplier_name="测试供应商", - order_date="2026-08-12", - source_type="YYC", - template_type="NON_FORMAT", - template_name="非格式--采购协议", - customer_code="CUST-001", - customer_name="测试供应商", - contract_category="采购协议", - settlement_type="付款", - contract_background="自动化测试合同起草", - contract_nature="主合同", - amount_currency="人民币", - amount="50000.00", - contract_end_date="2026-12-31", - is_major_contract="否", - is_prepayment="否", - belong_year="2026", - payment_method="账期支付", - payment_days="30", - contract_status="正式合同", - contains_blood_products="否", - submit_mode="dry_run", # dry-run mode - goods_lines=[ - ContractGoodsLine( - operation_code="ADD", - goods_code="YP-001", - goods_name="测试药品A", - approval_no="国药准字H00000001", - spec="50mg*30片", - unit="盒", - quantity=10.0, - ), - ContractGoodsLine( - operation_code="ADD", - goods_code="YP-002", - goods_name="测试药品B", - approval_no="国药准字H00000002", - spec="100mg*20片", - unit="瓶", - quantity=5.0, - ), - ], - merged_pdf_path="C:/rpa/jobs/test/merged.pdf", -) - -# Run the flow -browser = MockBrowser() -contract_no = run_contract_create_flow(browser, ctx) - -# Verify step sequence -expected_steps = [ - "open_url", - "fill_first", # username - "fill_first", # password - "click_first", # login - "wait_for_text", # 合同 - "click_first", # 合同管理 - "click_first", # 合同起草 - "wait_for_text", # 最近使用模板 - "click_first", # 模板 - "wait_for_text", # 合同起草 -] - -step_names = [s.split("(")[0] for s in browser.log] - -assert contract_no == "", f"dry_run should return empty string, got: {contract_no}" -print(f" [OK] dry-run mode: contract_no is empty string (not submitted)") - -# Verify key steps were executed -assert "open_url" in step_names, "open_url not called" -print(f" [OK] open_url called with: {browser.log[0]}") - -assert len([s for s in step_names if s == "fill_first"]) >= 2, "fill_first not called enough times" -print(f" [OK] fill_first called for username/password and form fields") - -assert "click_first" in step_names, "click_first not called" -print(f" [OK] click_first operations executed") - -assert "screenshot" in step_names, "screenshot not called" -print(f" [OK] screenshots captured at each stage") - -# Verify dry-run submitted correctly (returns False) -assert len([s for s in browser.log if "dry-run" in s]) == 1 -print(f" [OK] dry-run screenshot taken, submit skipped") - -print(f"\n Total browser actions: {len(browser.log)}") -for i, action in enumerate(browser.log): - print(f" [{i+1}] {action}") - -print() - -# --------------------------------------------------------------------------- -# Phase 4: Verify XYY flow (skip PDF upload) -# --------------------------------------------------------------------------- -print("=" * 60) -print("Phase 4: 验证 XYY 流程 (跳过 PDF 上传)") -print("=" * 60) - -ctx_xyy = ContractFlowContext( - contract_url="http://contract-test.gzmpc.com:8080/", - username="test002", - password="1", - supplier_name="小药药供应商", - order_date="2026-08-12", - source_type="XYY", - template_type="FORMAT", - template_name="购销协议书(2025)", - customer_code="CUST-XYY-001", - customer_name="小药药供应商", - contract_category="购销协议", - settlement_type="付款", - contract_background="XYY格式合同测试", - contract_nature="主合同", - amount_currency="人民币", - amount="30000.00", - contract_end_date="2026-12-31", - is_major_contract="否", - is_prepayment="否", - belong_year="2026", - payment_method="账期支付", - payment_days="30", - contract_status="正式合同", - contains_blood_products="否", - submit_mode="submit", - goods_lines=[], - merged_pdf_path=None, -) - -browser_xyy = MockBrowser() -browser_xyy.values["contract_number"] = "HT-20260812-XYY-001" - -contract_no_xyy = run_contract_create_flow(browser_xyy, ctx_xyy) - -# Verify XYY specific behavior -upload_calls = [s for s in browser_xyy.log if "upload" in s.lower()] -assert len(upload_calls) == 0, f"XYY should NOT upload PDF, but got: {upload_calls}" -print(f" [OK] XYY correctly skips PDF upload (0 upload calls)") - -assert contract_no_xyy == "HT-20260812-XYY-001" -print(f" [OK] XYY contract number read: {contract_no_xyy}") - -print() - -# --------------------------------------------------------------------------- -# Phase 5: Verify error classes and payload builders -# --------------------------------------------------------------------------- -print("=" * 60) -print("Phase 5: 验证异常类和回调 Payload 构建") -print("=" * 60) - -spec = ContractCreateInput( - job_id="TEST-JOB-001", - supplier_name="测试供应商", - order_date="2026-08-12", - source_type="YYC", - contract_system_no=None, - attempt_no=1, -) - -# Test build_complete_payload -complete_payload = build_complete_payload(spec, "HT-20260812-001") -assert complete_payload["businessRefs"]["contractSystemNo"] == "HT-20260812-001" -assert "attemptNo" in complete_payload -assert "idempotencyKey" in complete_payload -print(f" [OK] build_complete_payload -> {complete_payload['businessRefs']}") - -# Test error classes -for exc_class, code, retryable, role in [ - (LoginFailed, "SYS_LOGIN_FAIL", True, "OPS"), - (SupplierContractDisabled, "BIZ_CONTRACT_DISABLED", False, "PURCHASER"), - (PdfMergeUnsupported, "CAP_PDF_MERGE_UNSUPPORTED", False, "OPS"), -]: - exc = exc_class("测试异常") - assert exc.error_code == code, f"{exc_class.__name__}: expected {code}, got {exc.error_code}" - assert exc.retryable == retryable - assert exc.route_role == role - print(f" [OK] {exc_class.__name__}: code={exc.error_code}, retryable={exc.retryable}, role={exc.route_role}") - -# Test build_fail_payload -fail_payload = build_fail_payload(spec, LoginFailed("登录凭证无效")) -assert fail_payload["errorCode"] == "SYS_LOGIN_FAIL" -assert fail_payload["retryable"] is True -assert fail_payload["routeRole"] == "OPS" -print(f" [OK] build_fail_payload -> errorCode={fail_payload['errorCode']}") - -print() - -# --------------------------------------------------------------------------- -# Phase 6: Verify contract_version_label -# --------------------------------------------------------------------------- -print("=" * 60) -print("Phase 6: 验证工具函数") -print("=" * 60) - -from rpa_yisaiqi.contract_flow_steps import contract_version_label - -ctx_format = ContractFlowContext( - contract_url="", username="", password="", supplier_name="", order_date="", - source_type="", template_type="FORMAT", -) -assert contract_version_label(ctx_format) == "格式合同" -print(f" [OK] contract_version_label(FORMAT) -> '格式合同'") - -ctx_nonformat = ContractFlowContext( - contract_url="", username="", password="", supplier_name="", order_date="", - source_type="", template_type="NON_FORMAT", -) -assert contract_version_label(ctx_nonformat) == "非格式合同" -print(f" [OK] contract_version_label(NON_FORMAT) -> '非格式合同'") - -# Verify template default name logic -# (tested inside select_contract_template via the mock - NON_FORMAT without name uses '非格式--采购协议') -ctx_no_name = ContractFlowContext( - contract_url="", username="", password="", supplier_name="", order_date="", - source_type="", template_type="NON_FORMAT", template_name="", -) -browser_tpl = MockBrowser() -select_contract_template(browser_tpl, ctx_no_name) -assert "非格式--采购协议" in browser_tpl.log[0] -print(f" [OK] select_contract_template without name defaults to '非格式--采购协议'") - -ctx_formatted = ContractFlowContext( - contract_url="", username="", password="", supplier_name="", order_date="", - source_type="", template_type="FORMAT", template_name="", -) -browser_tpl2 = MockBrowser() -select_contract_template(browser_tpl2, ctx_formatted) -assert "购销协议书(2025)" in browser_tpl2.log[0] -print(f" [OK] select_contract_template FORMAT defaults to '购销协议书(2025)'") - -print() - -# --------------------------------------------------------------------------- -# Final Summary -# --------------------------------------------------------------------------- -print("=" * 60) -print("验证完成!") -print("=" * 60) -print(f""" -核心模块状态: - - callback_client.py : 导入正常 (StateCenterClient) - - contract_flow_steps.py : 所有15个步骤函数可调用 - - contract_draft.py : JSON数据加载正常 - - pdf_utils.py : 导入正常 - - contract_create_node.py: 异常类定义正常, payload构建正确 - -流程逻辑验证: - - YYC dry-run 流程 : 所有步骤按序执行 (不含提交) - - XYY 流程 : PDF上传步骤被正确跳过 - - 异常映射 : 4种异常类 = 4种错误码 - - 版本类型 : 格式/非格式合同标签正确 - -下一步: 在 APA Studio 中: - 1. 打开项目 D:/RPA/AutoRpa - 2. 在画布上拖拽 'Python脚本块' 节点 - 3. 粘贴 GlobalFun.py 中的 run_contract_create_flow 调用 -""") diff --git a/codes/rpa_yisaiqi/verify_selenium_fill.py b/codes/rpa_yisaiqi/verify_selenium_fill.py new file mode 100644 index 0000000..510b68e --- /dev/null +++ b/codes/rpa_yisaiqi/verify_selenium_fill.py @@ -0,0 +1,179 @@ +# -*- coding: utf-8 -*- +"""用真实 Selenium Chrome 验证合同建单填表流程(dry_run,不提交)。""" +import os +import sys +import time + +# 确保能 import rpa_yisaiqi 包 +BASE = os.path.dirname(os.path.abspath(__file__)) +PARENT = os.path.dirname(BASE) +if PARENT not in sys.path: + sys.path.insert(0, PARENT) + +from rpa_yisaiqi.apa_browser_adapter import ApaBrowserAdapter +from rpa_yisaiqi.contract_flow_steps import ( + ContractFlowContext, + ContractGoodsLine, + login_contract_system, + enter_contract_create, + select_contract_template, + fill_contract_signatory_info, + fill_contract_basic_info, + fill_contract_amount_and_dates, + fill_contract_goods_lines, + fill_contract_payment_info, +) + +CONTRACT_URL = "http://contract-test.gzmpc.com:8080/" + + +def build_ctx() -> ContractFlowContext: + return ContractFlowContext( + contract_url=CONTRACT_URL, + username="test002", + password="1", + supplier_name="测试供应商", + order_date="2026-08-12", + source_type="YYC", + template_type="NON_FORMAT", + template_name="非格式-采购协议", + customer_code="CUST-001", + customer_name="测试供应商", + contract_category="采购协议", + settlement_type="付款", + contract_background="自动化测试合同起草", + contract_nature="主合同", + amount="50000", + contract_end_date="2026-12-31", + is_major_contract="否", + is_prepayment="否", + belong_year="2026", + payment_method="账期支付", + payment_days="30", + contract_status="正式合同", + contains_blood_products="否", + submit_mode="dry_run", + goods_lines=[ + ContractGoodsLine( + operation_code="ADD", + goods_code="YP-001", + goods_name="测试药品A", + approval_no="国药准字H00000001", + spec="50mg*30片", + unit="盒", + quantity=10, + ) + ], + ) + + +def read_values(driver): + """回读表单中已填入的值,用于校验。""" + script = """ + function getVal(name) { + var el = document.querySelector('input[name="' + name + '"],textarea[name="' + name + '"],select[name="' + name + '"]'); + return el ? (el.value || '') : '(missing)'; + } + var keys = [ + 'extendDataFormInfo.value(fd_customeropcode)', + 'extendDataFormInfo.value(fd_customername)', + 'extendDataFormInfo.value(fd_contract_cate_type)', + 'fdAccount', + 'fdSigndate', + 'fdEffedate', + 'fdEnddate', + 'extendDataFormInfo.value(fd_is_major_contract)', + 'extendDataFormInfo.value(fd_advance_amout)', + 'extendDataFormInfo.value(fd_contract_year_text)', + 'extendDataFormInfo.value(fd_paytype)', + 'extendDataFormInfo.value(fd_pay_time_limit)', + 'extendDataFormInfo.value(fd_suconstatus)', + 'extendDataFormInfo.value(fd_3d1ce3953f1162)', + 'extendDataFormInfo.value(fd_detail1.0.fd_opcode)', + 'extendDataFormInfo.value(fd_detail1.0.fd_goodsname)', + 'extendDataFormInfo.value(fd_detail1.0.fd_registdocno)', + 'extendDataFormInfo.value(fd_detail1.0.fd_goodstype)', + 'extendDataFormInfo.value(fd_detail1.0.fd_goodsunit)', + 'extendDataFormInfo.value(fd_detail1.0.fd_goodsqty)' + ]; + var out = {}; + for (var i = 0; i < keys.length; i++) { out[keys[i]] = getVal(keys[i]); } + var bg = document.querySelector('textarea[name="extendDataFormInfo.value(fd_desc_info)"]'); + out['background'] = bg ? bg.value : '(missing)'; + return JSON.stringify(out); + """ + return driver.execute_script(script) + + +def main(): + print("=" * 60) + print("Selenium 合同建单填表验证 (dry_run, 不提交)") + print("=" * 60) + + adapter = ApaBrowserAdapter(CONTRACT_URL) + ctx = build_ctx() + + print("[1/6] 打开合同系统并登录 ...") + adapter.open_url(CONTRACT_URL) + time.sleep(2) + login_contract_system(adapter, ctx) + print(" [OK] 登录成功") + + print("[2/6] 进入合同起草页 ...") + enter_contract_create(adapter) + print(" [OK] 已进入模板选择页") + + print("[3/6] 选择模板 ...") + select_contract_template(adapter, ctx) + time.sleep(2) + print(" [OK] 模板已选择") + + print("[4/6] 填写签约方 + 基础信息 ...") + fill_contract_signatory_info(adapter, ctx) + fill_contract_basic_info(adapter, ctx) + print(" [OK] 已填写") + + print("[5/6] 填写金额日期 + 货品 + 付款 ...") + fill_contract_amount_and_dates(adapter, ctx) + fill_contract_goods_lines(adapter, ctx) + fill_contract_payment_info(adapter, ctx) + print(" [OK] 已填写") + + print("[6/6] 回读校验 ...") + time.sleep(1) + values = read_values(adapter._driver) + print(" 回读值:") + import json + for k, v in json.loads(values).items(): + print(f" {k} = {v}") + + # 校验关键字段 + expected = { + "extendDataFormInfo.value(fd_customeropcode)": "CUST-001", + "extendDataFormInfo.value(fd_customername)": "测试供应商", + "extendDataFormInfo.value(fd_contract_cate_type)": "采购协议", + "fdAccount": "50000", + "fdSigndate": "2026-08-12", + "fdEnddate": "2026-12-31", + "extendDataFormInfo.value(fd_paytype)": "账期支付", + "extendDataFormInfo.value(fd_pay_time_limit)": "30", + "extendDataFormInfo.value(fd_detail1.0.fd_goodsqty)": "10", + } + actual = json.loads(values) + all_ok = True + for k, v in expected.items(): + got = actual.get(k, "") + status = "OK" if str(got) == str(v) else "MISMATCH" + if status != "OK": + all_ok = False + print(f" [{status}] {k}: 期望={v} 实际={got}") + + print("=" * 60) + print("验证结果:", "全部通过" if all_ok else "存在不匹配,请检查") + print("=" * 60) + print("浏览器保持打开(detach),可人工核对。") + sys.exit(0 if all_ok else 1) + + +if __name__ == "__main__": + main() diff --git a/show_flow.py b/show_flow.py deleted file mode 100644 index 8d75074..0000000 --- a/show_flow.py +++ /dev/null @@ -1,43 +0,0 @@ -# coding=utf-8 -"""验证 Main.seq 并展示流程""" -import json - -data = json.load(open(r'd:\RPA\AutoRpa\Main.seq', 'r', encoding='utf-8')) -nodes = data['NodeList'] -lines = data['LineList'] - -print(f"节点总数: {len(nodes)}") -print(f"连线总数: {len(lines)}") -print() -print("=" * 70) -print(" 合同建单 (CONTRACT_CREATE) 流程图") -print("=" * 70) -print() - -for i, n in enumerate(nodes): - ui = n['NodeUI'] - subtype = ui['NodeSubType'].replace('EnumWidget_Begin', 'START').replace('EnumWidget_Code_Custom', 'CODE') - title = ui['NodeTitle'] - print(f" [{subtype:6s}] {title}") - -print() -print("=" * 70) -print(" 连线关系 (按序执行)") -print("=" * 70) -for i, l in enumerate(lines): - fr = l['LineStart']['Pidx'].split('/')[0][-6:] - to = l['LineEnd']['Pidx'].split('/')[0][-6:] - fr_node = next((n for n in nodes if n['NodeUI']['NodeTag'].endswith(fr)), None) - to_node = next((n for n in nodes if n['NodeUI']['NodeTag'].endswith(to)), None) - fr_title = fr_node['NodeUI']['NodeTitle'] if fr_node else fr - to_title = to_node['NodeUI']['NodeTitle'] if to_node else to - print(f" [{fr_title}] --> [{to_title}]") - -print() -print("=" * 70) -print(" 运行说明") -print("=" * 70) -print(" 1. 在 APA Studio 中按 Ctrl+R 运行流程") -print(" 2. S01 节点会初始化所有模块和上下文") -print(" 3. S02-S15 节点依次执行合同建单各步骤") -print(" 4. 输出面板显示每步执行状态")