Compare commits
782 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e0e8fef265 | |||
| deb0f8344b | |||
| ed135950eb | |||
| ed7c547a06 | |||
| 1a9f927c4c | |||
| 350af86b65 | |||
| e136b73192 | |||
| b6bf2fec21 | |||
| 56daadbbca | |||
| 7de26ec9a7 | |||
| 7dbd3036df | |||
| 687085f9c7 | |||
| 96cfc7e184 | |||
| 0b26433b1a | |||
| 4f06bb40f8 | |||
| 46d571b65a | |||
| 7da1bfd315 | |||
| b30f47b5aa | |||
| ccc6c7206c | |||
| a549f4cae0 | |||
| 07c451ce22 | |||
| 62da624369 | |||
| a1a9b03180 | |||
| e4c1d37c35 | |||
| 42cacac256 | |||
| 2816970327 | |||
| 5744fcdcb2 | |||
| 3c412b394d | |||
| 4e64cea373 | |||
| db579db87f | |||
| f612a918d9 | |||
| 1a78e06a7a | |||
| 374a98ffc3 | |||
| d723e2e273 | |||
| 41030171b5 | |||
| aa31706bcc | |||
| 4e3810493d | |||
| eeb2db92ed | |||
| 5a2930029b | |||
| 2e9a73923b | |||
| 97e67f8a88 | |||
| 074849af74 | |||
| 6939783518 | |||
| 260e48fa48 | |||
| 52c4ef5df5 | |||
| 46b634e0fa | |||
| a556c29a17 | |||
| bbc4f508b5 | |||
| fe0243549a | |||
| d9196ba650 | |||
| a0ea1fa392 | |||
| 195bcb4495 | |||
| 929627a191 | |||
| 83a313b4b7 | |||
| f734bfb50f | |||
| 9efc22d195 | |||
| bd3bef5fb0 | |||
| ddbe49e847 | |||
| ed9bddec0b | |||
| 73a07eeb54 | |||
| 6e27aed804 | |||
| 5f955781a6 | |||
| c124f7310e | |||
| 73ffcae549 | |||
| 80e55ba0be | |||
| eecf955096 | |||
| 6426db9a5d | |||
| bf4753cd09 | |||
| a592eaf383 | |||
| 75d654cec3 | |||
| a18ed37600 | |||
| 6747a8dfbc | |||
| 583dfb8126 | |||
| bda31dd086 | |||
| 283af35f3b | |||
| 82564e2580 | |||
| fc6a9026e6 | |||
| e1538bb5ab | |||
| d47db969ea | |||
| f01de37a59 | |||
| cee429a0a0 | |||
| 321ee89676 | |||
| aa60484315 | |||
| b807df3ee8 | |||
| c11e922573 | |||
| 7135a3bbeb | |||
| 142b87dd67 | |||
| dff4579f28 | |||
| 2b3b662a9a | |||
| 98bc5195b1 | |||
| a02912ccbb | |||
| 2f1d29dfd2 | |||
| 11efacc4cb | |||
| 8133d0da3d | |||
| 093d63faae | |||
| 07f5e04488 | |||
| acfc97fc7c | |||
| 6e30554143 | |||
| fa1c788c41 | |||
| 3a688942c3 | |||
| 6797522618 | |||
| 4730a53313 | |||
| 5a538792f0 | |||
| 5f20289767 | |||
| 4c7ed97a76 | |||
| a9c2228e3c | |||
| ec44fcb7cb | |||
| 1f9c8d261b | |||
| 23ab3bd416 | |||
| 79482775d2 | |||
| 2dcb3678b0 | |||
| 8d7c206012 | |||
| c21c264426 | |||
| 77a39c18f1 | |||
| 65a00b0a8e | |||
| 0c2d5679f1 | |||
| 75d0e0de43 | |||
| 19ff864eaa | |||
| 6bbee7cb10 | |||
| e268923cf6 | |||
| 4fb3d7182c | |||
| 6e7a6ec032 | |||
| 0e92c92f87 | |||
| a6f1eb39ec | |||
| 21364f6239 | |||
| a0527094a9 | |||
| eda890c294 | |||
| 1ed8db87cc | |||
| 136892478f | |||
| aee90d31a0 | |||
| 6fe632fda3 | |||
| fa975a2f5b | |||
| bd5ab14d2f | |||
| 32890bfda9 | |||
| a3d4113b97 | |||
| 8acfa722f8 | |||
| 234beba06a | |||
| 90c27a0f73 | |||
| 2577a5d2c2 | |||
| 84bf10b65b | |||
| 5d70f5fd97 | |||
| f42cc59d53 | |||
| d12b222d52 | |||
| b663c7f9d4 | |||
| 30abfa7e23 | |||
| 0be32c34c5 | |||
| 8d4863b894 | |||
| ad1ae8fb54 | |||
| b31e41bfd9 | |||
| 999d5a937d | |||
| 45ea543a4b | |||
| 9d72a5e02d | |||
| 8973c45b4e | |||
| b3e5a5dc62 | |||
| d8bddadd42 | |||
| aa552d1c80 | |||
| 6daac5f442 | |||
| a8c453fbda | |||
| 3e15da5ad7 | |||
| 8e776e7d42 | |||
| de3d5654b4 | |||
| b0e19ffa30 | |||
| bacf451cf5 | |||
| 2a5600b6af | |||
| bfbc78d9b7 | |||
| d509281bdc | |||
| 866fdfb4f6 | |||
| 6c14d0f4c5 | |||
| b31a8e3bf8 | |||
| 7f3261e453 | |||
| 861c3617dd | |||
| 1f4b1f5421 | |||
| f851460a32 | |||
| 992ac6d9eb | |||
| 2e9ac57930 | |||
| ce8d090f27 | |||
| 4ec8b59fd6 | |||
| 6b086af8b4 | |||
| 52f95665ed | |||
| d1ce5ab105 | |||
| 0fd10649f4 | |||
| 3c65900768 | |||
| 0b962b4fee | |||
| a58633fc5f | |||
| 24c2ed3ae3 | |||
| 84a77d98cf | |||
| 20e50e02e9 | |||
| c8bb354972 | |||
| 3f9b73d211 | |||
| 40bbffed17 | |||
| b16ec4e809 | |||
| 9629c895d5 | |||
| f4420fcc03 | |||
| 17f8c3e134 | |||
| 02512a522c | |||
| 4974f99842 | |||
| a3d0c32be3 | |||
| 31523160fc | |||
| 183d5cdeec | |||
| 52fed810f2 | |||
| f58d63da8d | |||
| 6793e6fbdc | |||
| 4f88e9b97d | |||
| 5004cec9b3 | |||
| 76c81e4209 | |||
| 323043dc01 | |||
| 37902ce70a | |||
| c69e795d49 | |||
| c3b82af95a | |||
| 24510d4828 | |||
| 11a04a99f7 | |||
| 6e96eeaa05 | |||
| 2c16d4af5a | |||
| d1f9cf43de | |||
| 26fb11cb30 | |||
| a7a3eb8658 | |||
| fbedcd1f5f | |||
| 6033af0f23 | |||
| 4809786b16 | |||
| a37f40ff6e | |||
| 44f5cc9de0 | |||
| e059d0cbd9 | |||
| c51040b0de | |||
| eba2abc108 | |||
| ed7626009a | |||
| 442413ec6b | |||
| 90986df641 | |||
| 8b505dbce3 | |||
| 2b26e9643b | |||
| eb16f01839 | |||
| 872cf33d1c | |||
| afbce19028 | |||
| e27228e735 | |||
| 3125232114 | |||
| e552840cc4 | |||
| 569142845a | |||
| e62d837858 | |||
| 27badfb88a | |||
| e4d0781eb8 | |||
| 93c8373335 | |||
| 42bad6b4ea | |||
| 83d4bd88cf | |||
| 4141267104 | |||
| d5778f165b | |||
| 7128e26a27 | |||
| 78247e9969 | |||
| 66d36e034e | |||
| 4100ee842c | |||
| d4857c8e6e | |||
| 0f52781489 | |||
| 332f19cd22 | |||
| 53a1c103d6 | |||
| addf7a3e6b | |||
| c76078ea06 | |||
| 9299288e8a | |||
| dc34f24ee2 | |||
| fe83bf1483 | |||
| c184f9281d | |||
| 3127a69b67 | |||
| d7326c4f82 | |||
| f8478284b6 | |||
| 1c910de4b1 | |||
| 2c3cb955ef | |||
| 1d7746d839 | |||
| e16d2ba238 | |||
| 60f8b448ec | |||
| c0d61f29a8 | |||
| c47b99f9ee | |||
| 653429a6d6 | |||
| c2250e1817 | |||
| 92ca48d65f | |||
| 2f53d9a79d | |||
| d025bfe2da | |||
| fbc2460ac4 | |||
| 76c2800275 | |||
| 536bf984ee | |||
| 23f8af43ee | |||
| eba54bf2e7 | |||
| 50270e6541 | |||
| 5f7d9e80ee | |||
| a71ef35d5c | |||
| 2cf5291831 | |||
| 75e3e9dd7b | |||
| 7a75365d2f | |||
| 7a23d59aa1 | |||
| 2583423600 | |||
| 1d748a0424 | |||
| 86172790d1 | |||
| 92c9103059 | |||
| 16393dbc9d | |||
| e841b0ef03 | |||
| 27c7ae9d76 | |||
| dc3a40675d | |||
| 31e65ac2e3 | |||
| c2444d1b09 | |||
| 2faadae4cf | |||
| d24e51c355 | |||
| 8ed8af1b9c | |||
| 5f6b416f26 | |||
| 3671c05573 | |||
| db6eed9292 | |||
| 4c903ba0bb | |||
| 9eee216065 | |||
| 9e1dd02d70 | |||
| b986bf9bcc | |||
| a3e0e62efc | |||
| 8646c5acf6 | |||
| dab9465755 | |||
| 3a076cc4ea | |||
| 1b6d1ea0ef | |||
| c490284c65 | |||
| c3ca329138 | |||
| d0f73c32bb | |||
| 79e0da7cf2 | |||
| b261f505aa | |||
| 65c4c95cbe | |||
| 5b3c9c9055 | |||
| d32d4f845c | |||
| 637950d37f | |||
| 0e1ede58e5 | |||
| e914a11768 | |||
| 73d66dab35 | |||
| 634766a08e | |||
| 5fbc450e96 | |||
| 5acd7ae2c4 | |||
| 09578ddc5d | |||
| 8270801711 | |||
| de91c3cfc8 | |||
| 06888a2061 | |||
| cde2d33d03 | |||
| 9cf89f95d1 | |||
| 20486e15ea | |||
| 420c403da1 | |||
| cb8fdf3461 | |||
| 0937d08770 | |||
| 9b1fd8e1aa | |||
| d87861142c | |||
| ff5fc1d0f3 | |||
| d5a671cbb8 | |||
| d0508cbd96 | |||
| 12098cf8ac | |||
| 2557c35bea | |||
| 11f23bbd77 | |||
| 2b2ec729ac | |||
| 9292acec8c | |||
| 3095ed5375 | |||
| a333e303c1 | |||
| bd8512feab | |||
| c235fc4a7a | |||
| 9c283b9aee | |||
| 4e824d739b | |||
| fcbbc792b0 | |||
| ba7cf8e79e | |||
| 4ab49d8e43 | |||
| 7f864db80d | |||
| 772a163693 | |||
| c8e106f272 | |||
| ade4f0a1d4 | |||
| 4fcf933dd1 | |||
| 1fb60771ca | |||
| 2cd391e106 | |||
| 196f3bcba1 | |||
| bc73c5ca86 | |||
| f4badc15d1 | |||
| 76f731d0cd | |||
| 5e22858ab4 | |||
| bc1c54c43e | |||
| 95ac0d36f8 | |||
| 2bee3eb179 | |||
| 52165f2a0b | |||
| 9f2da226f4 | |||
| 46b999317d | |||
| 80bce3c9c2 | |||
| 90b25b4bd8 | |||
| f562f1eafd | |||
| ff520622ff | |||
| 1a6c6a4f31 | |||
| 4a9e0388f4 | |||
| e47b2a971e | |||
| 96777ec095 | |||
| 52019deef5 | |||
| c26a534ff6 | |||
| e5be5c0b05 | |||
| 5b07f52641 | |||
| e0109cb530 | |||
| 1496dd1598 | |||
| efcaeb2f00 | |||
| 1252946fe5 | |||
| 2913d5f636 | |||
| c50beb3d43 | |||
| 46b559001a | |||
| 55a2e2e060 | |||
| 12364a052f | |||
| 45ac971035 | |||
| 26b818c9a6 | |||
| 007836f52e | |||
| ed0982770b | |||
| dcc72e90b7 | |||
| b428660944 | |||
| 0008dcc0ee | |||
| e3fbf3981d | |||
| c5e9244a08 | |||
| e1fd823c13 | |||
| 346f0d636c | |||
| 315a259e5b | |||
| 3157a03c74 | |||
| 045807a748 | |||
| ea0e12ac80 | |||
| 692060af6c | |||
| d8b23a4b5c | |||
| 074bf55d6c | |||
| 84e505b817 | |||
| a12e6ff603 | |||
| b01d662a1c | |||
| e631dad499 | |||
| 7ab0bfdd6b | |||
| 35a4db43fb | |||
| 094028006b | |||
| c12f42fcac | |||
| 3e498b098b | |||
| bbf1a1a544 | |||
| 42d57f30bf | |||
| 19967a9ad0 | |||
| 9d1a8f12d8 | |||
| 57bebebe09 | |||
| d19303cd62 | |||
| 7c96e46a37 | |||
| f75e68f8ce | |||
| 9b922b00c1 | |||
| c8d9ee2457 | |||
| a29f23086a | |||
| 4a76365df4 | |||
| 972264844a | |||
| e23e6f3c1a | |||
| 03c10382d1 | |||
| 3abe24aa18 | |||
| 2a4eabcda5 | |||
| 280d98c9c4 | |||
| 227c5c80c8 | |||
| edd00a6ab5 | |||
| d57bb17fc7 | |||
| 098da5c868 | |||
| dfc17bcc26 | |||
| 7a5eab104e | |||
| e0bf815377 | |||
| 2cbc6f2890 | |||
| 5859b2c08b | |||
| 44eaa9dc8b | |||
| 49cd7a41dd | |||
| 53de5ece21 | |||
| 90dffa5fcc | |||
| ac6cd0ca15 | |||
| 0e970128ab | |||
| 9fc373384d | |||
| a76bd2ea55 | |||
| 3c610b74d2 | |||
| 0623005ff4 | |||
| 8b032eba9a | |||
| e3229b85fd | |||
| 1178431b8f | |||
| 9d92c4a2b1 | |||
| 0bda8707ce | |||
| 9cb7d3a0fd | |||
| f1204f948a | |||
| 455b8dafb0 | |||
| fe5a32d95b | |||
| b34f6f1690 | |||
| a89bc35c6f | |||
| e551b6c202 | |||
| c6ed1afc51 | |||
| 4bedebb74d | |||
| 741fc7eb05 | |||
| 000da1197c | |||
| dd8be29d54 | |||
| 099c2c7ff4 | |||
| 874232a152 | |||
| d21f046e1f | |||
| ef0e25878e | |||
| cd37619af1 | |||
| 209ee18943 | |||
| aff7733a5b | |||
| 4bb65921e4 | |||
| 26216a6a8c | |||
| 60c3f11d7e | |||
| fcab08bfab | |||
| e69ba466e1 | |||
| 675e85ee09 | |||
| 36ec13b2c4 | |||
| 0b9c36e3ce | |||
| 07cf895447 | |||
| 028f21f0cf | |||
| 77724e363d | |||
| 15863d8a5a | |||
| b836af58f0 | |||
| ef5d880f14 | |||
| 15fd03b4c0 | |||
| cac06f3e17 | |||
| 6d81c62cdf | |||
| 60e319589d | |||
| 7723a5785d | |||
| dd982fe6e2 | |||
| c02dfeaa97 | |||
| a3d513afb0 | |||
| 40fa99a1f9 | |||
| f26c996095 | |||
| 012c4dfb9f | |||
| 9337ca2616 | |||
| 5dcec4bcfd | |||
| ed2e5e4d09 | |||
| 2249f52c32 | |||
| f4ef5d2b53 | |||
| a92c5d6ce0 | |||
| b61ceaead6 | |||
| 0672cfc8f3 | |||
| b5859a43cc | |||
| de1cd7c737 | |||
| f588312ad3 | |||
| 643ef1fa45 | |||
| ec38a4ed33 | |||
| defbd80ce0 | |||
| f11b91e7fa | |||
| 58bb4d5a7f | |||
| d92af60d9b | |||
| bca1e043bd | |||
| 7719175e85 | |||
| 046146c4d8 | |||
| 0e2f66b35a | |||
| 56b9ff1dc9 | |||
| dab8e78cd5 | |||
| 67dd27b9a1 | |||
| 4605985a47 | |||
| 02ac27342a | |||
| cbc59a4a1c | |||
| 02758d96c7 | |||
| 12752134fd | |||
| 504e70441b | |||
| 85cd16e793 | |||
| aa2277f05e | |||
| 27eb96722a | |||
| 0215096c9e | |||
| 90d8c51402 | |||
| ba0bbfefe7 | |||
| dd65673c1e | |||
| 2f3f8de682 | |||
| 3ce6fcad3a | |||
| 4302e7426e | |||
| 4e246c35c8 | |||
| 0cbcae9624 | |||
| 313aed606f | |||
| aade735c79 | |||
| 1e5786a142 | |||
| 3e5e5eeafa | |||
| 8a48ee8e14 | |||
| 6a89981c77 | |||
| 03c014fdda | |||
| ae683cd3c9 | |||
| a0dcae0882 | |||
| 6e839b210c | |||
| c7f4f62e93 | |||
| 214aba8f01 | |||
| 153c9d944f | |||
| 99a9d15871 | |||
| f6fb905ef8 | |||
| a5456452f6 | |||
| 48cd9af739 | |||
| 01fac428d5 | |||
| 3acce100f6 | |||
| ba2dfe6598 | |||
| 46600c38a8 | |||
| b9ae66e557 | |||
| bed9010918 | |||
| d13ccbde71 | |||
| 2e2aac0413 | |||
| 5f18cff344 | |||
| 830300ea81 | |||
| 9da2fcfbca | |||
| ece68df495 | |||
| 6a43ddbee9 | |||
| ee58596ed0 | |||
| 5bce6e77ae | |||
| 5dec45c0c8 | |||
| b7ba6500e4 | |||
| 2a263d749b | |||
| 3a21af48d1 | |||
| cb3bfe99ce | |||
| 328d51622f | |||
| 38ead74697 | |||
| ffd4d72850 | |||
| d23d90a794 | |||
| c74036ad26 | |||
| a146a9f424 | |||
| ddc0c82cee | |||
| bab09ac0d7 | |||
| 8a6eb912c5 | |||
| b5fbd40ea3 | |||
| 46b1c085cd | |||
| e5642cd752 | |||
| be933e9085 | |||
| 26769675c7 | |||
| 672d6d67aa | |||
| b3c6967ed9 | |||
| 9a528b17e8 | |||
| 4532932749 | |||
| 639a8e2f35 | |||
| 56c2ab38c6 | |||
| 9769cc0ed4 | |||
| 96d66b2205 | |||
| fea73109ca | |||
| e39c15c16f | |||
| 2a13f60bbd | |||
| d8e9738d71 | |||
| f38b78f821 | |||
| 9c21630295 | |||
| ea502727ba | |||
| 72f4999162 | |||
| adf1dbe635 | |||
| af2cdb1ca1 | |||
| e499c0e990 | |||
| 206ab6793f | |||
| e207c90fd4 | |||
| c9864008a3 | |||
| 5c89b27a87 | |||
| 3c482ba017 | |||
| 2d684411d4 | |||
| 408ee36a8b | |||
| d9efbad80d | |||
| 92e5c9f71a | |||
| baef88be50 | |||
| 7eff6ac504 | |||
| a3ea4d4e18 | |||
| f12047e36f | |||
| c77f37cc43 | |||
| 8bc463b6d6 | |||
| 953429f0da | |||
| 8598050931 | |||
| 81566674a8 | |||
| aba9f88352 | |||
| 0093c6ddf7 | |||
| cd5a0c2fbc | |||
| b1c803d2f1 | |||
| 3883b68669 | |||
| 5111bb12cf | |||
| cc2518a346 | |||
| 6e98f4c953 | |||
| 8990b25970 | |||
| c0cdde8685 | |||
| 7a7e20e4c2 | |||
| 743ccd27b6 | |||
| 155d9110c5 | |||
| 651e0b3445 | |||
| 82f438cbe4 | |||
| c3c30b4e20 | |||
| 8b4889ce83 | |||
| 7ccaa0390d | |||
| 5f0ed73d0d | |||
| 54518b63d0 | |||
| 1c38f2e5fd | |||
| 7ba12707e1 | |||
| 01992b26f4 | |||
| 8670ba08d5 | |||
| 67698757cf | |||
| 833ef3f5d3 | |||
| b104f14899 | |||
| 60c86b1db8 | |||
| b6d2965962 | |||
| 1548552af3 | |||
| 002cf8025b | |||
| dfd7e4b62c | |||
| e54380cd81 | |||
| 09b3532793 | |||
| b36e288958 | |||
| bcd3c2739a | |||
| 695e653461 | |||
| b068ff810c | |||
| 1a57f7a047 | |||
| 90f955e48e | |||
| 14fbce5a6b | |||
| 5cff407379 | |||
| 79b7ff21f5 | |||
| 65996a40e9 | |||
| cf3566255a | |||
| 582c6fa36d | |||
| aefba8b4df | |||
| e43674fd29 | |||
| 3856826e4d | |||
| 00ed0b7691 | |||
| aa456f6cff | |||
| 3a9dfa643e | |||
| 28580114da | |||
| 8d47f118dd | |||
| d7b45157ff | |||
| db8a48284f | |||
| b00b8d6ca3 | |||
| 22b3d8f691 | |||
| 432c1cd476 | |||
| ed8e4a059a | |||
| 571c1e31ac | |||
| b294d0eea0 | |||
| 0b50fbc55d | |||
| f5d5534290 | |||
| 777eb7e398 | |||
| 9b1be55a9e | |||
| ef4a7d1fa1 | |||
| e6a7380aa5 | |||
| 2f41591339 | |||
| 8916ef0207 | |||
| c3288f765e | |||
| 11d631bee3 | |||
| e3be78edb0 | |||
| 59f01a8426 | |||
| 76b7c8ef3b | |||
| fbaae9ce76 | |||
| e70b58a3df | |||
| ec551c9de8 | |||
| 7a8cb5835a | |||
| 3b8ccf173b | |||
| 172ce29665 | |||
| e7d8a8d2c0 | |||
| cc7b43f4ba | |||
| 71425977f5 | |||
| 4549727d0b | |||
| e030d97059 | |||
| 13c1ca4f79 | |||
| 637d7c0db1 | |||
| 7513a4b6d6 | |||
| 76100ef07d | |||
| ab139209db | |||
| c6a31baaab | |||
| 7c9d4629b6 | |||
| 986858c20d | |||
| a0551095dc | |||
| d0804c886a | |||
| 1befca7e28 | |||
| 3d4482157a | |||
| 3cd71f1cd4 | |||
| eebf443c1d | |||
| 7aaf0b9375 | |||
| 5ff9675e60 | |||
| 646390edff | |||
| b1958c5442 | |||
| 01b9590e81 | |||
| 985a288bf3 | |||
| 01dcfb88e7 | |||
| 76415a8dec | |||
| 586b7c14f8 | |||
| 0ee4bf2b25 | |||
| 5673e8abe8 | |||
| c446e32a8a | |||
| eee9392fb4 | |||
| 82a3961608 | |||
| f8689fc409 | |||
| 7de93e1780 | |||
| 19e742b7d1 | |||
| e3f0250fd2 | |||
| e4b39b89f3 | |||
| d2888b49e8 | |||
| b97ed49a60 | |||
| 65936dc85b | |||
| b1d02ee302 | |||
| cca47abb9c | |||
| c810ba62b4 | |||
| dcce7ffacc | |||
| e9fb137146 | |||
| 7100c9c300 | |||
| e9b5496387 | |||
| 87ecde390d | |||
| 8b33c66e22 | |||
| b3bbb6624e | |||
| 2ddac45612 | |||
| ddd7d47516 | |||
| a770072809 | |||
| 5b38ce0b37 | |||
| 804708076a | |||
| a0ad71a524 | |||
| 9d03694ea7 | |||
| 7a619e0e9e | |||
| 6bd5eb7930 | |||
| 744d1859d5 | |||
| 34a61f49f6 | |||
| 40addbf595 | |||
| abd6da0a37 | |||
| 794d1c5019 |
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"agentCanUpdateSnapshot": true
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
description:
|
||||
globs:
|
||||
alwaysApply: true
|
||||
---
|
||||
ВАЖНО!!! Мы работаем в МОНО-репозитории pnpm, любая установка пакетов производится ЧЕРЕЗ ФИЛЬТР компонента в корне: pnpm add glob --filter notificator2.
|
||||
|
||||
ВАЖНО!!! НИКОГДА НЕ ЗАПУСКАЙ ПРИЛОЖЕНИЕ ИЛИ ЕГО СБОРКУ!!! Просто отчитайся что всё сделал.
|
||||
@@ -10,6 +10,7 @@ alwaysApply: false
|
||||
2. Типы IName, IChecksum256, ITimePointSec - это просто строки.
|
||||
3. Домен должен быть полностью изолирован от инфраструктурных деталей.
|
||||
4. Направление зависимостей - внутрь (к ядру, домену).
|
||||
5. Домен, инфраструктура и приложение связаны через App.module, НЕ НУЖНО импортировать их друг в друга, а достаточно просто импортировать домен на уровне приложения чтобы использовать все экспорты домена.
|
||||
|
||||
## Структура проекта
|
||||
- `domain/` - доменный слой (бизнес-логика, независимая от инфраструктуры)
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
---
|
||||
description:
|
||||
globs:
|
||||
alwaysApply: true
|
||||
globs: **/desktop/**
|
||||
---
|
||||
|
||||
# DESKTOP
|
||||
|
||||
- Пишем код фронтенда в архитектуре Feature Sliced Design (FSD).
|
||||
- Стиль шаблонов PUG на composite API.
|
||||
- Всегда создаём index.ts файлы для vue компонент.
|
||||
- Создавай индексные файлы для vue через export {default as NAME} from './where', а на уровне выше, если это необходимо, делай export * from './ui'
|
||||
- Всегда удаляем неиспользуемые импорты.
|
||||
- Не используем emit избыточно. Вместо них используем композабл функции моделей фич, если необходимо. Emit только в крайнем случае.
|
||||
@@ -0,0 +1,62 @@
|
||||
---
|
||||
description:
|
||||
globs:
|
||||
alwaysApply: false
|
||||
---
|
||||
Файл [main.py](mdc:monocoop/monocoop/monocoop/components/docs/main.py) автоматически генерит ссылки на документацию SDK и GraphQL, которые формируются и публикуются автоматически. При создании документации к методам всегда применяй ссылки на SDK и GraphQL по форме:
|
||||
{{ get_sdk_doc("Mutations", "Accounts", "RegisterAccount") }} | {{ get_graphql_doc("Mutation.registerAccount") }}
|
||||
|
||||
### 🎯 Основные паттерны использования
|
||||
|
||||
**1. Стандартная структура для методов API:**
|
||||
```markdown
|
||||
## Название действия
|
||||
{{ get_sdk_doc("Namespace", "Module", "Method") }} | {{ get_graphql_doc("Type.methodName") }}
|
||||
|
||||
{{ get_typedoc_input("Namespace.Module.Method") }}
|
||||
|
||||
Результат:
|
||||
{{ get_typedoc_definition("Namespace.Module.Method", "IOutput") }}
|
||||
```
|
||||
|
||||
**2. Описание + пример (для сложных методов):**
|
||||
```markdown
|
||||
{{ get_typedoc_desc("Namespace.Module.Method") }}
|
||||
|
||||
{{ get_typedoc_input("Namespace.Module.Method") }}
|
||||
```
|
||||
|
||||
**3. Ссылки на типы данных:**
|
||||
```markdown
|
||||
У каждого аккаунта есть объект {{ get_graphql_definition("Account") }} в GraphQL-API
|
||||
```
|
||||
|
||||
### 🔧 Макросы и их применение
|
||||
|
||||
| Макрос | Назначение | Пример использования |
|
||||
|--------|------------|---------------------|
|
||||
| `get_sdk_doc` | Ссылка на SDK документацию | `{{ get_sdk_doc("Mutations", "Payments", "CreateDeposit") }}` |
|
||||
| `get_graphql_doc` | Ссылка на GraphQL операцию | `{{ get_graphql_doc("Query.getAccount") }}` |
|
||||
| `get_graphql_definition` | Ссылка на GraphQL тип | `{{ get_graphql_definition("Account") }}` |
|
||||
| `get_class_doc` | Ссылка на класс/метод SDK | `{{ get_class_doc("Document", "sign") }}` |
|
||||
| `get_typedoc_input` | **Полный пример** TypeScript вызова | `{{ get_typedoc_input("Mutations.Auth.Login") }}` |
|
||||
| `get_typedoc_definition` | **Структура интерфейса** | `{{ get_typedoc_definition("Mutations.Auth.Login", "IOutput") }}` |
|
||||
| `get_typedoc_desc` | Описание + примеры | `{{ get_typedoc_desc("Mutations.Auth.Login") }}` |
|
||||
| `get_typedoc_value` | Значение константы | `{{ get_typedoc_value("Constants.API_VERSION") }}` |
|
||||
|
||||
### 📝 Правила оформления
|
||||
|
||||
**ОБЯЗАТЕЛЬНО:**
|
||||
- Всегда используй `get_typedoc_input` для демонстрации **КАК вызывать** метод
|
||||
- Всегда используй `get_typedoc_definition` для показа **ЧТО возвращается**
|
||||
- Комбинируй SDK и GraphQL ссылки через ` | `: `{{ get_sdk_doc(...) }} | {{ get_graphql_doc(...) }}`
|
||||
|
||||
**ЖЕЛАТЕЛЬНО:**
|
||||
- Для сложных методов добавляй `get_typedoc_desc` в начало
|
||||
- Используй `get_graphql_definition` для ссылок на типы данных в тексте
|
||||
- Группируй связанные операции в одном разделе
|
||||
|
||||
**ФОРМАТ ССЫЛОК:**
|
||||
- SDK: `"Namespace", "Module", "Method"` (3 аргумента)
|
||||
- GraphQL: `"Type.methodName"` (точка между типом и методом)
|
||||
- Определения: просто имя типа `"TypeName"`
|
||||
@@ -0,0 +1,96 @@
|
||||
---
|
||||
description:
|
||||
globs:
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Структура файлов шаблонов документов и алгоритм правки
|
||||
|
||||
## Основные директории и файлы
|
||||
|
||||
### 1. Определение типов и интерфейсов (cooptypes)
|
||||
**Путь:** `/cooptypes/src/cooperative/registry/[ID.DocumentName]/index.ts`
|
||||
|
||||
В этих файлах определяются:
|
||||
- Интерфейсы данных (`IAgendaMeet`, etc.)
|
||||
- Основная модель данных документа (`Model`)
|
||||
- HTML шаблон документа (`context`)
|
||||
- Переводы строк (`translations`)
|
||||
- Пример данных для тестирования (`exampleData`)
|
||||
|
||||
**Для изменения шаблона нужно править:**
|
||||
- Интерфейсы модели данных при изменении структуры
|
||||
- HTML в переменной `context` для изменения внешнего вида
|
||||
- Строки переводов в `translations.ru`
|
||||
|
||||
### 2. Схемы валидации (factory)
|
||||
**Путь:** `/factory/src/Schema/[SchemaName].ts`
|
||||
|
||||
Содержат:
|
||||
- JSON схемы для валидации данных
|
||||
- Определение обязательных полей
|
||||
|
||||
**При изменении структуры данных:**
|
||||
- Обновите соответствующую схему валидации
|
||||
- Обновите список обязательных полей в `required`
|
||||
|
||||
### 3. Фабрики документов (factory)
|
||||
**Путь:** `/factory/src/Actions/[ID.DocumentName].ts`
|
||||
|
||||
Обрабатывают:
|
||||
- Получение данных из разных источников
|
||||
- Сборку модели для шаблонизатора
|
||||
- Валидацию данных по схеме
|
||||
- Генерацию PDF
|
||||
|
||||
**При изменении логики сборки документа:**
|
||||
- Обновите метод `generateDocument()`
|
||||
- Добавьте новые источники данных
|
||||
|
||||
### 4. Шаблоны (factory)
|
||||
**Путь:** `/factory/src/Templates/[ID.DocumentName].ts`
|
||||
|
||||
Связывают:
|
||||
- Шаблон из cooptypes
|
||||
- Схему валидации
|
||||
- Метаданные документа
|
||||
|
||||
**После изменения в cooptypes:**
|
||||
- Убедитесь, что схема в Templates соответствует новой модели
|
||||
|
||||
### 5. Тесты (factory)
|
||||
**Путь:** `/factory/test/[category].test.ts`
|
||||
|
||||
Содержат:
|
||||
- Тестовые данные для генерации документов
|
||||
- Вызовы функции `testDocumentGeneration`
|
||||
|
||||
**После внесения изменений:**
|
||||
- Обновите тестовые данные в соответствии с новой структурой
|
||||
- Запустите тесты командой `pnpm test`
|
||||
|
||||
## Алгоритм внесения изменений
|
||||
|
||||
1. **Модификация типов**
|
||||
- Изменить файл в `/cooptypes/src/cooperative/registry/[ID.DocumentName]/index.ts`
|
||||
- Обновить интерфейсы, шаблон HTML и переводы
|
||||
|
||||
2. **Обновление схемы валидации**
|
||||
- Изменить соответствующую схему в `/factory/src/Schema/`
|
||||
- Убедиться, что обязательные поля совпадают с интерфейсом
|
||||
|
||||
3. **Компиляция библиотеки типов**
|
||||
- Выполнить `cd /cooptypes && pnpm build`
|
||||
|
||||
4. **Обновление тестовых данных**
|
||||
- Привести тестовые данные в соответствие с новой структурой
|
||||
|
||||
5. **Запуск тестов**
|
||||
- Выполнить `cd /factory && pnpm test [filename].test.ts`
|
||||
|
||||
## Особенности шаблонизации
|
||||
|
||||
- Используется Nunjucks для шаблонов
|
||||
- Поддерживаются конструкции: `{% if %}`, `{% for %}`, `{% trans %}`
|
||||
- Переменные вставляются через `{{ variable }}`
|
||||
- Переводы через `{% trans 'KEY' %}`
|
||||
@@ -0,0 +1,261 @@
|
||||
---
|
||||
description:
|
||||
globs: src/**/*.ts
|
||||
alwaysApply: false
|
||||
---
|
||||
# Фабрика Документов Кооперативов
|
||||
|
||||
## Общая Архитектура
|
||||
|
||||
Фабрика документов — это система генерации PDF документов для кооперативов, построенная на TypeScript с использованием MongoDB для хранения данных. Система состоит из трех основных частей:
|
||||
|
||||
1. **registry/** — JSON-шаблоны документов (статические данные)
|
||||
2. **factory/** — основная фабрика с логикой генерации
|
||||
3. **cooptypes/** — типы данных и интерфейсы
|
||||
|
||||
## Структура Registry
|
||||
|
||||
В корневой папке `registry/` находятся JSON файлы с номерными названиями, представляющие шаблоны документов:
|
||||
|
||||
### Основные документы:
|
||||
- `1.walletProgramAgreement.json` — соглашение о кошельке
|
||||
- `2.regulationElectronicSignature.json` — регламент электронной подписи
|
||||
- `3.privacyPolicy.json` — политика конфиденциальности
|
||||
- `4.userAgreement.json` — пользовательское соглашение
|
||||
- `50.CoopenomicsAgreement.json` — соглашение с партнерами
|
||||
- `100.participantApplication.json` — заявление участника
|
||||
- `101.selectBranchStatement.json` — заявление о выборе филиала
|
||||
|
||||
### Документы общих собраний (300-304):
|
||||
- `300.annualGeneralMeetingAgenda.json` — предложение повестки дня
|
||||
- `301.annualGeneralMeetingSovietDecision.json` — протокол заседания совета
|
||||
- `302.annualGeneralMeetingNotification.json` — уведомление о собрании
|
||||
- `303.annualGeneralMeetingVotingBallot.json` — бюллетень для голосования
|
||||
- `304.annualGeneralMeetingDecision.json` — протокол общего собрания
|
||||
|
||||
### Инвестиционные документы (1000+):
|
||||
- `1000.investAgreement.json` — инвестиционное соглашение
|
||||
- `1001.investByResultStatement.json` — заявление о зачете по результатам
|
||||
- `1002.investByResultAct.json` — акт зачета по результатам
|
||||
- `1005.investByMoneyStatement.json` — заявление о зачете денежных средств
|
||||
|
||||
### Структура JSON-шаблона:
|
||||
```json
|
||||
{
|
||||
"context": "<div>...HTML шаблон с переменными...</div>",
|
||||
"model": {...данные для примера...},
|
||||
"translation": {...переводы ключей...},
|
||||
"object_model": {...схема объектной модели...}
|
||||
}
|
||||
```
|
||||
|
||||
## Фабрика (factory/)
|
||||
|
||||
### Основные компоненты:
|
||||
|
||||
#### src/index.ts — Главный класс Generator
|
||||
```typescript
|
||||
export class Generator implements IGenerator {
|
||||
// Хранилище фабрик для каждого типа документа
|
||||
factories: { [K in Numbers]: DocFactory<IGenerate> }
|
||||
|
||||
// MongoDB коннектор
|
||||
public storage: MongoDBConnector
|
||||
|
||||
// Основной метод генерации
|
||||
async generate(data: IGenerate, options?: IGenerationOptions): Promise<IGeneratedDocument>
|
||||
}
|
||||
```
|
||||
|
||||
#### Архитектура Factory Pattern:
|
||||
- Базовый класс `DocFactory<T>` в `src/Factory/index.ts`
|
||||
- Каждый документ имеет свою фабрику в `src/Actions/`
|
||||
- Фабрики наследуются от `DocFactory` и реализуют метод `generateDocument()`
|
||||
|
||||
### Сервисы:
|
||||
|
||||
#### Services/Generator/ — PDF генерация
|
||||
- `PDFService` — конвертирует HTML в PDF через WeasyPrint
|
||||
- Использует шрифт Arial (base64)
|
||||
- Добавляет метаданные в PDF
|
||||
- Вычисляет SHA-256 хеш документа
|
||||
|
||||
#### Services/Templator/ — Шаблонизация
|
||||
- Основан на Nunjucks
|
||||
- Поддерживает кастомное расширение `{% trans %}` для переводов
|
||||
- Рендерит HTML из шаблона с подстановкой переменных
|
||||
|
||||
#### Services/Validator/ — Валидация
|
||||
- Использует AJV для JSON Schema валидации
|
||||
- Поддерживает кастомные форматы (телефон)
|
||||
- Локализация ошибок на русском языке
|
||||
|
||||
#### Services/Databazor/ — База данных
|
||||
- `MongoDBConnector` — работа с MongoDB
|
||||
- `DataService` — абстракция над данными
|
||||
- Коллекции: `deltas`, `actions`, `documents`, и другие
|
||||
|
||||
### Модели данных (src/Models/):
|
||||
|
||||
#### Основные типы пользователей:
|
||||
- `Individual` — физические лица (ФИО, паспорт, адрес)
|
||||
- `Organization` — организации (ИНН, ОГРН, представитель)
|
||||
- `Entrepreneur` — ИП (ФИО + ИНН/ОГРН)
|
||||
|
||||
#### Кооперативные данные:
|
||||
- `Cooperative` — данные кооператива
|
||||
- `PaymentMethod` — платежные методы
|
||||
- `Vars` — переменные кооператива
|
||||
- `Project` — проекты
|
||||
|
||||
### Система Action-ов:
|
||||
|
||||
Каждый документ имеет Action класс в `src/Actions/` с методом `generateDocument()`:
|
||||
|
||||
1. **Получение шаблона** — из локального Registry или MongoDB
|
||||
2. **Сбор данных** — пользователь, кооператив, переменные, специфичные данные
|
||||
3. **Валидация** — проверка по JSON схеме
|
||||
4. **Рендеринг** — HTML из шаблона + данные
|
||||
5. **PDF генерация** — HTML → PDF с метаданными
|
||||
6. **Сохранение** — в MongoDB (если не skip_save)
|
||||
|
||||
## Система типов (cooptypes/)
|
||||
|
||||
### cooperative/registry/ — Типы документов
|
||||
Каждый документ имеет папку с интерфейсами:
|
||||
- `Action` — входные данные для генерации
|
||||
- `Model` — модель данных для шаблона
|
||||
- `Template` — структура шаблона
|
||||
|
||||
### contracts/ — Блокчейн контракты
|
||||
- `registrator/` — регистрация кооперативов
|
||||
- `soviet/` — управление советом
|
||||
- `meet/` — общие собрания
|
||||
- `wallet/`, `capital/`, `fund/` — финансовые операции
|
||||
|
||||
## База данных MongoDB
|
||||
|
||||
### Основные коллекции:
|
||||
|
||||
#### deltas — Состояние блокчейна
|
||||
```javascript
|
||||
{
|
||||
block_num: number,
|
||||
present: boolean,
|
||||
code: string, // название контракта
|
||||
scope: string, // область действия
|
||||
table: string, // имя таблицы
|
||||
primary_key: string,
|
||||
value: {...} // данные записи
|
||||
}
|
||||
```
|
||||
|
||||
#### actions — Действия блокчейна
|
||||
```javascript
|
||||
{
|
||||
block_num: number,
|
||||
account: string,
|
||||
name: string, // имя действия
|
||||
receiver: string,
|
||||
data: {...} // данные действия
|
||||
}
|
||||
```
|
||||
|
||||
#### documents — Сгенерированные документы
|
||||
```javascript
|
||||
{
|
||||
hash: string, // SHA-256 хеш
|
||||
binary: Uint8Array, // PDF данные
|
||||
html: string, // HTML исходник
|
||||
meta: { // метаданные
|
||||
title: string,
|
||||
created_at: string,
|
||||
lang: string
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Таблицы данных:
|
||||
- `coops` — кооперативы
|
||||
- `meets` — собрания
|
||||
- `questions` — вопросы собраний
|
||||
- `decisions` — решения совета
|
||||
- `individuals` — физические лица
|
||||
- `organizations` — организации
|
||||
- `entrepreneurs` — ИП
|
||||
- `paymentMethods` — платежные методы
|
||||
- `vars` — переменные кооперативов
|
||||
|
||||
## Генерация документов
|
||||
|
||||
### Процесс генерации:
|
||||
1. **Вызов** `generator.generate(action, options)`
|
||||
2. **Поиск фабрики** по `registry_id`
|
||||
3. **Загрузка шаблона** (локально или из БД)
|
||||
4. **Сбор данных** из MongoDB по `coopname`, `username`, `block_num`
|
||||
5. **Создание модели** — объединение всех данных
|
||||
6. **Валидация** модели по JSON схеме
|
||||
7. **Рендеринг HTML** через Nunjucks
|
||||
8. **Генерация PDF** через WeasyPrint
|
||||
9. **Добавление метаданных** в PDF
|
||||
10. **Вычисление хеша** SHA-256
|
||||
11. **Сохранение** в MongoDB
|
||||
|
||||
### Пример использования:
|
||||
```typescript
|
||||
const generator = new Generator()
|
||||
await generator.connect(mongoUri)
|
||||
|
||||
const document = await generator.generate({
|
||||
registry_id: '300',
|
||||
coopname: 'voskhod',
|
||||
username: 'ant',
|
||||
block_num: 0,
|
||||
meet: {...},
|
||||
questions: [...]
|
||||
})
|
||||
```
|
||||
|
||||
## Особенности реализации
|
||||
|
||||
### Шаблонизация:
|
||||
- HTML шаблоны с CSS стилями
|
||||
- Переменные в формате `{{variable.field}}`
|
||||
- Условная логика `{% if condition %}`
|
||||
- Циклы `{% for item in array %}`
|
||||
- Переводы `{% trans 'KEY', var1, var2 %}`
|
||||
|
||||
### Подписи:
|
||||
- Цифровые подписи вместо физических
|
||||
- Текст "Подписано электронной подписью"
|
||||
- Убраны подчеркивания для подписей
|
||||
|
||||
### Типы собраний:
|
||||
- `regular` — очередное
|
||||
- `extraordinary` — внеочередное
|
||||
- Условная логика в шаблонах
|
||||
|
||||
### Филиалы:
|
||||
- `coop.is_branched` — проверка на наличие филиалов
|
||||
- "пайщиков" vs "уполномоченных" в зависимости от типа
|
||||
|
||||
### Форматирование дат:
|
||||
- Формат: "г. Москва, 15 декабря 2024 г."
|
||||
- Без кавычек вокруг дат
|
||||
- Запятая после города
|
||||
|
||||
## Тестирование
|
||||
|
||||
### test/utils/index.ts — Тестовые утилиты:
|
||||
- `preLoading()` — инициализация тестовых данных
|
||||
- Создание кооператива, пользователей, платежных методов
|
||||
- Настройка данных собраний и решений
|
||||
- Очистка временных файлов
|
||||
|
||||
### Тестовые данные:
|
||||
- Кооператив "ВОСХОД"
|
||||
- Пользователи: ant, individual, entrepreneur
|
||||
- Организации: voskhod, branch, exampleorg
|
||||
- Собрания с вопросами и решениями
|
||||
|
||||
Фабрика поддерживает полный цикл создания документов кооператива от заявлений до протоколов собраний с возможностью кастомизации под разные типы кооперативов и требования.
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
globs: notifications/src/workflows/**/*.ts
|
||||
---
|
||||
# Правила валидации воркфлоу
|
||||
|
||||
## 🚫 ID воркфлоу
|
||||
- **Длина ID не должна превышать 32 символа**
|
||||
- Используйте описательные, но короткие идентификаторы малыми латинскими буквами и тире.
|
||||
|
||||
## 🚫 Условия в шаблонах
|
||||
- **Запрещено использовать JavaScript выражения в шаблонах Novu**
|
||||
- Нельзя использовать:
|
||||
- Тернарные операторы: `{{condition ? "text1" : "text2"}}`
|
||||
- Логические операторы: `{{field && "text"}}`
|
||||
- Любые другие JS конструкции
|
||||
|
||||
## ✅ Рекомендации
|
||||
- Добавляйте текстовые поля в payload для условной логики
|
||||
- Вычисляйте значения на стороне сервера перед отправкой уведомления
|
||||
- Используйте только простые переменные: `{{payload.fieldName}}`
|
||||
@@ -2,8 +2,6 @@ name: Build Docker Images
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- testnet
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
@@ -14,7 +12,7 @@ jobs:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: testnet
|
||||
ref: ${{ github.ref }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Debug info
|
||||
@@ -28,12 +26,23 @@ jobs:
|
||||
echo "Проверяем файлы в middlewares:"
|
||||
ls -la components/desktop/src-ssr/middlewares/ || echo "Директория middlewares не найдена!"
|
||||
|
||||
- name: Set docker tag
|
||||
- name: Set docker tags
|
||||
run: |
|
||||
if [[ $GITHUB_REF == refs/tags/* ]]; then
|
||||
echo "DOCKER_TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
|
||||
TAG_NAME=${GITHUB_REF#refs/tags/}
|
||||
echo "DOCKER_TAG=$TAG_NAME" >> $GITHUB_ENV
|
||||
|
||||
# Проверяем, является ли тег продакшн-тегом (не содержит alpha, beta, rc и т.д.)
|
||||
if [[ ! $TAG_NAME =~ -(alpha|beta|rc|test) ]]; then
|
||||
echo "IS_PRODUCTION_TAG=true" >> $GITHUB_ENV
|
||||
echo "Это продакшн тег, будем добавлять latest"
|
||||
else
|
||||
echo "IS_PRODUCTION_TAG=false" >> $GITHUB_ENV
|
||||
echo "Это не продакшн тег, latest не добавляем"
|
||||
fi
|
||||
else
|
||||
echo "DOCKER_TAG=latest" >> $GITHUB_ENV
|
||||
echo "IS_PRODUCTION_TAG=false" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
- name: Login to DockerHub
|
||||
@@ -47,6 +56,12 @@ jobs:
|
||||
run: |
|
||||
docker build --target runtime -t dicoop/mono-base:${{ env.DOCKER_TAG }} .
|
||||
docker push dicoop/mono-base:${{ env.DOCKER_TAG }}
|
||||
|
||||
# Если это продакшн тег, добавляем latest
|
||||
if [[ "${{ env.IS_PRODUCTION_TAG }}" == "true" ]]; then
|
||||
docker tag dicoop/mono-base:${{ env.DOCKER_TAG }} dicoop/mono-base:latest
|
||||
docker push dicoop/mono-base:latest
|
||||
fi
|
||||
|
||||
# Создаем сервисные образы на основе базового
|
||||
- name: Build desktop image
|
||||
@@ -55,6 +70,11 @@ jobs:
|
||||
echo "CMD [\"pnpm\", \"-F\", \"@coopenomics/desktop\", \"run\", \"start\"]" >> Dockerfile.desktop
|
||||
docker build -t dicoop/desktop:${{ env.DOCKER_TAG }} -f Dockerfile.desktop .
|
||||
docker push dicoop/desktop:${{ env.DOCKER_TAG }}
|
||||
|
||||
if [[ "${{ env.IS_PRODUCTION_TAG }}" == "true" ]]; then
|
||||
docker tag dicoop/desktop:${{ env.DOCKER_TAG }} dicoop/desktop:latest
|
||||
docker push dicoop/desktop:latest
|
||||
fi
|
||||
|
||||
- name: Build controller image
|
||||
run: |
|
||||
@@ -62,6 +82,11 @@ jobs:
|
||||
echo "CMD [\"pnpm\", \"-F\", \"@coopenomics/controller\", \"run\", \"start\"]" >> Dockerfile.coopback
|
||||
docker build -t dicoop/coopback:${{ env.DOCKER_TAG }} -f Dockerfile.coopback .
|
||||
docker push dicoop/coopback:${{ env.DOCKER_TAG }}
|
||||
|
||||
if [[ "${{ env.IS_PRODUCTION_TAG }}" == "true" ]]; then
|
||||
docker tag dicoop/coopback:${{ env.DOCKER_TAG }} dicoop/coopback:latest
|
||||
docker push dicoop/coopback:latest
|
||||
fi
|
||||
|
||||
- name: Build parser image
|
||||
run: |
|
||||
@@ -69,6 +94,11 @@ jobs:
|
||||
echo "CMD [\"pnpm\", \"-F\", \"@coopenomics/parser\", \"run\", \"start\"]" >> Dockerfile.cooparser
|
||||
docker build -t dicoop/cooparser:${{ env.DOCKER_TAG }} -f Dockerfile.cooparser .
|
||||
docker push dicoop/cooparser:${{ env.DOCKER_TAG }}
|
||||
|
||||
if [[ "${{ env.IS_PRODUCTION_TAG }}" == "true" ]]; then
|
||||
docker tag dicoop/cooparser:${{ env.DOCKER_TAG }} dicoop/cooparser:latest
|
||||
docker push dicoop/cooparser:latest
|
||||
fi
|
||||
|
||||
- name: Build notificator image
|
||||
run: |
|
||||
@@ -76,19 +106,58 @@ jobs:
|
||||
echo "CMD [\"pnpm\", \"-F\", \"coop-notificator\", \"run\", \"start\"]" >> Dockerfile.notificator
|
||||
docker build -t dicoop/notificator:${{ env.DOCKER_TAG }} -f Dockerfile.notificator .
|
||||
docker push dicoop/notificator:${{ env.DOCKER_TAG }}
|
||||
|
||||
|
||||
if [[ "${{ env.IS_PRODUCTION_TAG }}" == "true" ]]; then
|
||||
docker tag dicoop/notificator:${{ env.DOCKER_TAG }} dicoop/notificator:latest
|
||||
docker push dicoop/notificator:latest
|
||||
fi
|
||||
|
||||
- name: Build notifications image
|
||||
run: |
|
||||
echo "FROM dicoop/mono-base:${{ env.DOCKER_TAG }}" > Dockerfile.notifications
|
||||
echo "CMD [\"pnpm\", \"-F\", \"@coopenomics/notifications\", \"run\", \"sync\"]" >> Dockerfile.notifications
|
||||
docker build -t dicoop/notifications:${{ env.DOCKER_TAG }} -f Dockerfile.notifications .
|
||||
docker push dicoop/notifications:${{ env.DOCKER_TAG }}
|
||||
|
||||
if [[ "${{ env.IS_PRODUCTION_TAG }}" == "true" ]]; then
|
||||
docker tag dicoop/notifications:${{ env.DOCKER_TAG }} dicoop/notifications:latest
|
||||
docker push dicoop/notifications:latest
|
||||
fi
|
||||
|
||||
# Отправка хука для деплоя
|
||||
- name: Trigger deployment webhook
|
||||
if: ${{ success() }}
|
||||
run: |
|
||||
if [[ $GITHUB_REF == refs/tags/* ]]; then
|
||||
# Хук для продакшена
|
||||
curl -X POST ${{ vars.PRODUCTION_WEBHOOK_URL }} \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '${{ env.DOCKER_TAG }}'
|
||||
if [[ $GITHUB_REF == refs/tags/*alpha* ]]; then
|
||||
# Хук для тестнета (alpha теги)
|
||||
curl -X POST ${{ vars.TESTNET_WEBHOOK_URL }} \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '${{ env.DOCKER_TAG }}'
|
||||
elif [[ $GITHUB_REF == refs/tags/* ]]; then
|
||||
# Хук для продакшена (остальные теги)
|
||||
curl -X POST ${{ vars.PRODUCTION_WEBHOOK_URL }} \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '${{ env.DOCKER_TAG }}'
|
||||
fi
|
||||
|
||||
# Уведомление в Telegram об успехе
|
||||
- name: Telegram notify success
|
||||
if: ${{ success() }}
|
||||
run: |
|
||||
if [[ "${{ env.IS_PRODUCTION_TAG }}" == "true" ]]; then
|
||||
ADDITIONAL_INFO=" (с тегом latest)"
|
||||
else
|
||||
# Хук для тестнета
|
||||
curl -X POST ${{ vars.TESTNET_WEBHOOK_URL }} \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '${{ env.DOCKER_TAG }}'
|
||||
fi
|
||||
ADDITIONAL_INFO=""
|
||||
fi
|
||||
|
||||
curl -s -X POST https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendMessage \
|
||||
-d chat_id=${{ secrets.TELEGRAM_CHAT_ID }} \
|
||||
-d text="✅ [GITHUB MONO] Успешная сборка контейнеров: $GITHUB_REPOSITORY ($GITHUB_REF) [${{ env.DOCKER_TAG }}]$ADDITIONAL_INFO"
|
||||
|
||||
# Уведомление в Telegram об ошибке
|
||||
- name: Telegram notify failure
|
||||
if: ${{ failure() }}
|
||||
run: |
|
||||
curl -s -X POST https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendMessage \
|
||||
-d chat_id=${{ secrets.TELEGRAM_CHAT_ID }} \
|
||||
-d text="❌ [GITHUB MONO] Ошибка при сборке контейнеров: $GITHUB_REPOSITORY ($GITHUB_REF) [${{ env.DOCKER_TAG }}]"
|
||||
@@ -0,0 +1,18 @@
|
||||
# .github/workflows/trigger-coopenomics.yml
|
||||
name: Trigger Contracts Docs Deploy
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [dev, testnet, main, capital] # или когда нужно триггерить
|
||||
|
||||
jobs:
|
||||
trigger-coopenomics:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Trigger Coopenomics deployment
|
||||
uses: peter-evans/repository-dispatch@v2
|
||||
with:
|
||||
token: ${{ secrets.COOPENOMICS_PAT }}
|
||||
repository: coopenomics/coopenomics # укажи правильный owner/repo
|
||||
event-type: deploy_from_mono
|
||||
client-payload: '{"repository": "${{ github.repository }}", "sha": "${{ github.sha }}", "ref": "${{ github.ref }}", "actor": "${{ github.actor }}"}'
|
||||
@@ -4,6 +4,9 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- testnet
|
||||
- dev
|
||||
|
||||
jobs:
|
||||
build-and-publish-docs:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -81,6 +84,18 @@ jobs:
|
||||
mkdocs build
|
||||
working-directory: ./components/docs
|
||||
|
||||
- name: Remove specific large file before publishing
|
||||
run: |
|
||||
# Удаляем конкретный большой файл sdk/typedoc.json
|
||||
rm -f ./components/docs/site/sdk/typedoc.json
|
||||
# Проверяем, что файл удален
|
||||
if [ -f "./components/docs/site/sdk/typedoc.json" ]; then
|
||||
echo "ERROR: typedoc.json still exists!"
|
||||
exit 1
|
||||
else
|
||||
echo "SUCCESS: typedoc.json removed successfully"
|
||||
fi
|
||||
|
||||
- name: Publish to GitHub Pages
|
||||
run: npx gh-pages --nojekyll -d site --repo https://x-access-token:${GITHUB_TOKEN}@github.com/coopenomics/mono.git
|
||||
working-directory: ./components/docs
|
||||
@@ -89,5 +104,4 @@ jobs:
|
||||
GIT_AUTHOR_EMAIL: github-actions@github.com
|
||||
GIT_COMMITTER_NAME: github-actions
|
||||
GIT_COMMITTER_EMAIL: github-actions@github.com
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -3,31 +3,29 @@ name: Publish Packages
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
- 'v*'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build-and-publish:
|
||||
if: |
|
||||
startsWith(github.ref, 'refs/tags/v') &&
|
||||
!contains(github.ref, '-alpha')
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v3
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
node-version: 24
|
||||
registry-url: https://registry.npmjs.org
|
||||
|
||||
- name: Install pnpm
|
||||
run: npm install -g pnpm
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Build all packages
|
||||
run: pnpm lerna run build
|
||||
|
||||
- name: Publish to npm
|
||||
- run: npm install -g pnpm
|
||||
- run: pnpm install
|
||||
- run: pnpm lerna run build
|
||||
- run: pnpm lerna publish from-package --yes
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
run: pnpm lerna publish from-package --yes --no-verify-access
|
||||
@@ -5,3 +5,7 @@ blockchain-data/
|
||||
scripts/changelog-prompt.md
|
||||
scripts/changelog-release.md
|
||||
scripts/release-info.md
|
||||
components/docs/docs/sdk
|
||||
dist/
|
||||
.env
|
||||
.DS_Store
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"printWidth": 120,
|
||||
"plugins": [
|
||||
"prettier-plugin-sort-imports"
|
||||
]
|
||||
}
|
||||
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"vue.volar",
|
||||
"vue.vscode-typescript-vue-plugin"
|
||||
]
|
||||
}
|
||||
|
||||
Vendored
+63
-5
@@ -1,16 +1,74 @@
|
||||
{
|
||||
"editor.bracketPairColorization.enabled": true,
|
||||
"editor.guides.bracketPairs": true,
|
||||
"editor.formatOnSave": true,
|
||||
"editor.tabCompletion": "onlySnippets",
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint":"explicit"
|
||||
},
|
||||
"eslint.run": "onType",
|
||||
"eslint.validate": ["javascript", "typescript", "vue"],
|
||||
"i18n-ally.localesPaths": ["src/i18n"],
|
||||
"typescript.format.enable": true,
|
||||
"typescript.format.indentSwitchCase": false,
|
||||
"typescript.validate.enable": true
|
||||
"typescript.validate.enable": true,
|
||||
"notebook.defaultFormatter": "Vue.volar",
|
||||
|
||||
// Оптимизация для монорепозитория
|
||||
"typescript.preferences.useAliasesForRenames": false,
|
||||
"typescript.preferences.includePackageJsonAutoImports": "on",
|
||||
"typescript.suggest.autoImports": true,
|
||||
"typescript.suggest.paths": true,
|
||||
"typescript.updateImportsOnFileMove.enabled": "always",
|
||||
"typescript.workspaceSymbols.scope": "currentProject",
|
||||
|
||||
// Настройки для снижения нагрузки
|
||||
"files.watcherExclude": {
|
||||
"**/node_modules/**": true,
|
||||
"**/dist/**": true,
|
||||
"**/.git/objects/**": true,
|
||||
"**/.git/subtree-cache/**": true,
|
||||
"**/node_modules/*/**": true,
|
||||
"**/.cache/**": true,
|
||||
"**/.quasar/**": true,
|
||||
"**/*.tsbuildinfo": true
|
||||
},
|
||||
|
||||
"search.exclude": {
|
||||
"**/node_modules": true,
|
||||
"**/dist": true,
|
||||
"**/.cache": true,
|
||||
"**/.quasar": true,
|
||||
"**/*.tsbuildinfo": true
|
||||
},
|
||||
|
||||
"files.exclude": {
|
||||
"**/.cache": true,
|
||||
"**/*.tsbuildinfo": true,
|
||||
"**/node_modules/.cache": true
|
||||
},
|
||||
|
||||
// TypeScript server настройки для монорепозитория
|
||||
"typescript.tsserver.maxTsServerMemory": 8192,
|
||||
"typescript.tsserver.watchOptions": {
|
||||
"excludeDirectories": [
|
||||
"**/node_modules",
|
||||
"**/dist",
|
||||
"**/.cache",
|
||||
"**/.quasar",
|
||||
"**/build"
|
||||
]
|
||||
},
|
||||
|
||||
// Использовать локальный TypeScript из монорепо
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"typescript.enablePromptUseWorkspaceTsdk": true,
|
||||
|
||||
// КРИТИЧЕСКИ ВАЖНО: включить project references для монорепозитория
|
||||
"typescript.tsserver.useSyntaxServer": "auto",
|
||||
"typescript.tsserver.experimental.enableProjectDiagnostics": true,
|
||||
|
||||
// Оптимизация для больших монорепозиториев
|
||||
"typescript.disableAutomaticTypeAcquisition": true,
|
||||
"typescript.surveys.enabled": false,
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "vscode.typescript-language-features"
|
||||
}
|
||||
}
|
||||
|
||||
+227
@@ -1,3 +1,230 @@
|
||||
# v2025.12.28-8
|
||||
|
||||
В этой версии представлен прототип трекера результатов интеллектуальной деятельности, реализован мост в 1С, обновлен интерфейс и существенно повышена стабильность системы.
|
||||
|
||||
---
|
||||
|
||||
### ✨ Новые функции
|
||||
- [#332](https://github.com/coopenomics/mono/issues/332): Прототип конструктора требований дополнительных документов при регистрации
|
||||
- [#330](https://github.com/coopenomics/mono/issues/330): Прототип моста в 1С:Бухгалтерию для передачи документов и проводок
|
||||
- [#329](https://github.com/coopenomics/mono/issues/329): Интеграция и тестирование LMS TUTOR для образовательных задач
|
||||
- [#328](https://github.com/coopenomics/mono/issues/328): Размещение прототипов мульти-лендингов на цифровой-кооператив.рф и coopenomics.world
|
||||
- [#326](https://github.com/coopenomics/mono/issues/326): Минимальный интерфейс трекера результатов интеллектуальной деятельности
|
||||
- [#324](https://github.com/coopenomics/mono/issues/324): Смарт-контракт генерации и капитализации результатов интеллектуальной деятельности ("Благорост")
|
||||
- [#322](https://github.com/coopenomics/mono/issues/322): Поставка обновлений ПО с нулевым даунтаймом по blue-green стратегии
|
||||
- [#321](https://github.com/coopenomics/mono/issues/321): Внедрение системы проводок по фондам для контрактов
|
||||
- [#319](https://github.com/coopenomics/mono/issues/319): Палитра команд и быстрый доступ к страницам рабочих столов (cmk+k)
|
||||
- [#316](https://github.com/coopenomics/mono/issues/316): Переход рабочего стола на GraphQL SDK
|
||||
- [#314](https://github.com/coopenomics/mono/issues/314): Развёртывание GlitchTip для мониторинга ошибок
|
||||
- [#306](https://github.com/coopenomics/mono/issues/306): Модуль запросов и мутаций для контракта капитализации
|
||||
|
||||
### 🐛 Исправления ошибок
|
||||
- [#312](https://github.com/coopenomics/mono/issues/312): Исправление подписки на изменение статуса коммитов
|
||||
- [#309](https://github.com/coopenomics/mono/issues/309): Исправление отображения чужих билетов времени в трекере
|
||||
|
||||
### 🔧 Улучшения
|
||||
- [#331](https://github.com/coopenomics/mono/issues/331): Настройка системы мониторинга сбоев и ошибок на базе GlitchTIP, Loki, Prometheus
|
||||
- [#327](https://github.com/coopenomics/mono/issues/327): Пользовательская документация по интерфейсам цифрового кооператива
|
||||
- [#325](https://github.com/coopenomics/mono/issues/325): Документирование смарт-контракта программы "Благорост"
|
||||
- [#308](https://github.com/coopenomics/mono/issues/308): Улучшение отображения рабочих столов в магазине приложений
|
||||
- [#307](https://github.com/coopenomics/mono/issues/307): Объединение настроек контракта с нативными настройками приложения
|
||||
- [#305](https://github.com/coopenomics/mono/issues/305): Объединение полей title и description в проекте
|
||||
- [#304](https://github.com/coopenomics/mono/issues/304): Доменная модель контракта капитализации на бэкенде
|
||||
- [#303](https://github.com/coopenomics/mono/issues/303): Пересмотр архитектуры парсера и формирования локальной истории
|
||||
- [#302](https://github.com/coopenomics/mono/issues/302): Рефакторинг архитектуры, внедрение двухконтурной шины данных и обработки микрофорков
|
||||
- [#301](https://github.com/coopenomics/mono/issues/301): Доработка и отладка контракта "Капитализация РИД" v0.2
|
||||
- [#222](https://github.com/coopenomics/mono/issues/222): Внедрение метода Водянова для распределения пула премий по программе "Благорост"
|
||||
- [#212](https://github.com/coopenomics/mono/issues/212): Снижение точности валютных значений до двух знаков после запятой в документах
|
||||
|
||||
#releases
|
||||
|
||||
---
|
||||
|
||||
# v2025.12.28
|
||||
|
||||
В этой версии представлен прототип трекера результатов интеллектуальной деятельности, реализован мост в 1С, обновлен интерфейс и существенно повышена стабильность системы.
|
||||
|
||||
---
|
||||
|
||||
### ✨ Новые функции
|
||||
- [#332](https://github.com/coopenomics/mono/issues/332): Прототип конструктора требований дополнительных документов при регистрации
|
||||
- [#330](https://github.com/coopenomics/mono/issues/330): Прототип моста в 1С:Бухгалтерию для передачи документов и проводок
|
||||
- [#329](https://github.com/coopenomics/mono/issues/329): Интеграция и тестирование LMS TUTOR для образовательных задач
|
||||
- [#328](https://github.com/coopenomics/mono/issues/328): Размещение прототипов мульти-лендингов на цифровой-кооператив.рф и coopenomics.world
|
||||
- [#326](https://github.com/coopenomics/mono/issues/326): Минимальный интерфейс трекера результатов интеллектуальной деятельности
|
||||
- [#324](https://github.com/coopenomics/mono/issues/324): Смарт-контракт генерации и капитализации результатов интеллектуальной деятельности ("Благорост")
|
||||
- [#322](https://github.com/coopenomics/mono/issues/322): Поставка обновлений ПО с нулевым даунтаймом по blue-green стратегии
|
||||
- [#321](https://github.com/coopenomics/mono/issues/321): Внедрение системы проводок по фондам для контрактов
|
||||
- [#319](https://github.com/coopenomics/mono/issues/319): Палитра команд и быстрый доступ к страницам рабочих столов (cmk+k)
|
||||
- [#316](https://github.com/coopenomics/mono/issues/316): Переход рабочего стола на GraphQL SDK
|
||||
- [#314](https://github.com/coopenomics/mono/issues/314): Развёртывание GlitchTip для мониторинга ошибок
|
||||
- [#306](https://github.com/coopenomics/mono/issues/306): Модуль запросов и мутаций для контракта капитализации
|
||||
|
||||
### 🐛 Исправления ошибок
|
||||
- [#312](https://github.com/coopenomics/mono/issues/312): Исправление подписки на изменение статуса коммитов
|
||||
- [#309](https://github.com/coopenomics/mono/issues/309): Исправление отображения чужих билетов времени в трекере
|
||||
|
||||
### 🔧 Улучшения
|
||||
- [#331](https://github.com/coopenomics/mono/issues/331): Настройка системы мониторинга сбоев и ошибок на базе GlitchTIP, Loki, Prometheus
|
||||
- [#327](https://github.com/coopenomics/mono/issues/327): Пользовательская документация по интерфейсам цифрового кооператива
|
||||
- [#325](https://github.com/coopenomics/mono/issues/325): Документирование смарт-контракта программы "Благорост"
|
||||
- [#308](https://github.com/coopenomics/mono/issues/308): Улучшение отображения рабочих столов в магазине приложений
|
||||
- [#307](https://github.com/coopenomics/mono/issues/307): Объединение настроек контракта с нативными настройками приложения
|
||||
- [#305](https://github.com/coopenomics/mono/issues/305): Объединение полей title и description в проекте
|
||||
- [#304](https://github.com/coopenomics/mono/issues/304): Доменная модель контракта капитализации на бэкенде
|
||||
- [#303](https://github.com/coopenomics/mono/issues/303): Пересмотр архитектуры парсера и формирования локальной истории
|
||||
- [#302](https://github.com/coopenomics/mono/issues/302): Рефакторинг архитектуры, внедрение двухконтурной шины данных и обработки микрофорков
|
||||
- [#301](https://github.com/coopenomics/mono/issues/301): Доработка и отладка контракта "Капитализация РИД" v0.2
|
||||
- [#222](https://github.com/coopenomics/mono/issues/222): Внедрение метода Водянова для распределения пула премий по программе "Благорост"
|
||||
- [#212](https://github.com/coopenomics/mono/issues/212): Снижение точности валютных значений до двух знаков после запятой в документах
|
||||
|
||||
#releases
|
||||
|
||||
---
|
||||
|
||||
# v2025.12.28
|
||||
|
||||
В этом релизе реализован смарт-контракт генерации и капитализации результатов интеллектуальной деятельности, завершена интеграция с учётными системами, улучшены интерфейсы и документация. Подробнее о контракте: https://coopenomics.world/contracts/group__public__capital.html
|
||||
|
||||
✨ Новые функции
|
||||
- [#324](https://github.com/coopenomics/mono/issues/324): Реализован смарт-контракт генерации и капитализации результатов интеллектуальной деятельности ("Благорост")
|
||||
- [#301](https://github.com/coopenomics/mono/issues/301): Контракт "Капитализация РИД" v0.2
|
||||
- [#330](https://github.com/coopenomics/mono/issues/330): Прототип моста в 1С:Бухгалтерию: выгрузка документов и проводки по счетам
|
||||
- [#322](https://github.com/coopenomics/mono/issues/322): Обновления ПО с нулевым даунтаймом по blue-green стратегии
|
||||
- [#319](https://github.com/coopenomics/mono/issues/319): Палитра команд и быстрый доступ к страницам рабочих столов (cmk+k)
|
||||
- [#308](https://github.com/coopenomics/mono/issues/308): Магазин приложений с поддержкой подключения нескольких рабочих столов одним приложением
|
||||
- [#326](https://github.com/coopenomics/mono/issues/326): Минимальный интерфейс трекера результатов интеллектуальной деятельности по программе "Благорост"
|
||||
- [#332](https://github.com/coopenomics/mono/issues/332): Прототип конструктора требований дополнительных документов при регистрации
|
||||
|
||||
🐛 Исправления ошибок
|
||||
- [#312](https://github.com/coopenomics/mono/issues/312): Исправлена ошибка со статусом коммитов — подписка теперь работает корректно
|
||||
- [#309](https://github.com/coopenomics/mono/issues/309): Исправлен баг с отображением чужих билетов времени в трекере
|
||||
|
||||
🔧 Улучшения
|
||||
- [#325](https://github.com/coopenomics/mono/issues/325): Документирован смарт-контракт программы "Благорост"
|
||||
- [#327](https://github.com/coopenomics/mono/issues/327): Подготовлена пользовательская документация цифрового кооператива по интерфейсам
|
||||
- [#329](https://github.com/coopenomics/mono/issues/329): Интеграция и тестирование образовательной платформы LMS TUTOR на Wordpress
|
||||
- [#328](https://github.com/coopenomics/mono/issues/328): Размещены прототипы мульти-лендингов на цифровой-кооператив.рф и coopenomics.world
|
||||
- [#321](https://github.com/coopenomics/mono/issues/321): Встроена система проводок по фондам и интеграция с контрактами
|
||||
- [#318](https://github.com/coopenomics/mono/issues/318): Настроены Loki & Grafana для выгрузки логов из контейнеров
|
||||
- [#316](https://github.com/coopenomics/mono/issues/316): Завершён переход рабочего стола на GraphQL SDK
|
||||
- [#314](https://github.com/coopenomics/mono/issues/314): Развёрнут GlitchTip как альтернатива Sentry
|
||||
- [#307](https://github.com/coopenomics/mono/issues/307): Интеграция настроек контракта с нативными настройками приложения, поддержка импорта после конфигурации
|
||||
- [#306](https://github.com/coopenomics/mono/issues/306): Собран модуль запросов и мутаций контракта капитализации
|
||||
- [#305](https://github.com/coopenomics/mono/issues/305): Упрощена структура проекта — title & description объединены в одно поле
|
||||
- [#304](https://github.com/coopenomics/mono/issues/304): Реализована доменная модель контракта капитализации на бэкенде с поддержкой микрофорков
|
||||
- [#303](https://github.com/coopenomics/mono/issues/303): Пересмотрена архитектура парсера и формирования локальной истории
|
||||
- [#302](https://github.com/coopenomics/mono/issues/302): Рефакторинг архитектуры, реализована двухконтурная шина данных и обработка микрофорков
|
||||
- [#212](https://github.com/coopenomics/mono/issues/212): Уменьшена точность валютных значений в документах с четырёх до двух знаков
|
||||
|
||||
#releases
|
||||
|
||||
---
|
||||
|
||||
# v2025.9.1
|
||||
|
||||
В системе Кооперативной Экономики развернут смарт-контракт CAPITAL v0.2 для генерации и капитализации результатов интеллектуальной деятельности. Контракт описывает и обеспечивает:
|
||||
- бизнес-процесс производства результатов интеллектуальной деятельности в кооперативах создателями, авторами, инвесторами, координаторами, мастерами и собственниками имущества в кооперации на проектах;
|
||||
- приём результатов интеллектуальной деятельности в качестве паевых взносов в кооператив;
|
||||
- распределение потока членских взносов среди вкладчиков;
|
||||
- капитализацию результатов интеллектуальной деятельности новыми результатами по модели золотого сечения;
|
||||
- оценку вкладов авторов и создателей по методу Водянова;
|
||||
|
||||
В основе математической модели контракта лежит принцип распределения справедливой выгоды между вкладчиками согласно концепции "Общественно-полезного времени", разработанной в рамках теорий Кузнецова и академика Глушкова при работе над проектом общегосударственной автоматизированной системы учета и обработки информации (ОГАС).
|
||||
|
||||
Подробнее в документации: https://coopenomics.world/contracts/group__public__capital.html
|
||||
|
||||
✨ Новые функции
|
||||
- [#301](https://github.com/coopenomics/mono/issues/301): Реализация контракта "Капитализация" v0.2: регистрация вкладчиков, создание и управление проектами, поддержка инвестиций, ссуд, членских взносов, проведение голосований, расчет капитализации, интеграция с внешними контрактами, поддержка импорта данных.
|
||||
|
||||
#releases
|
||||
|
||||
---
|
||||
|
||||
# v2025.7.1-1
|
||||
|
||||
В этом релизе реализованы механизмы возврата паевого взноса пайщика и инструменты управления этим процессом для членов совета. Также внесены визуальные доработки интерфейсов для улучшения восприятия информации.
|
||||
|
||||
✨ Новые функции
|
||||
- [#276](https://github.com/coopenomics/mono/issues/276): Реализовать путь возврата паевого взноса из кошелька пайщика
|
||||
- [#281](https://github.com/coopenomics/mono/issues/281): Генерация заявления на возврат паевого взноса
|
||||
- [#282](https://github.com/coopenomics/mono/issues/282): Генерация решения совета на возврат паевого взноса
|
||||
- [#278](https://github.com/coopenomics/mono/issues/278): Введение методов управления возвратами паевых взносов в контроллере
|
||||
- [#279](https://github.com/coopenomics/mono/issues/279): Отобразить исходящие платежи с кнопками управления в реестре платежей для совета
|
||||
|
||||
🐛 Исправления ошибок
|
||||
- [#286](https://github.com/coopenomics/mono/issues/286): Поправить ошибки склонений времени
|
||||
- [#262](https://github.com/coopenomics/mono/issues/262): Баг: рабочий стол совета включается, однако, страница всегда открывается со стола пайщика
|
||||
- [#261](https://github.com/coopenomics/mono/issues/261): Баг: первая загрузка переадресует на главную страницу всегда - прямой переход на собрание становится недоступен.
|
||||
|
||||
🔧 Улучшения
|
||||
- [#285](https://github.com/coopenomics/mono/issues/285): Корректировка дизайна кошелька, профиля, повестки совета, реестра документов, реестра платежей
|
||||
- [#284](https://github.com/coopenomics/mono/issues/284): Разместить кошелек на главную вместо профиля
|
||||
- [#283](https://github.com/coopenomics/mono/issues/283): Ввести лоадер на переходе между рабочими столами
|
||||
- [#280](https://github.com/coopenomics/mono/issues/280): Мигрировать имеющиеся данные о входящих платежах в новую модель
|
||||
- [#277](https://github.com/coopenomics/mono/issues/277): Рефакторинг модуля платежей: переход на унифицированную модель входящего и исходящего платежа
|
||||
|
||||
#releases
|
||||
|
||||
---
|
||||
|
||||
# v2025.6.14
|
||||
|
||||
Разработан модуль для проведения очередных общих собраний пайщиков. Исправлены баги, внесены улучшения пользовательского интерфейса.
|
||||
|
||||
✨ Новые функции
|
||||
- [#264](https://github.com/coopenomics/mono/issues/264): Разработан смарт-контракт общего собрания пайщиков (meet)
|
||||
- [#263](https://github.com/coopenomics/mono/issues/263): Реализован модуль оповещений на электронные почты по жизненному циклу общего собрания пайщиков
|
||||
- [#273](https://github.com/coopenomics/mono/issues/273): Встроены реальные шаблоны документов общего собрания
|
||||
- [#268](https://github.com/coopenomics/mono/issues/268): Добавлена страница результатов общего собрания
|
||||
- [#267](https://github.com/coopenomics/mono/issues/267): Добавлена страница просмотра и скачивания бюллетеней и уведомлений по собранию
|
||||
- [#270](https://github.com/coopenomics/mono/issues/270): Ссылка в оповещении ведет на страницу собрания с документом-уведомлением для подписи
|
||||
|
||||
🐛 Исправления ошибок
|
||||
- [#262](https://github.com/coopenomics/mono/issues/262): Исправлено некорректное открытие рабочего стола совета
|
||||
- [#261](https://github.com/coopenomics/mono/issues/261): Исправлена ошибка с редиректом при первой загрузке и прямом переходе на собрание
|
||||
- [#259](https://github.com/coopenomics/mono/issues/259): Исправлены отступы в мобильной карточке пайщика
|
||||
- [#258](https://github.com/coopenomics/mono/issues/258): Убран hover-эффект на документе и пайщике в таблице
|
||||
|
||||
🔧 Улучшения
|
||||
- [#274](https://github.com/coopenomics/mono/issues/274): Настроена рассылка оповещений на почту при получении решения о проведении собрания
|
||||
- [#272](https://github.com/coopenomics/mono/issues/272): Подписанные уведомления сохраняются и извлекаются из реестра по Graph-QL
|
||||
- [#271](https://github.com/coopenomics/mono/issues/271): Ведется подсчет количества пайщиков в каждом кооперативе при добавлении и удалении
|
||||
- [#260](https://github.com/coopenomics/mono/issues/260): Введен счетчик количества пайщиков в кооперативе на контракте регистратора
|
||||
- [#256](https://github.com/coopenomics/mono/issues/256): Отображается статус членства каждого пайщика
|
||||
- [#255](https://github.com/coopenomics/mono/issues/255): В разделе Платежи отображается ФИО/Наименование плательщика
|
||||
- [#269](https://github.com/coopenomics/mono/issues/269): Реализована рассылка уведомлений перед началом собрания
|
||||
- [#265](https://github.com/coopenomics/mono/issues/265): Проведена отладка и тестирование процесса общего собрания пайщиков
|
||||
- [#266](https://github.com/coopenomics/mono/issues/266): Протестированы все процессы общего собрания
|
||||
|
||||
#releases
|
||||
|
||||
---
|
||||
|
||||
# v2025.5.14
|
||||
|
||||
В этом релизе реализован новый стандарт передачи и хранения документов по блокчейну с поддержкой неограниченного количества подписей и их валидацией. Также внесены улучшения в интерфейс и исправлены ошибки.
|
||||
|
||||
✨ Новые функции
|
||||
- [#252](https://github.com/coopenomics/mono/issues/252): Внедрение обновленного стандарта хранения и передачи документов по блокчейну
|
||||
- [#251](https://github.com/coopenomics/mono/issues/251): Реализация версионированного мигратора данных для контроллера кооператива
|
||||
- [#244](https://github.com/coopenomics/mono/issues/244): Обновление стандарта сборки документов и переход на хэш-идентификаторы
|
||||
|
||||
🐛 Исправления ошибок
|
||||
- [#249](https://github.com/coopenomics/mono/issues/249): Исправлена спутанная маршрутизация между рабочими столами кооперативов
|
||||
- [#253](https://github.com/coopenomics/mono/issues/253): Исправлена избыточная точность суммы оплаты в заявлении на вступление
|
||||
|
||||
🔧 Улучшения
|
||||
- [#259](https://github.com/coopenomics/mono/issues/259): Исправлены отступы в мобильной карточке пайщика на странице пайщиков
|
||||
- [#258](https://github.com/coopenomics/mono/issues/258): Удалён hover-эффект для документов и пайщиков в таблице
|
||||
- [#257](https://github.com/coopenomics/mono/issues/257): Добавлено сохранение светлой/тёмной темы в localStorage и восстановление при загрузке страницы
|
||||
- [#256](https://github.com/coopenomics/mono/issues/256): Отображение статуса членства каждого пайщика в разделе "Пайщики"
|
||||
- [#255](https://github.com/coopenomics/mono/issues/255): Отображение ФИО/Наименования плательщика в разделе "Платежи"
|
||||
|
||||
#releases
|
||||
|
||||
---
|
||||
|
||||
# v2025.5.2
|
||||
|
||||
В этом релизе рабочие столы переведены на серверный рендеринг, что улучшает стабильность развертывания и упрощает автоматизацию поставки ПО. Данный релиз является подготовительным к переходу на новый стандарт цифровых документов на платформе.
|
||||
|
||||
+19
-13
@@ -1,4 +1,4 @@
|
||||
FROM node:20-alpine AS builder
|
||||
FROM node:20-slim AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@@ -12,23 +12,29 @@ RUN npm install -g pnpm lerna
|
||||
# Используем версию pnpm, совместимую с существующим lock-файлом
|
||||
RUN pnpm install
|
||||
|
||||
# Установка системных зависимостей для WeasyPrint
|
||||
RUN apk add --no-cache \
|
||||
# Установка системных зависимостей для WeasyPrint и диагностических утилит (Debian/Ubuntu версии)
|
||||
RUN apt-get update && apt-get install -y \
|
||||
python3 \
|
||||
py3-pip \
|
||||
python3-pip \
|
||||
python3-venv \
|
||||
gcc \
|
||||
musl-dev \
|
||||
python3-dev \
|
||||
pango \
|
||||
zlib-dev \
|
||||
jpeg-dev \
|
||||
openjpeg-dev \
|
||||
g++ \
|
||||
python3-dev \
|
||||
libpango-1.0-0 \
|
||||
libpangoft2-1.0-0 \
|
||||
libpangocairo-1.0-0 \
|
||||
libcairo2 \
|
||||
libcairo2-dev \
|
||||
libffi-dev \
|
||||
harfbuzz-subset \
|
||||
shared-mime-info \
|
||||
zlib1g-dev \
|
||||
libjpeg-dev \
|
||||
libopenjp2-7-dev \
|
||||
procps \
|
||||
wget \
|
||||
&& python3 -m venv /venv \
|
||||
&& /venv/bin/pip install WeasyPrint==62.3 \
|
||||
&& rm -rf /var/cache/*
|
||||
&& /venv/bin/pip install WeasyPrint==67 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Сборка всех компонентов
|
||||
RUN lerna run build
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# MONO
|
||||
# Цифровой Кооператив
|
||||
|
||||
Система управления взаимоотношениями в кооперативе. Включает в себя полный комплект программного обеспечения для подключения к платформе "Кооперативная Экономика" и управления взаимоотношениями с пайщиками в кооперативе и самим кооперативом на основе смарт-контрактов и простой электронной подписи.
|
||||
|
||||
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
# 📋 Резюме создания компонентов Notifications
|
||||
|
||||
## ✅ Что создано
|
||||
|
||||
### 1. 📚 Библиотека `@monocoop/notifications`
|
||||
**Расположение:** `components/notifications/`
|
||||
|
||||
**Функции:**
|
||||
- ✅ Типизированные интерфейсы для workflow с Zod
|
||||
- ✅ Builder паттерн для создания workflow
|
||||
- ✅ Базовые шаблоны для email, in-app, push, SMS
|
||||
- ✅ Автоматическая конвертация Zod схем в JSON Schema для Novu
|
||||
- ✅ Экспорт всех workflow для использования в других пакетах
|
||||
|
||||
**Структура:**
|
||||
```
|
||||
src/
|
||||
├── types/ # Базовые типы и интерфейсы
|
||||
├── base/ # Утилиты и настройки по умолчанию
|
||||
├── workflows/ # Папки с workflow
|
||||
│ └── welcome/ # Пример приветственного workflow
|
||||
└── index.ts # Главный экспорт
|
||||
```
|
||||
|
||||
### 2. 🚀 NestJS приложение `@monocoop/notificator2`
|
||||
**Расположение:** `components/notificator2/`
|
||||
|
||||
**Функции:**
|
||||
- ✅ Автоматический upsert всех workflow в Novu при запуске
|
||||
- ✅ RESTful API для триггера уведомлений
|
||||
- ✅ Типизированная валидация payload
|
||||
- ✅ Health check endpoints
|
||||
- ✅ Использует библиотеку notifications для типов
|
||||
|
||||
**API Endpoints:**
|
||||
- `GET /api/notifications/health` - Health check
|
||||
- `GET /api/notifications/workflows` - Список workflow
|
||||
- `POST /api/notifications/trigger` - Универсальный триггер
|
||||
- `POST /api/notifications/trigger/welcome` - Триггер welcome workflow
|
||||
- `POST /api/notifications/workflows/upsert-all` - Принудительный upsert
|
||||
|
||||
## 🔧 Как использовать
|
||||
|
||||
### 1. Настройка библиотеки notifications
|
||||
```bash
|
||||
cd components/notifications
|
||||
pnpm install # или npm install
|
||||
pnpm build # для компиляции TypeScript
|
||||
```
|
||||
|
||||
### 2. Настройка notificator2
|
||||
```bash
|
||||
cd components/notificator2
|
||||
pnpm install # или npm install
|
||||
|
||||
# Настройка окружения
|
||||
cp .env.example .env
|
||||
# Добавить NOVU_API_KEY в .env
|
||||
|
||||
# Запуск
|
||||
pnpm start:dev
|
||||
```
|
||||
|
||||
### 3. Добавление нового workflow
|
||||
|
||||
**В библиотеке notifications:**
|
||||
```typescript
|
||||
// components/notifications/src/workflows/order/order-workflow.ts
|
||||
export const orderWorkflow = WorkflowBuilder
|
||||
.create<OrderPayload>()
|
||||
.name('Order Confirmation')
|
||||
.workflowId('order-confirmation')
|
||||
.payloadSchema(orderPayloadSchema)
|
||||
.addSteps([...])
|
||||
.build();
|
||||
```
|
||||
|
||||
**Регистрация в index.ts:**
|
||||
```typescript
|
||||
// components/notifications/src/workflows/index.ts
|
||||
import { orderWorkflow } from './order';
|
||||
|
||||
export const allWorkflows = [
|
||||
welcomeWorkflow,
|
||||
orderWorkflow, // ← добавить новый workflow
|
||||
];
|
||||
```
|
||||
|
||||
### 4. Использование API
|
||||
|
||||
**Триггер welcome workflow:**
|
||||
```bash
|
||||
curl -X POST http://localhost:3000/api/notifications/trigger/welcome \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"subscriberId": "user-123",
|
||||
"email": "user@example.com",
|
||||
"payload": {
|
||||
"userName": "Иван Иванов",
|
||||
"userEmail": "user@example.com",
|
||||
"age": 25
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
## 🎯 Преимущества архитектуры
|
||||
|
||||
1. **Типобезопасность** - Zod схемы обеспечивают валидацию на уровне TypeScript и runtime
|
||||
2. **Разделение ответственности** - Библиотека содержит только типы, сервер - логику
|
||||
3. **Расширяемость** - Легко добавлять новые workflow
|
||||
4. **Автоматизация** - Workflow автоматически синхронизируются с Novu
|
||||
5. **Переиспользование** - Типы можно использовать в других частях системы
|
||||
|
||||
## 🔄 Workflow при запуске
|
||||
|
||||
1. **Запуск notificator2** →
|
||||
2. **Чтение всех workflow из библиотеки** →
|
||||
3. **Проверка существования в Novu** →
|
||||
4. **Создание/обновление workflow** →
|
||||
5. **Готов к приёму запросов на триггеры**
|
||||
|
||||
## 📁 Структура как в testFramework2.ts
|
||||
|
||||
Вся логика из `testFramework2.ts` была перенесена в structured архитектуру:
|
||||
- ✅ `buildWorkflowData` → `WorkflowBuilder`
|
||||
- ✅ `baseSteps` → `createEmailStep`, `createInAppStep`, etc.
|
||||
- ✅ `upsertWorkflow` → `NovuService.upsertWorkflow`
|
||||
- ✅ `triggerWorkflow` → `NovuService.triggerWorkflow`
|
||||
- ✅ Типизация payload → Zod схемы + TypeScript типы
|
||||
Vendored
BIN
Binary file not shown.
@@ -8,3 +8,8 @@ SERVER_URL=http://127.0.0.1:2998
|
||||
MONGO_URI=mongodb://127.0.0.1:27017/cooperative-x
|
||||
SIMPLE_EXPLORER_API=http://localhost:4000
|
||||
SKIP_BLOCK_FETCH=TRUE
|
||||
POSTGRES_HOST=127.0.0.1
|
||||
POSTGRES_PORT=5432
|
||||
POSTGRES_USERNAME=postgres
|
||||
POSTGRES_PASSWORD=postgres
|
||||
POSTGRES_DATABASE=voskhod
|
||||
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"configurations": [
|
||||
{
|
||||
"includePath": [
|
||||
"${workspaceFolder}/**",
|
||||
"${workspaceFolder}/lib/**",
|
||||
"${workspaceFolder}/lib/eosiolib/contracts/**",
|
||||
"${workspaceFolder}/lib/eosiolib/core/**"
|
||||
],
|
||||
"defines": [
|
||||
"_DEBUG",
|
||||
"UNICODE",
|
||||
"_UNICODE",
|
||||
"int128_t=__int128_t",
|
||||
"uint128_t=__uint128_t"
|
||||
],
|
||||
"cStandard": "c17",
|
||||
"cppStandard": "c++20"
|
||||
}
|
||||
],
|
||||
"version": 4
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
{
|
||||
// Enable the ESlint flat config support
|
||||
"prettier.enable": false,
|
||||
"editor.formatOnSave": false,
|
||||
// Auto fix
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": "explicit"
|
||||
// "source.fixAll": "explicit",
|
||||
// "source.organizeImports": "never"
|
||||
},
|
||||
// Silent the stylistic rules in you IDE, but still auto fix them
|
||||
"eslint.rules.customizations": [
|
||||
{
|
||||
"rule": "style/*",
|
||||
"severity": "off"
|
||||
},
|
||||
{
|
||||
"rule": "*-indent",
|
||||
"severity": "off"
|
||||
},
|
||||
{
|
||||
"rule": "*-spacing",
|
||||
"severity": "off"
|
||||
},
|
||||
{
|
||||
"rule": "*-spaces",
|
||||
"severity": "off"
|
||||
},
|
||||
{
|
||||
"rule": "*-order",
|
||||
"severity": "off"
|
||||
},
|
||||
{
|
||||
"rule": "*-dangle",
|
||||
"severity": "off"
|
||||
},
|
||||
{
|
||||
"rule": "*-newline",
|
||||
"severity": "off"
|
||||
},
|
||||
{
|
||||
"rule": "*quotes",
|
||||
"severity": "off"
|
||||
},
|
||||
{
|
||||
"rule": "*semi",
|
||||
"severity": "off"
|
||||
}
|
||||
],
|
||||
// Enable eslint for all supported languages
|
||||
"eslint.validate": [
|
||||
"javascript",
|
||||
"javascriptreact",
|
||||
"typescript",
|
||||
"typescriptreact",
|
||||
"vue",
|
||||
"html",
|
||||
"markdown",
|
||||
"json",
|
||||
"jsonc",
|
||||
"yaml"
|
||||
],
|
||||
// "[cpp]": {
|
||||
// "editor.defaultFormatter": "ms-vscode.cpptools",
|
||||
// },
|
||||
// "C_Cpp.vcFormat.indent.withinParentheses": "indent",
|
||||
"C_Cpp.errorSquiggles": "disabled"
|
||||
}
|
||||
@@ -9,7 +9,7 @@ export default antfu(
|
||||
},
|
||||
{
|
||||
rules: {
|
||||
'no-unused-vars': 'warn',
|
||||
'no-unused-vars': 'off',
|
||||
'unused-imports/no-unused-vars': 'off',
|
||||
'no-console': 'off',
|
||||
'unicorn/prefer-number-properties': 'off',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@coopenomics/boot",
|
||||
"type": "module",
|
||||
"version": "2025.5.5-2",
|
||||
"version": "2026.2.22-alpha-4",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@9.0.6",
|
||||
"description": "",
|
||||
@@ -15,12 +15,18 @@
|
||||
"cleos": "esno src/index.ts cleos",
|
||||
"unlock": "esno src/index.ts unlock",
|
||||
"boot": "esno src/index.ts boot",
|
||||
"clear": "esno src/index.ts clear",
|
||||
"boot:clean": "esno src/index.ts boot:clean",
|
||||
"boot:extra": "esno src/index.ts boot:extra",
|
||||
"reboot:clean": "cd components/boot/scripts && bash clean_reboot.sh",
|
||||
"reboot:extra": "cd components/boot/scripts && bash extra_reboot.sh",
|
||||
"create-coop": "esno src/index.ts create-coop",
|
||||
"clear": "./scripts/clear.sh",
|
||||
"start": "./scripts/restart.sh",
|
||||
"stop": "./scripts/stop.sh"
|
||||
},
|
||||
"dependencies": {
|
||||
"@coopenomics/factory": "workspace:*",
|
||||
"@types/pg": "^8.16.0",
|
||||
"axios": "^1.6.8",
|
||||
"chai": "^5.1.2",
|
||||
"commander": "^12.1.0",
|
||||
@@ -32,7 +38,8 @@
|
||||
"eosjs-ecc": "^4.0.7",
|
||||
"execa": "^9.5.2",
|
||||
"mocha": "^10.7.3",
|
||||
"mongoose": "^8.10.0"
|
||||
"mongoose": "^8.10.0",
|
||||
"pg": "^8.16.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@antfu/eslint-config": "^2.18.0",
|
||||
|
||||
Executable
+47
@@ -0,0 +1,47 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Останавливаем и удаляем контейнеры вместе с volumes
|
||||
echo "Останавливаем и удаляем контейнеры с volumes..."
|
||||
docker compose down -v mongo postgres cooparser || true
|
||||
|
||||
# Останавливаем blockchain контейнер перед удалением данных
|
||||
echo "Останавливаем blockchain контейнер..."
|
||||
docker compose stop node || true
|
||||
|
||||
# Удаляем blockchain data
|
||||
echo "Удаляем blockchain data..."
|
||||
# sudo chmod -R 755 ../blockchain-data/ 2>/dev/null || true
|
||||
sudo rm -rf ../blockchain-data/
|
||||
|
||||
# Пересоздаем и запускаем базы данных
|
||||
echo "Пересоздаем и запускаем базы данных..."
|
||||
docker compose up -d mongo postgres
|
||||
|
||||
# Ждем готовности MongoDB
|
||||
echo "Ждем готовности MongoDB..."
|
||||
until docker exec mongo mongosh --eval "db.adminCommand('ping')" --quiet > /dev/null 2>&1; do
|
||||
echo "MongoDB еще не готов, ждем..."
|
||||
sleep 2
|
||||
done
|
||||
echo "MongoDB готов!"
|
||||
|
||||
# Ждем готовности PostgreSQL
|
||||
echo "Ждем готовности PostgreSQL..."
|
||||
until docker exec postgres pg_isready -U postgres -d voskhod > /dev/null 2>&1; do
|
||||
echo "PostgreSQL еще не готов, ждем..."
|
||||
sleep 2
|
||||
done
|
||||
echo "PostgreSQL готов!"
|
||||
|
||||
# Запускаем boot процесс
|
||||
echo "Запускаем boot процесс..."
|
||||
pnpm run boot:clean
|
||||
|
||||
# Запускаем parser
|
||||
echo "Запускаем parser..."
|
||||
docker compose up -d cooparser
|
||||
|
||||
echo "Запускаем контроллер..."
|
||||
docker compose restart coopback || true
|
||||
|
||||
echo "Перезапуск завершен!"
|
||||
Executable
+34
@@ -0,0 +1,34 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Останавливаем и удаляем контейнеры вместе с volumes
|
||||
echo "Останавливаем и удаляем контейнеры с volumes..."
|
||||
docker compose down -v mongo postgres cooparser || true
|
||||
|
||||
# Останавливаем blockchain контейнер перед удалением данных
|
||||
echo "Останавливаем blockchain контейнер..."
|
||||
docker compose stop node || true
|
||||
|
||||
# Удаляем blockchain data
|
||||
echo "Удаляем blockchain data..."
|
||||
# sudo chmod -R 755 ../blockchain-data/ 2>/dev/null || true
|
||||
sudo rm -rf ../blockchain-data/
|
||||
|
||||
# Пересоздаем и запускаем базы данных
|
||||
echo "Пересоздаем и запускаем базы данных..."
|
||||
docker compose up -d mongo postgres
|
||||
|
||||
# Ждем готовности MongoDB
|
||||
echo "Ждем готовности MongoDB..."
|
||||
until docker exec mongo mongosh --eval "db.adminCommand('ping')" --quiet > /dev/null 2>&1; do
|
||||
echo "MongoDB еще не готов, ждем..."
|
||||
sleep 2
|
||||
done
|
||||
echo "MongoDB готов!"
|
||||
|
||||
# Ждем готовности PostgreSQL
|
||||
echo "Ждем готовности PostgreSQL..."
|
||||
until docker exec postgres pg_isready -U postgres -d voskhod > /dev/null 2>&1; do
|
||||
echo "PostgreSQL еще не готов, ждем..."
|
||||
sleep 2
|
||||
done
|
||||
echo "PostgreSQL готов!"
|
||||
Executable
+51
@@ -0,0 +1,51 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Останавливаем контроллер перед очисткой данных
|
||||
echo "Останавливаем контроллер..."
|
||||
docker compose down coopback || true
|
||||
|
||||
# Останавливаем и удаляем контейнеры вместе с volumes
|
||||
echo "Останавливаем и удаляем контейнеры с volumes..."
|
||||
docker compose down -v mongo postgres cooparser || true
|
||||
|
||||
# Останавливаем blockchain контейнер перед удалением данных
|
||||
echo "Останавливаем blockchain контейнер..."
|
||||
docker compose stop node || true
|
||||
|
||||
# Удаляем blockchain data
|
||||
echo "Удаляем blockchain data..."
|
||||
# sudo chmod -R 755 ../blockchain-data/ 2>/dev/null || true
|
||||
sudo rm -rf ../blockchain-data/
|
||||
|
||||
# Пересоздаем и запускаем базы данных
|
||||
echo "Пересоздаем и запускаем базы данных..."
|
||||
docker compose up -d mongo postgres
|
||||
|
||||
# Ждем готовности MongoDB
|
||||
echo "Ждем готовности MongoDB..."
|
||||
until docker exec mongo mongosh --eval "db.adminCommand('ping')" --quiet > /dev/null 2>&1; do
|
||||
echo "MongoDB еще не готов, ждем..."
|
||||
sleep 2
|
||||
done
|
||||
echo "MongoDB готов!"
|
||||
|
||||
# Ждем готовности PostgreSQL
|
||||
echo "Ждем готовности PostgreSQL..."
|
||||
until docker exec postgres pg_isready -U postgres -d voskhod > /dev/null 2>&1; do
|
||||
echo "PostgreSQL еще не готов, ждем..."
|
||||
sleep 2
|
||||
done
|
||||
echo "PostgreSQL готов!"
|
||||
|
||||
# Запускаем boot процесс
|
||||
echo "Запускаем boot процесс..."
|
||||
pnpm run boot:extra
|
||||
|
||||
# Запускаем parser
|
||||
echo "Запускаем parser..."
|
||||
docker compose up -d cooparser
|
||||
|
||||
echo "Запускаем контроллер..."
|
||||
docker compose up -d coopback
|
||||
|
||||
echo "Перезапуск завершен!"
|
||||
Executable
+47
@@ -0,0 +1,47 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Останавливаем и удаляем контейнеры вместе с volumes
|
||||
echo "Останавливаем и удаляем контейнеры с volumes..."
|
||||
docker compose down -v mongo postgres cooparser || true
|
||||
|
||||
# Останавливаем blockchain контейнер перед удалением данных
|
||||
echo "Останавливаем blockchain контейнер..."
|
||||
docker compose stop node || true
|
||||
|
||||
# Удаляем blockchain data
|
||||
echo "Удаляем blockchain data..."
|
||||
# sudo chmod -R 755 ../blockchain-data/ 2>/dev/null || true
|
||||
sudo rm -rf ../blockchain-data/
|
||||
|
||||
# Пересоздаем и запускаем базы данных
|
||||
echo "Пересоздаем и запускаем базы данных..."
|
||||
docker compose up -d mongo postgres
|
||||
|
||||
# Ждем готовности MongoDB
|
||||
echo "Ждем готовности MongoDB..."
|
||||
until docker exec mongo mongosh --eval "db.adminCommand('ping')" --quiet > /dev/null 2>&1; do
|
||||
echo "MongoDB еще не готов, ждем..."
|
||||
sleep 2
|
||||
done
|
||||
echo "MongoDB готов!"
|
||||
|
||||
# Ждем готовности PostgreSQL
|
||||
echo "Ждем готовности PostgreSQL..."
|
||||
until docker exec postgres pg_isready -U postgres -d voskhod > /dev/null 2>&1; do
|
||||
echo "PostgreSQL еще не готов, ждем..."
|
||||
sleep 2
|
||||
done
|
||||
echo "PostgreSQL готов!"
|
||||
|
||||
# Запускаем boot процесс
|
||||
echo "Запускаем boot процесс..."
|
||||
pnpm run boot
|
||||
|
||||
# Запускаем parser
|
||||
echo "Запускаем parser..."
|
||||
docker compose up -d cooparser
|
||||
|
||||
echo "Запускаем контроллер..."
|
||||
docker compose restart coopback || true
|
||||
|
||||
echo "Перезапуск завершен!"
|
||||
@@ -15,6 +15,7 @@ import EosApi from 'eosjs-api'
|
||||
// @ts-expect-error заменить на antelope
|
||||
import ecc from 'eosjs-ecc'
|
||||
import config from '../configs'
|
||||
import { consoleIt } from '../tests/shared/consoleIt'
|
||||
|
||||
import type { Contract, Feature, Keys, Network } from '../types'
|
||||
|
||||
@@ -43,8 +44,8 @@ export default class Blockchain {
|
||||
this.api = new Api({
|
||||
rpc,
|
||||
signatureProvider,
|
||||
textDecoder: new TextDecoder(),
|
||||
textEncoder: new TextEncoder(),
|
||||
textDecoder: new TextDecoder() as any,
|
||||
textEncoder: new TextEncoder() as any,
|
||||
})
|
||||
this.api.read = await EosApi({ httpEndpoint: res })
|
||||
}
|
||||
@@ -607,10 +608,10 @@ export default class Blockchain {
|
||||
}
|
||||
|
||||
async registerUser(
|
||||
params: RegistratorContract.Actions.RegisterUser.IRegistrerUser,
|
||||
params: RegistratorContract.Actions.RegisterUser.IRegisterUser,
|
||||
) {
|
||||
await this.update_pass_instance()
|
||||
|
||||
console.dir(params, { depth: null })
|
||||
await this.api.transact(
|
||||
{
|
||||
actions: [
|
||||
@@ -623,9 +624,7 @@ export default class Blockchain {
|
||||
permission: 'active',
|
||||
},
|
||||
],
|
||||
data: {
|
||||
...params,
|
||||
},
|
||||
data: params,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -638,9 +637,69 @@ export default class Blockchain {
|
||||
console.log('Новый пользователь: ', params)
|
||||
}
|
||||
|
||||
async addUser(
|
||||
params: RegistratorContract.Actions.AddUser.IAddUser,
|
||||
) {
|
||||
await this.update_pass_instance()
|
||||
console.dir(params, { depth: null })
|
||||
await this.api.transact(
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
account: RegistratorContract.contractName.production,
|
||||
name: RegistratorContract.Actions.AddUser.actionName,
|
||||
authorization: [
|
||||
{
|
||||
actor: params.coopname,
|
||||
permission: 'active',
|
||||
},
|
||||
],
|
||||
data: params,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
blocksBehind: 3,
|
||||
expireSeconds: 30,
|
||||
},
|
||||
)
|
||||
|
||||
console.log('Добавлен пользователь: ', params.username)
|
||||
}
|
||||
|
||||
async changeKey(
|
||||
params: RegistratorContract.Actions.ChangeKey.IChangeKey,
|
||||
) {
|
||||
await this.update_pass_instance()
|
||||
console.dir(params, { depth: null })
|
||||
await this.api.transact(
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
account: RegistratorContract.contractName.production,
|
||||
name: RegistratorContract.Actions.ChangeKey.actionName,
|
||||
authorization: [
|
||||
{
|
||||
actor: params.coopname,
|
||||
permission: 'active',
|
||||
},
|
||||
],
|
||||
data: params,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
blocksBehind: 3,
|
||||
expireSeconds: 30,
|
||||
},
|
||||
)
|
||||
|
||||
console.log('Изменён ключ для пользователя: ', params.username)
|
||||
}
|
||||
|
||||
async transfer(params: TokenContract.Actions.Transfer.ITransfer) {
|
||||
await this.update_pass_instance()
|
||||
|
||||
console.dir(params, { depth: null })
|
||||
const result = await this.api.transact(
|
||||
{
|
||||
actions: [
|
||||
@@ -748,7 +807,7 @@ export default class Blockchain {
|
||||
name: SovietContract.Actions.Decisions.VoteFor.actionName,
|
||||
authorization: [
|
||||
{
|
||||
actor: params.member,
|
||||
actor: params.username,
|
||||
permission: 'active',
|
||||
},
|
||||
],
|
||||
@@ -833,7 +892,7 @@ export default class Blockchain {
|
||||
}
|
||||
|
||||
async ConfirmPayment(
|
||||
params: GatewayContract.Actions.CompleteIncome.ICompeteIncome,
|
||||
params: GatewayContract.Actions.CompleteIncome.ICompleteIncome,
|
||||
) {
|
||||
await this.update_pass_instance()
|
||||
|
||||
@@ -864,38 +923,6 @@ export default class Blockchain {
|
||||
console.log('Заявление на вступление отправлено в совет: ', params)
|
||||
}
|
||||
|
||||
async createBoard(
|
||||
params: SovietContract.Actions.Boards.CreateBoard.ICreateboard,
|
||||
) {
|
||||
await this.update_pass_instance()
|
||||
|
||||
await this.api.transact(
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
account: SovietContract.contractName.production,
|
||||
name: SovietContract.Actions.Boards.CreateBoard.actionName,
|
||||
authorization: [
|
||||
{
|
||||
actor: params.username,
|
||||
permission: 'active',
|
||||
},
|
||||
],
|
||||
data: {
|
||||
...params,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
blocksBehind: 3,
|
||||
expireSeconds: 30,
|
||||
},
|
||||
)
|
||||
|
||||
console.log('Совет создан: ', params)
|
||||
}
|
||||
|
||||
async createDraft(params: DraftContract.Actions.CreateDraft.ICreateDraft) {
|
||||
await this.update_pass_instance()
|
||||
|
||||
@@ -923,7 +950,7 @@ export default class Blockchain {
|
||||
},
|
||||
)
|
||||
|
||||
console.log('Шаблон создан: ', params)
|
||||
console.log('Шаблон создан: ', params.registry_id)
|
||||
}
|
||||
|
||||
async createTranslation(
|
||||
@@ -955,7 +982,7 @@ export default class Blockchain {
|
||||
},
|
||||
)
|
||||
|
||||
console.log('Перевод создан: ', params)
|
||||
console.log('Перевод создан: ', params.registry_id)
|
||||
}
|
||||
|
||||
async createProgram(
|
||||
@@ -989,4 +1016,66 @@ export default class Blockchain {
|
||||
|
||||
console.log('Программа установлена: ', params)
|
||||
}
|
||||
|
||||
async createBoard(params: SovietContract.Actions.Boards.CreateBoard.ICreateboard) {
|
||||
await this.update_pass_instance()
|
||||
console.dir(params, { depth: null })
|
||||
const result = await this.api.transact(
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
account: SovietContract.contractName.production,
|
||||
name: SovietContract.Actions.Boards.CreateBoard.actionName,
|
||||
authorization: [
|
||||
{
|
||||
actor: params.coopname,
|
||||
permission: 'active',
|
||||
},
|
||||
],
|
||||
data: {
|
||||
...params,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
blocksBehind: 3,
|
||||
expireSeconds: 30,
|
||||
},
|
||||
)
|
||||
|
||||
console.log('Создан совет: ', params)
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Выполняет транзакцию и автоматически выводит консоль логи через consoleIt
|
||||
* @param actions - массив действий для транзакции
|
||||
* @param options - опции транзакции (по умолчанию стандартные)
|
||||
* @param options.blocksBehind - количество блоков позади для подтверждения
|
||||
* @param options.expireSeconds - время жизни транзакции в секундах
|
||||
* @returns результат транзакции
|
||||
*/
|
||||
async transactWithLogs(
|
||||
actions: any[],
|
||||
options: { blocksBehind?: number, expireSeconds?: number } = {},
|
||||
): Promise<any> {
|
||||
await this.update_pass_instance()
|
||||
|
||||
const defaultOptions = {
|
||||
blocksBehind: 3,
|
||||
expireSeconds: 30,
|
||||
...options,
|
||||
}
|
||||
|
||||
const result = await this.api.transact(
|
||||
{ actions },
|
||||
defaultOptions,
|
||||
)
|
||||
|
||||
// Автоматически выводим консоль логи
|
||||
consoleIt(result)
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ enable-stale-production = true
|
||||
read-only-read-window-time-us = 120000
|
||||
|
||||
net-threads = 2
|
||||
max-transaction-time=2000
|
||||
max-transaction-time=10000
|
||||
|
||||
http-server-address = 0.0.0.0:8888
|
||||
p2p-listen-endpoint = 0.0.0.0:9876
|
||||
@@ -26,8 +26,8 @@ max-body-size = 10485760
|
||||
abi-serializer-max-time-ms = 200000
|
||||
contracts-console = true
|
||||
|
||||
max-block-cpu-usage-threshold-us = 5000
|
||||
max-block-net-usage-threshold-bytes = 1024
|
||||
max-block-cpu-usage-threshold-us = 200000
|
||||
max-block-net-usage-threshold-bytes = 800000
|
||||
|
||||
verbose-http-errors = true
|
||||
chain-state-history = true
|
||||
|
||||
@@ -65,6 +65,16 @@ export default [
|
||||
path: path.resolve(process.cwd(), '../contracts/build/contracts/fund'),
|
||||
target: 'fund',
|
||||
},
|
||||
{
|
||||
name: 'ledger',
|
||||
path: path.resolve(process.cwd(), '../contracts/build/contracts/ledger'),
|
||||
target: 'ledger',
|
||||
},
|
||||
{
|
||||
name: 'test',
|
||||
path: path.resolve(process.cwd(), '../contracts/build/contracts/test'),
|
||||
target: 'test',
|
||||
},
|
||||
{
|
||||
name: 'contributor',
|
||||
path: path.resolve(process.cwd(), '../contracts/build/contracts/contributor'),
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
"net_usage_leeway": 500,
|
||||
"context_free_discount_net_usage_num": 20,
|
||||
"context_free_discount_net_usage_den": 100,
|
||||
"max_block_cpu_usage": 300000,
|
||||
"target_block_cpu_usage_pct": 500,
|
||||
"max_block_cpu_usage": 1000000,
|
||||
"target_block_cpu_usage_pct": 1000,
|
||||
"max_transaction_cpu_usage": 290000,
|
||||
"min_transaction_cpu_usage": 100,
|
||||
"max_transaction_lifetime": 3600,
|
||||
|
||||
@@ -53,7 +53,7 @@ export default {
|
||||
allocations: [
|
||||
{
|
||||
to: 'eosio',
|
||||
quantity: `10000.0000 ${SYMBOL}`,
|
||||
quantity: `100000.0000 ${SYMBOL}`,
|
||||
},
|
||||
],
|
||||
accounts: [
|
||||
@@ -91,6 +91,14 @@ export default {
|
||||
name: 'loan',
|
||||
code_permissions_to: ['loan'],
|
||||
},
|
||||
{
|
||||
name: 'ledger',
|
||||
code_permissions_to: ['ledger'],
|
||||
},
|
||||
{
|
||||
name: 'test',
|
||||
code_permissions_to: ['test'],
|
||||
},
|
||||
{
|
||||
name: 'meet',
|
||||
code_permissions_to: ['meet'],
|
||||
@@ -127,9 +135,9 @@ export default {
|
||||
name: 'contributor',
|
||||
code_permissions_to: ['contributor'],
|
||||
},
|
||||
{
|
||||
name: provider_chairman,
|
||||
},
|
||||
// {
|
||||
// name: provider_chairman,
|
||||
// },
|
||||
{
|
||||
name: provider,
|
||||
code_permissions_to: ['registrator'],
|
||||
|
||||
@@ -3,6 +3,7 @@ import * as path from 'node:path'
|
||||
import mongoose from 'mongoose'
|
||||
|
||||
export async function clearDB(): Promise<void> {
|
||||
// Очистка MongoDB
|
||||
// eslint-disable-next-line node/prefer-global/process
|
||||
await mongoose.connect(process.env.MONGO_URI as string)
|
||||
|
||||
@@ -37,6 +38,8 @@ export async function clearDB(): Promise<void> {
|
||||
catch (e) {
|
||||
console.error('Ошибка при удалении:', e)
|
||||
}
|
||||
|
||||
// PostgreSQL будет пересоздан полностью в скрипте extra_reboot.sh
|
||||
}
|
||||
|
||||
export async function clearDirectory(dirPath: string): Promise<void> {
|
||||
|
||||
@@ -6,7 +6,8 @@ import { config } from 'dotenv'
|
||||
import { execCommand } from './docker/exec'
|
||||
import { stopContainerByName } from './docker/stop'
|
||||
import { runContainer } from './docker/run'
|
||||
import { boot } from './init/booter'
|
||||
import { boot, bootClean, bootExtra } from './init/booter'
|
||||
import { startCoop } from './init/cooperative'
|
||||
import { sleep } from './utils'
|
||||
import { checkHealth } from './docker/health'
|
||||
import { clearDB, clearDirectory, deleteFile } from './docker/purge'
|
||||
@@ -120,15 +121,13 @@ program
|
||||
}
|
||||
|
||||
try {
|
||||
await clearDirectory(basePath)
|
||||
await clearDB()
|
||||
await runContainer()
|
||||
|
||||
await sleep(5000)
|
||||
await checkHealth()
|
||||
await boot()
|
||||
console.log(`
|
||||
|
||||
|
||||
Boot is
|
||||
.d8888b. .d88888b. 888b d888 8888888b. 888 8888888888 88888888888 8888888888
|
||||
d88P Y88b d88P" "Y88b 8888b d8888 888 Y88b 888 888 888 888
|
||||
@@ -138,7 +137,7 @@ d88P Y88b d88P" "Y88b 8888b d8888 888 Y88b 888 888 888
|
||||
888 888 888 888 888 Y8P 888 888 888 888 888 888
|
||||
Y88b d88P Y88b. .d88P 888 " 888 888 888 888 888 888
|
||||
"Y8888P" "Y88888P" 888 888 888 88888888 8888888888 888 8888888888
|
||||
|
||||
|
||||
`)
|
||||
process.exit(0)
|
||||
}
|
||||
@@ -148,6 +147,111 @@ Y88b d88P Y88b. .d88P 888 " 888 888 888 888 888
|
||||
}
|
||||
})
|
||||
|
||||
// Команда для получения списка контейнеров
|
||||
program
|
||||
.command('boot:clean')
|
||||
.description('Boot infrastructure without initial data and cooperatives')
|
||||
.action(async () => {
|
||||
try {
|
||||
await stopContainerByName('node')
|
||||
}
|
||||
catch (e) {
|
||||
console.log('Нет контейнера для остановки. Стартуем новый..')
|
||||
}
|
||||
|
||||
try {
|
||||
await runContainer()
|
||||
|
||||
await sleep(5000)
|
||||
await checkHealth()
|
||||
await bootClean()
|
||||
console.log(`
|
||||
|
||||
Boot Clean is
|
||||
.d8888b. .d88888b. 888b d888 8888888b. 888 8888888888 88888888888 8888888888
|
||||
d88P Y88b d88P" "Y88b 8888b d8888 888 Y88b 888 888 888 888
|
||||
888 888 888 888 88888b.d88888 888 888 888 888 888 888
|
||||
888 888 888 888Y88888P888 888 d88P 888 8888888 888 8888888
|
||||
888 888 888 888 Y888P 888 8888888P" 888 888 888 888
|
||||
888 888 888 888 888 Y8P 888 888 888 888 888 888
|
||||
Y88b d88P Y88b. .d88P 888 " 888 888 888 888 888 888
|
||||
"Y8888P" "Y88888P" 888 888 888 88888888 8888888888 888 8888888888
|
||||
|
||||
`)
|
||||
process.exit(0)
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Failed to boot clean:', error)
|
||||
process.exit(1)
|
||||
}
|
||||
})
|
||||
|
||||
// Команда для получения списка контейнеров
|
||||
program
|
||||
.command('boot:extra')
|
||||
.description('Boot infrastructure with extended initial data and additional shareholders')
|
||||
.action(async () => {
|
||||
try {
|
||||
await stopContainerByName('node')
|
||||
}
|
||||
catch (e) {
|
||||
console.log('Нет контейнера для остановки. Стартуем новый..')
|
||||
}
|
||||
|
||||
try {
|
||||
await runContainer()
|
||||
|
||||
await sleep(5000)
|
||||
await checkHealth()
|
||||
await bootExtra()
|
||||
const councilMembers = [
|
||||
{ username: 'ant', fullName: 'Иванов Иван Иванович', email: 'ivanov@example.com' },
|
||||
{ username: 'petr', fullName: 'Сидоров Петр Сергеевич', email: 'sidorov@example.com' },
|
||||
{ username: 'anna', fullName: 'Петрова Анна Ивановна', email: 'petrova@example.com' },
|
||||
{ username: 'mikhail', fullName: 'Кузнецов Михаил Андреевич', email: 'kuznetsov@example.com' },
|
||||
{ username: 'olga', fullName: 'Соколова Ольга Викторовна', email: 'sokolova@example.com' },
|
||||
]
|
||||
|
||||
console.log('\nЧлены совета (логин / ФИО / email):')
|
||||
for (const member of councilMembers)
|
||||
console.log(` - ${member.username}: ${member.fullName} / ${member.email}`)
|
||||
console.log(`
|
||||
|
||||
Boot Extra is
|
||||
.d8888b. .d88888b. 888b d888 8888888b. 888 8888888888 88888888888 8888888888
|
||||
d88P Y88b d88P" "Y88b 8888b d8888 888 Y88b 888 888 888 888
|
||||
888 888 888 888 88888b.d88888 888 888 888 888 888 888
|
||||
888 888 888 888Y88888P888 888 d88P 888 8888888 888 8888888
|
||||
888 888 888 888 Y888P 888 8888888P" 888 888 888 888
|
||||
888 888 888 888 888 Y8P 888 888 888 888 888 888
|
||||
Y88b d88P Y88b. .d88P 888 " 888 888 888 888 888 888
|
||||
"Y8888P" "Y88888P" 888 888 888 88888888 8888888888 888 8888888888
|
||||
|
||||
`)
|
||||
process.exit(0)
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Failed to boot extra:', error)
|
||||
process.exit(1)
|
||||
}
|
||||
})
|
||||
|
||||
// Команда для создания кооператива
|
||||
program
|
||||
.command('create-coop')
|
||||
.description('Create cooperative after clean boot')
|
||||
.action(async () => {
|
||||
try {
|
||||
await startCoop()
|
||||
console.log('Кооператив успешно создан')
|
||||
process.exit(0)
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Failed to create cooperative:', error)
|
||||
process.exit(1)
|
||||
}
|
||||
})
|
||||
|
||||
// Команда для получения списка контейнеров
|
||||
program
|
||||
.command('clear')
|
||||
|
||||
@@ -1,7 +1,35 @@
|
||||
import { startInfra } from "./infra"
|
||||
import { startCoop } from "./cooperative"
|
||||
import config from '../configs'
|
||||
import { initExtensionsInPostgres, initSystemStatus } from '../postgres-init'
|
||||
import { installExtraData, installInitialData, startInfra } from './infra'
|
||||
import { CooperativeClass, startCoop } from './cooperative'
|
||||
|
||||
export async function boot() {
|
||||
await startInfra()
|
||||
const blockchain = await startInfra()
|
||||
await installInitialData(blockchain, false) // Создать базовый совет
|
||||
|
||||
console.log('Инициализируем статус системы в PostgreSQL')
|
||||
await initSystemStatus()
|
||||
|
||||
await startCoop()
|
||||
}
|
||||
|
||||
export async function bootClean() {
|
||||
const blockchain = await startInfra()
|
||||
|
||||
console.log('Создаём программы (Благорост и маркетплейс)')
|
||||
|
||||
const cooperative = new CooperativeClass(blockchain)
|
||||
await cooperative.createPrograms(config.provider)
|
||||
}
|
||||
|
||||
export async function bootExtra() {
|
||||
const blockchain = await startInfra()
|
||||
await installInitialData(blockchain, true) // Создать расширенный совет (устанавливает статус 'active' в MongoDB)
|
||||
await installExtraData(blockchain) // Добавить дополнительных пайщиков
|
||||
|
||||
console.log('Инициализируем статус системы в PostgreSQL')
|
||||
await initSystemStatus() // Устанавливает статус 'active' в PostgreSQL
|
||||
|
||||
console.log('Инициализируем extensions в PostgreSQL')
|
||||
await initExtensionsInPostgres() // Инициализирует таблицу extensions с данными capital
|
||||
}
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import axios from 'axios'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { Registry } from '@coopenomics/factory'
|
||||
import { Cooperative as TCooperative } from 'cooptypes'
|
||||
import type { Account, Contract, Keys } from '../types'
|
||||
import type { Cooperative } from 'cooptypes'
|
||||
import type { Keys } from '../types'
|
||||
import config, { GOVERN_SYMBOL, SYMBOL } from '../configs'
|
||||
import Blockchain from '../blockchain'
|
||||
import { sendPostToCoopbackWithSecret, sleep } from '../utils'
|
||||
import { generateRandomSHA256 } from '../utils/randomHash'
|
||||
import { processLastDecision } from '../tests/soviet/processLastDecision'
|
||||
|
||||
const test_hash
|
||||
= '157192b276da23cc84ab078fc8755c051c5f0430bf4802e55718221e6b76c777'
|
||||
@@ -15,11 +12,23 @@ const test_sign
|
||||
const test_pkey = 'EOS5JhMfxbsNebajHcTEK8yC9uNN9Dit9hEmzE8ri8yMhhzxrLg3J'
|
||||
const test_meta = JSON.stringify({})
|
||||
|
||||
const document = {
|
||||
const document: Cooperative.Document.ISignedDocument2 = {
|
||||
hash: test_hash,
|
||||
signature: test_sign,
|
||||
public_key: test_pkey,
|
||||
signatures: [
|
||||
{
|
||||
id: 1,
|
||||
signed_hash: test_hash,
|
||||
public_key: test_pkey,
|
||||
signed_at: '2025-05-14T12:22:26',
|
||||
meta: test_meta,
|
||||
signer: 'cooperative1',
|
||||
signature: test_sign,
|
||||
},
|
||||
],
|
||||
meta: test_meta,
|
||||
version: '1.0.0',
|
||||
doc_hash: test_hash,
|
||||
meta_hash: test_hash,
|
||||
}
|
||||
|
||||
export class CooperativeClass {
|
||||
@@ -33,7 +42,7 @@ export class CooperativeClass {
|
||||
await this.blockchain.createProgram({
|
||||
coopname,
|
||||
username: coopname,
|
||||
type: 'cooplace',
|
||||
type: 'marketplace',
|
||||
title: 'КООПЛЕЙС',
|
||||
announce: '',
|
||||
description: '',
|
||||
@@ -45,6 +54,38 @@ export class CooperativeClass {
|
||||
meta: '',
|
||||
is_can_coop_spend_share_contributions: false,
|
||||
})
|
||||
|
||||
await this.blockchain.createProgram({
|
||||
coopname,
|
||||
username: coopname,
|
||||
type: 'generator',
|
||||
title: 'Целевая потребительская программа "Генератор"',
|
||||
announce: '',
|
||||
description: '',
|
||||
preview: '',
|
||||
images: '',
|
||||
calculation_type: 'free',
|
||||
fixed_membership_contribution: `${Number(0).toFixed(4)} ${GOVERN_SYMBOL}`,
|
||||
membership_percent_fee: 0, // 10%
|
||||
meta: '',
|
||||
is_can_coop_spend_share_contributions: true,
|
||||
})
|
||||
|
||||
await this.blockchain.createProgram({
|
||||
coopname,
|
||||
username: coopname,
|
||||
type: 'blagorost',
|
||||
title: 'Целевая потребительская программа "Благорост"',
|
||||
announce: '',
|
||||
description: '',
|
||||
preview: '',
|
||||
images: '',
|
||||
calculation_type: 'free',
|
||||
fixed_membership_contribution: `${Number(0).toFixed(4)} ${GOVERN_SYMBOL}`,
|
||||
membership_percent_fee: 0, // 10%
|
||||
meta: '',
|
||||
is_can_coop_spend_share_contributions: true,
|
||||
})
|
||||
}
|
||||
|
||||
async createCooperative(username: string, keys?: Keys) {
|
||||
@@ -82,30 +123,36 @@ export class CooperativeClass {
|
||||
income_hash: registration_hash,
|
||||
})
|
||||
|
||||
console.log('Голосуем по решению в провайдере')
|
||||
await processLastDecision(this.blockchain, config.provider)
|
||||
// console.log('Голосуем по решению в провайдере')
|
||||
|
||||
await this.blockchain.votefor({
|
||||
coopname: config.provider,
|
||||
member: config.provider_chairman,
|
||||
decision_id: 1,
|
||||
})
|
||||
// await this.blockchain.votefor({
|
||||
// version: '1.0.0',
|
||||
// signed_at: '2025-05-14T12:22:26',
|
||||
// signed_hash: test_hash,
|
||||
// signature: test_sign,
|
||||
// public_key: test_pkey,
|
||||
// coopname: config.provider,
|
||||
// username: config.provider_chairman,
|
||||
// decision_id: 1,
|
||||
// })
|
||||
|
||||
console.log('Утверждаем решение в провайдере')
|
||||
// console.log('Утверждаем решение в провайдере')
|
||||
|
||||
await this.blockchain.authorize({
|
||||
coopname: config.provider,
|
||||
chairman: config.provider_chairman,
|
||||
decision_id: 1,
|
||||
document,
|
||||
})
|
||||
// await this.blockchain.authorize({
|
||||
// coopname: config.provider,
|
||||
// chairman: config.provider_chairman,
|
||||
// decision_id: 1,
|
||||
// document,
|
||||
// })
|
||||
|
||||
console.log('Исполняем решение в провайдере')
|
||||
// console.log('Исполняем решение в провайдере')
|
||||
|
||||
await this.blockchain.exec({
|
||||
executer: config.provider_chairman,
|
||||
coopname: config.provider,
|
||||
decision_id: 1,
|
||||
})
|
||||
// await this.blockchain.exec({
|
||||
// executer: config.provider_chairman,
|
||||
// coopname: config.provider,
|
||||
// decision_id: 1,
|
||||
// })
|
||||
|
||||
console.log('Отправляем подписанное положение о ЦПП Кошелька оператору')
|
||||
|
||||
@@ -114,12 +161,7 @@ export class CooperativeClass {
|
||||
administrator: config.provider,
|
||||
username: username!,
|
||||
agreement_type: 'wallet',
|
||||
document: { // отправляем произвольный документ с валидной подписью
|
||||
hash: '157192B276DA23CC84AB078FC8755C051C5F0430BF4802E55718221E6B76C777',
|
||||
public_key: 'PUB_K1_5JhMfxbsNebajHcTEK8yC9uNN9Dit9hEmzE8ri8yMhhzzEtUA4',
|
||||
signature: 'SIG_K1_KmKWPBC8dZGGDGhbKEoZEzPr3h5crRrR2uLdGRF5DJbeibY1MY1bZ9sPwHsgmPfiGFv9psfoCVsXFh9TekcLuvaeuxRKA8',
|
||||
meta: '{}',
|
||||
},
|
||||
document,
|
||||
})
|
||||
|
||||
console.log('Переводим аккаунт в кооператив')
|
||||
@@ -141,7 +183,7 @@ export class CooperativeClass {
|
||||
})
|
||||
|
||||
console.log('Переводим инициализационные токены')
|
||||
await this.blockchain.transfer({ from: 'eosio', to: username, quantity: `100.0000 ${SYMBOL}`, memo: '' })
|
||||
await this.blockchain.transfer({ from: 'eosio', to: account.username, quantity: `100.0000 ${SYMBOL}`, memo: '' })
|
||||
|
||||
console.log(`Арендуем ресурсы кооперативу`)
|
||||
|
||||
@@ -160,18 +202,18 @@ export async function startCoop() {
|
||||
const blockchain = new Blockchain(config.network, config.private_keys)
|
||||
const cooperative = new CooperativeClass(blockchain)
|
||||
|
||||
await cooperative.createCooperative('cooperative1', {
|
||||
// eslint-disable-next-line node/prefer-global/process
|
||||
privateKey: process.env.EOSIO_PRV_KEY!,
|
||||
// eslint-disable-next-line node/prefer-global/process
|
||||
publicKey: process.env.EOSIO_PUB_KEY!,
|
||||
})
|
||||
// await cooperative.createCooperative('cooperative1', {
|
||||
// // eslint-disable-next-line node/prefer-global/process
|
||||
// privateKey: process.env.EOSIO_PRV_KEY!,
|
||||
// // eslint-disable-next-line node/prefer-global/process
|
||||
// publicKey: process.env.EOSIO_PUB_KEY!,
|
||||
// })
|
||||
|
||||
await blockchain.preInit({
|
||||
coopname: 'cooperative1',
|
||||
username: config.provider,
|
||||
status: 'active',
|
||||
})
|
||||
// await blockchain.preInit({
|
||||
// coopname: 'cooperative1',
|
||||
// username: config.provider,
|
||||
// status: 'active',
|
||||
// })
|
||||
|
||||
console.log('Кооператив предварительно подготовлен к установке совета.')
|
||||
}
|
||||
|
||||
@@ -3,11 +3,12 @@ import axios from 'axios'
|
||||
import { Generator, Registry } from '@coopenomics/factory'
|
||||
import type { Cooperative } from 'cooptypes'
|
||||
import { DraftContract } from 'cooptypes'
|
||||
import mongoose from 'mongoose'
|
||||
import mongoose, { Types } from 'mongoose'
|
||||
import type { Account, Contract } from '../types'
|
||||
import config from '../configs'
|
||||
import Blockchain from '../blockchain'
|
||||
import { sleep } from '../utils'
|
||||
import { initUsersInPostgres, initVaultInPostgres } from '../postgres-init'
|
||||
import { CooperativeClass } from './cooperative'
|
||||
|
||||
export async function startInfra() {
|
||||
@@ -145,10 +146,32 @@ export async function startInfra() {
|
||||
})
|
||||
}
|
||||
|
||||
console.log(`Арендуем ресурсы провайдеру`)
|
||||
await blockchain.powerup({
|
||||
payer: 'eosio',
|
||||
receiver: config.provider,
|
||||
days: config.powerup.days,
|
||||
payment: `10000.0000 ${config.token.symbol}`,
|
||||
transfer: true,
|
||||
})
|
||||
|
||||
await blockchain.transfer({
|
||||
from: 'eosio',
|
||||
to: config.provider,
|
||||
quantity: `1000.0000 ${config.token.symbol}`,
|
||||
memo: '',
|
||||
})
|
||||
|
||||
console.log('Базовая инфраструктура установлена')
|
||||
|
||||
return blockchain
|
||||
}
|
||||
|
||||
export async function installInitialData(blockchain: Blockchain, isExtended = false) {
|
||||
const organizationData: Cooperative.Users.IOrganizationData = {
|
||||
username: 'voskhod',
|
||||
type: 'coop',
|
||||
short_name: '"ПК Восход"',
|
||||
short_name: 'ПК "Восход"',
|
||||
full_name: 'Потребительский Кооператив "ВОСХОД"',
|
||||
represented_by: {
|
||||
first_name: 'Иван',
|
||||
@@ -220,9 +243,9 @@ export async function startInfra() {
|
||||
// добавляем переменные кооператива
|
||||
const vars: Cooperative.Model.IVars = {
|
||||
coopname: 'voskhod',
|
||||
full_abbr: 'потребительский кооператив',
|
||||
full_abbr_genitive: 'потребительского кооператива',
|
||||
full_abbr_dative: 'потребительскому кооперативу',
|
||||
full_abbr: 'Потребительский Кооператив',
|
||||
full_abbr_genitive: 'Потребительского Кооператива',
|
||||
full_abbr_dative: 'Потребительскому Кооперативу',
|
||||
short_abbr: 'ПК',
|
||||
website: 'цифровой-кооператив.рф',
|
||||
name: 'Восход',
|
||||
@@ -250,13 +273,55 @@ export async function startInfra() {
|
||||
protocol_number: '10-04-2024',
|
||||
protocol_day_month_year: '10 апреля 2024 г.',
|
||||
},
|
||||
generator_program: {
|
||||
protocol_number: '1',
|
||||
protocol_day_month_year: '09.02.2026 10:24',
|
||||
},
|
||||
generation_contract_template: {
|
||||
protocol_number: '2',
|
||||
protocol_day_month_year: '09.02.2026 10:24',
|
||||
},
|
||||
blagorost_program: {
|
||||
protocol_number: '3',
|
||||
protocol_day_month_year: '09.02.2026 10:24',
|
||||
},
|
||||
generator_offer_template: {
|
||||
protocol_number: '4',
|
||||
protocol_day_month_year: '09.02.2026 10:27',
|
||||
},
|
||||
blagorost_offer_template: {
|
||||
protocol_number: '5',
|
||||
protocol_day_month_year: '09.02.2026 10:27',
|
||||
},
|
||||
deleted: false,
|
||||
block_num: 1,
|
||||
}
|
||||
|
||||
await generator.save('vars', vars)
|
||||
|
||||
// Сохраняем vars с указанием конкретного _id и _created_at
|
||||
// eslint-disable-next-line node/prefer-global/process
|
||||
await mongoose.connect(process.env.MONGO_URI as string)
|
||||
|
||||
try {
|
||||
await mongoose.connection.collection('vars').insertOne({
|
||||
_id: new Types.ObjectId('69898c7d996550b4db4b1a36'),
|
||||
_created_at: new Date('2026-02-08T13:29:12.423Z'),
|
||||
...vars,
|
||||
})
|
||||
console.log('Vars сохранены с указанным _id и _created_at')
|
||||
}
|
||||
catch (e) {
|
||||
console.log('Vars уже существуют, обновляем...')
|
||||
await mongoose.connection.collection('vars').updateOne(
|
||||
{ coopname: 'voskhod' },
|
||||
{
|
||||
$set: {
|
||||
...vars,
|
||||
_created_at: new Date('2026-02-08T13:29:12.423Z'),
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
try {
|
||||
await mongoose.connection.collection('sync').deleteMany({})
|
||||
console.log('Все документы удалены из коллекции sync')
|
||||
@@ -281,17 +346,17 @@ export async function startInfra() {
|
||||
console.error('Ошибка при удалении:', e)
|
||||
}
|
||||
|
||||
// добавляем пользователя для подключений
|
||||
try {
|
||||
await mongoose.connection.collection('users').insertOne({
|
||||
email: 'ivanov@example.com',
|
||||
// Собираем пользователей для инициализации в PostgreSQL
|
||||
const usersToInit = [
|
||||
{
|
||||
username: 'ant',
|
||||
type: 'individual',
|
||||
email: 'ivanov@example.com',
|
||||
type: 'individual' as const,
|
||||
role: 'chairman',
|
||||
status: 'active',
|
||||
is_registered: true,
|
||||
})
|
||||
}
|
||||
catch (e) { console.log('user is exist') }
|
||||
},
|
||||
]
|
||||
|
||||
// имитируем установку
|
||||
try {
|
||||
@@ -312,27 +377,195 @@ export async function startInfra() {
|
||||
})
|
||||
}
|
||||
catch (e) { console.log('vault is exist') }
|
||||
|
||||
console.log('Добавляем пайщика ant')
|
||||
|
||||
await blockchain.addUser({
|
||||
coopname: 'voskhod',
|
||||
referer: '',
|
||||
username: 'ant',
|
||||
type: 'individual',
|
||||
created_at: '2025-01-15T10:00:00',
|
||||
initial: '100.0000 RUB',
|
||||
minimum: '200.0000 RUB',
|
||||
spread_initial: true,
|
||||
meta: 'Основатель кооператива ВОСХОД',
|
||||
})
|
||||
|
||||
console.log('Устанавливаем дефолтный публичный ключ для ant')
|
||||
|
||||
await blockchain.changeKey({
|
||||
coopname: 'voskhod',
|
||||
changer: 'voskhod',
|
||||
username: 'ant',
|
||||
public_key: config.default_public_key,
|
||||
})
|
||||
|
||||
// Если расширенный режим, сначала добавляем дополнительных пайщиков
|
||||
if (isExtended) {
|
||||
console.log('Добавляем дополнительных пайщиков для расширенного совета')
|
||||
|
||||
const extraUsers = [
|
||||
{
|
||||
username: 'petr',
|
||||
first_name: 'Петр',
|
||||
last_name: 'Сидоров',
|
||||
middle_name: 'Сергеевич',
|
||||
email: 'sidorov@example.com',
|
||||
},
|
||||
{
|
||||
username: 'anna',
|
||||
first_name: 'Анна',
|
||||
last_name: 'Петрова',
|
||||
middle_name: 'Ивановна',
|
||||
email: 'petrova@example.com',
|
||||
},
|
||||
{
|
||||
username: 'mikhail',
|
||||
first_name: 'Михаил',
|
||||
last_name: 'Кузнецов',
|
||||
middle_name: 'Андреевич',
|
||||
email: 'kuznetsov@example.com',
|
||||
},
|
||||
{
|
||||
username: 'olga',
|
||||
first_name: 'Ольга',
|
||||
last_name: 'Соколова',
|
||||
middle_name: 'Викторовна',
|
||||
email: 'sokolova@example.com',
|
||||
},
|
||||
]
|
||||
|
||||
for (const user of extraUsers) {
|
||||
// Добавляем в MongoDB
|
||||
const userData: Cooperative.Users.IIndividualData = {
|
||||
username: user.username,
|
||||
first_name: user.first_name,
|
||||
last_name: user.last_name,
|
||||
middle_name: user.middle_name,
|
||||
birthdate: '1990/04/01',
|
||||
phone: '+71234567890',
|
||||
email: user.email,
|
||||
full_address: 'г. Москва, ул. Примерная д. 1',
|
||||
passport: {
|
||||
series: 7122,
|
||||
number: Math.floor(Math.random() * 900000) + 100000,
|
||||
issued_by: 'отделом УФМС по г. Москва',
|
||||
issued_at: '2010/05/10',
|
||||
code: '111-232',
|
||||
},
|
||||
}
|
||||
|
||||
await generator.save('individual', userData)
|
||||
|
||||
// Добавляем пользователя в список для PostgreSQL
|
||||
usersToInit.push({
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
type: 'individual' as const,
|
||||
role: 'member',
|
||||
status: 'active',
|
||||
is_registered: true,
|
||||
})
|
||||
|
||||
console.log(`Добавляем пайщика ${user.username}`)
|
||||
|
||||
// Добавляем в блокчейн
|
||||
await blockchain.addUser({
|
||||
coopname: 'voskhod',
|
||||
referer: user.username === 'petr' ? '' : 'petr',
|
||||
username: user.username,
|
||||
type: 'individual',
|
||||
created_at: '2025-01-15T10:00:00',
|
||||
initial: '100.0000 RUB',
|
||||
minimum: '300.0000 RUB',
|
||||
spread_initial: true,
|
||||
meta: `Член совета кооператива ВОСХОД - ${user.first_name} ${user.middle_name} ${user.last_name}`,
|
||||
})
|
||||
|
||||
console.log(`Устанавливаем дефолтный публичный ключ для ${user.username}`)
|
||||
|
||||
await blockchain.changeKey({
|
||||
coopname: 'voskhod',
|
||||
changer: 'voskhod',
|
||||
username: user.username,
|
||||
public_key: config.default_public_key,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Инициализируем пользователей в PostgreSQL')
|
||||
await initUsersInPostgres(usersToInit)
|
||||
|
||||
console.log('Инициализируем vault в PostgreSQL')
|
||||
await initVaultInPostgres()
|
||||
|
||||
console.log('Создаём совет')
|
||||
|
||||
const boardMembers: Array<{
|
||||
username: string
|
||||
is_voting: boolean
|
||||
position_title: string
|
||||
position: 'chairman' | 'member'
|
||||
}> = [
|
||||
{
|
||||
username: 'ant',
|
||||
is_voting: true,
|
||||
position_title: 'Председатель совета',
|
||||
position: 'chairman',
|
||||
},
|
||||
]
|
||||
|
||||
// Если расширенный режим, добавляем дополнительных членов
|
||||
if (isExtended) {
|
||||
boardMembers.push(
|
||||
{
|
||||
username: 'petr',
|
||||
is_voting: true,
|
||||
position_title: 'Член совета',
|
||||
position: 'member',
|
||||
},
|
||||
{
|
||||
username: 'anna',
|
||||
is_voting: true,
|
||||
position_title: 'Член совета',
|
||||
position: 'member',
|
||||
},
|
||||
{
|
||||
username: 'mikhail',
|
||||
is_voting: true,
|
||||
position_title: 'Член совета',
|
||||
position: 'member',
|
||||
},
|
||||
{
|
||||
username: 'olga',
|
||||
is_voting: true,
|
||||
position_title: 'Член совета',
|
||||
position: 'member',
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
await blockchain.createBoard({
|
||||
coopname: 'voskhod',
|
||||
username: 'ant',
|
||||
type: 'soviet',
|
||||
members: boardMembers,
|
||||
name: 'Совет',
|
||||
description: isExtended ? 'Совет кооператива ВОСХОД (расширенный состав)' : 'Совет кооператива ВОСХОД',
|
||||
})
|
||||
|
||||
console.log('Создаём программы и соглашения')
|
||||
|
||||
const cooperative = new CooperativeClass(blockchain)
|
||||
|
||||
await cooperative.createPrograms(config.provider)
|
||||
|
||||
console.log(`Арендуем ресурсы провайдеру`)
|
||||
await blockchain.powerup({
|
||||
payer: 'eosio',
|
||||
receiver: config.provider,
|
||||
days: config.powerup.days,
|
||||
payment: `100.0000 ${config.token.symbol}`,
|
||||
transfer: true,
|
||||
})
|
||||
|
||||
await blockchain.transfer({
|
||||
from: 'eosio',
|
||||
to: config.provider,
|
||||
quantity: `100.0000 ${config.token.symbol}`,
|
||||
memo: '',
|
||||
})
|
||||
|
||||
console.log('Базовая установка завершена')
|
||||
console.log('Начальные данные установлены')
|
||||
}
|
||||
|
||||
export async function installExtraData(blockchain: Blockchain) {
|
||||
// В расширенном режиме пайщики уже добавлены в installInitialData
|
||||
// Здесь можно добавить дополнительную логику инициализации если потребуется
|
||||
console.log('Дополнительная инициализация для расширенного режима выполнена')
|
||||
}
|
||||
|
||||
@@ -6,29 +6,96 @@ import type { Account, Contract, Keys } from '../types'
|
||||
import config, { GOVERN_SYMBOL, SYMBOL } from '../configs'
|
||||
import Blockchain from '../blockchain'
|
||||
import { sendPostToCoopbackWithSecret, sleep } from '../utils'
|
||||
import { fakeDocument } from '../tests/shared/fakeDocument'
|
||||
|
||||
// const test_hash
|
||||
// = '157192b276da23cc84ab078fc8755c051c5f0430bf4802e55718221e6b76c777'
|
||||
// const test_sign
|
||||
// = 'SIG_K1_KmKWPBC8dZGGDGhbKEoZEzPr3h5crRrR2uLdGRF5DJbeibY1MY1bZ9sPwHsgmPfiGFv9psfoCVsXFh9TekcLuvaeuxRKA8'
|
||||
// const test_pkey = 'EOS5JhMfxbsNebajHcTEK8yC9uNN9Dit9hEmzE8ri8yMhhzxrLg3J'
|
||||
// const test_meta = JSON.stringify({})
|
||||
|
||||
// const document = {
|
||||
// hash: test_hash,
|
||||
// signature: test_sign,
|
||||
// public_key: test_pkey,
|
||||
// meta: test_meta,
|
||||
// }
|
||||
|
||||
export class CooperativeClass {
|
||||
export class ParticipantsClass {
|
||||
public blockchain: Blockchain
|
||||
|
||||
constructor(blockchain: Blockchain) {
|
||||
this.blockchain = blockchain
|
||||
}
|
||||
|
||||
async addUser(username: string, keys?: Keys) {
|
||||
async addUser2(coopname: string, username: string, keys?: Keys, referer?: string) {
|
||||
const account = await this.blockchain.generateKeypair(
|
||||
username,
|
||||
keys,
|
||||
'Аккаунт участника',
|
||||
)
|
||||
console.log('Регистрируем аккаунт')
|
||||
|
||||
const data: RegistratorContract.Actions.AddUser.IAddUser = {
|
||||
coopname,
|
||||
referer: referer || '',
|
||||
username: account.username,
|
||||
type: 'individual',
|
||||
created_at: '2025-02-05T09:34:27',
|
||||
initial: '100.0000 RUB',
|
||||
minimum: '100.0000 RUB',
|
||||
spread_initial: false,
|
||||
meta: '',
|
||||
}
|
||||
|
||||
await this.blockchain.api.transact({
|
||||
actions: [
|
||||
{
|
||||
account: RegistratorContract.contractName.production,
|
||||
name: RegistratorContract.Actions.AddUser.actionName,
|
||||
authorization: [{ actor: coopname, permission: 'active' }],
|
||||
data,
|
||||
},
|
||||
],
|
||||
}, {
|
||||
blocksBehind: 3,
|
||||
expireSeconds: 30,
|
||||
})
|
||||
|
||||
console.log('Регистрируем аккаунт как пользователя')
|
||||
|
||||
const changeKey: RegistratorContract.Actions.ChangeKey.IChangeKey = {
|
||||
coopname,
|
||||
username,
|
||||
public_key: account.publicKey,
|
||||
changer: coopname,
|
||||
}
|
||||
|
||||
await this.blockchain.api.transact({
|
||||
actions: [
|
||||
{
|
||||
account: RegistratorContract.contractName.production,
|
||||
name: RegistratorContract.Actions.ChangeKey.actionName,
|
||||
authorization: [{ actor: coopname, permission: 'active' }],
|
||||
data: changeKey,
|
||||
},
|
||||
],
|
||||
}, {
|
||||
blocksBehind: 3,
|
||||
expireSeconds: 30,
|
||||
})
|
||||
|
||||
// console.log('Отправляем подписанное положение о ЦПП Кошелька оператору')
|
||||
|
||||
// await this.blockchain.sendAgreement({
|
||||
// coopname: config.provider,
|
||||
// administrator: config.provider,
|
||||
// username: username!,
|
||||
// agreement_type: 'wallet',
|
||||
// document: fakeDocument,
|
||||
// })
|
||||
|
||||
// console.log('создаём кошелёк')
|
||||
|
||||
// console.log(`Арендуем ресурсы кооперативу`)
|
||||
|
||||
// await this.blockchain.powerup({
|
||||
// payer: 'eosio',
|
||||
// receiver: username,
|
||||
// days: config.powerup.days,
|
||||
// payment: `100.0000 ${config.token.symbol}`,
|
||||
// transfer: true,
|
||||
// })
|
||||
}
|
||||
|
||||
async addUser(username: string, keys?: Keys, referer?: string) {
|
||||
const account = await this.blockchain.generateKeypair(
|
||||
username,
|
||||
keys,
|
||||
@@ -38,7 +105,7 @@ export class CooperativeClass {
|
||||
|
||||
const data: RegistratorContract.Actions.AddUser.IAddUser = {
|
||||
coopname: config.provider,
|
||||
referer: '',
|
||||
referer: referer || '',
|
||||
username: account.username,
|
||||
type: 'individual',
|
||||
created_at: '2025-02-05T09:34:27',
|
||||
@@ -92,12 +159,7 @@ export class CooperativeClass {
|
||||
administrator: config.provider,
|
||||
username: username!,
|
||||
agreement_type: 'wallet',
|
||||
document: { // отправляем произвольный документ с валидной подписью
|
||||
hash: '157192B276DA23CC84AB078FC8755C051C5F0430BF4802E55718221E6B76C777',
|
||||
public_key: 'PUB_K1_5JhMfxbsNebajHcTEK8yC9uNN9Dit9hEmzE8ri8yMhhzzEtUA4',
|
||||
signature: 'SIG_K1_KmKWPBC8dZGGDGhbKEoZEzPr3h5crRrR2uLdGRF5DJbeibY1MY1bZ9sPwHsgmPfiGFv9psfoCVsXFh9TekcLuvaeuxRKA8',
|
||||
meta: '{}',
|
||||
},
|
||||
document: fakeDocument,
|
||||
})
|
||||
|
||||
console.log('создаём кошелёк')
|
||||
@@ -114,15 +176,28 @@ export class CooperativeClass {
|
||||
}
|
||||
}
|
||||
|
||||
export async function addUser(username: string) {
|
||||
export async function addUser(username: string, referer?: string) {
|
||||
// инициализируем инстанс с ключами
|
||||
const blockchain = new Blockchain(config.network, config.private_keys)
|
||||
const cooperative = new CooperativeClass(blockchain)
|
||||
const participants = new ParticipantsClass(blockchain)
|
||||
|
||||
await cooperative.addUser(username, {
|
||||
await participants.addUser(username, {
|
||||
// eslint-disable-next-line node/prefer-global/process
|
||||
privateKey: process.env.EOSIO_PRV_KEY!,
|
||||
// eslint-disable-next-line node/prefer-global/process
|
||||
publicKey: process.env.EOSIO_PUB_KEY!,
|
||||
})
|
||||
}, referer)
|
||||
}
|
||||
|
||||
export async function addUser2(coopname: string, username: string, referer?: string) {
|
||||
// инициализируем инстанс с ключами
|
||||
const blockchain = new Blockchain(config.network, config.private_keys)
|
||||
const participants = new ParticipantsClass(blockchain)
|
||||
|
||||
await participants.addUser2(coopname, username, {
|
||||
// eslint-disable-next-line node/prefer-global/process
|
||||
privateKey: process.env.EOSIO_PRV_KEY!,
|
||||
// eslint-disable-next-line node/prefer-global/process
|
||||
publicKey: process.env.EOSIO_PUB_KEY!,
|
||||
}, referer)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,294 @@
|
||||
/* eslint-disable node/prefer-global/process */
|
||||
import { Client } from 'pg'
|
||||
import type { Cooperative } from 'cooptypes'
|
||||
|
||||
export async function initSystemStatus() {
|
||||
console.log('Инициализация статуса системы для coopname: voskhod')
|
||||
|
||||
const client = new Client({
|
||||
host: process.env.POSTGRES_HOST,
|
||||
port: parseInt(process.env.POSTGRES_PORT || '5432'),
|
||||
user: process.env.POSTGRES_USERNAME,
|
||||
password: process.env.POSTGRES_PASSWORD,
|
||||
database: process.env.POSTGRES_DATABASE,
|
||||
})
|
||||
|
||||
try {
|
||||
await client.connect()
|
||||
console.log('Подключение к PostgreSQL установлено для initSystemStatus')
|
||||
|
||||
// Создаем enum тип для статуса системы
|
||||
try {
|
||||
await client.query(`
|
||||
CREATE TYPE public.system_status_status_enum AS ENUM ('install', 'initialized', 'active', 'maintenance')
|
||||
`)
|
||||
}
|
||||
catch (error) {
|
||||
// Тип уже существует, продолжаем
|
||||
console.log('Enum тип system_status_status_enum уже существует, пропускаем создание')
|
||||
}
|
||||
|
||||
// Создаем таблицу system_status правильно, как в TypeORM entity
|
||||
await client.query(`
|
||||
CREATE TABLE IF NOT EXISTS public.system_status (
|
||||
coopname varchar(12) NOT NULL,
|
||||
install_code varchar(255) NULL,
|
||||
install_code_expires_at timestamp NULL,
|
||||
init_by_server bool NOT NULL DEFAULT false,
|
||||
created_at timestamp NOT NULL DEFAULT now(),
|
||||
updated_at timestamp NOT NULL DEFAULT now(),
|
||||
status public.system_status_status_enum NOT NULL DEFAULT 'install'::system_status_status_enum,
|
||||
CONSTRAINT system_status_pkey PRIMARY KEY (coopname)
|
||||
)
|
||||
`)
|
||||
|
||||
try {
|
||||
// Устанавливаем начальный статус для voskhod (active - система готова к работе)
|
||||
await client.query(`
|
||||
INSERT INTO system_status (coopname, status)
|
||||
VALUES ('voskhod', 'active')
|
||||
ON CONFLICT (coopname) DO UPDATE SET
|
||||
status = EXCLUDED.status,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
`)
|
||||
|
||||
// Проверяем, что статус действительно установлен
|
||||
const result = await client.query(`
|
||||
SELECT status FROM system_status WHERE coopname = 'voskhod'
|
||||
`)
|
||||
console.log('Статус системы в PostgreSQL после установки:', result.rows[0]?.status)
|
||||
}
|
||||
catch (queryError) {
|
||||
console.error('Ошибка при установке статуса системы в PostgreSQL:', queryError)
|
||||
throw queryError
|
||||
}
|
||||
|
||||
console.log('Статус системы инициализирован в PostgreSQL')
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Ошибка инициализации статуса системы в PostgreSQL:', error)
|
||||
throw error
|
||||
}
|
||||
finally {
|
||||
await client.end()
|
||||
}
|
||||
}
|
||||
|
||||
export async function initUsersInPostgres(
|
||||
users: Array<{
|
||||
username: string
|
||||
email: string
|
||||
type: 'individual' | 'entrepreneur' | 'organization'
|
||||
role: string
|
||||
status: string
|
||||
is_registered: boolean
|
||||
}>,
|
||||
) {
|
||||
const client = new Client({
|
||||
host: process.env.POSTGRES_HOST,
|
||||
port: parseInt(process.env.POSTGRES_PORT || '5432'),
|
||||
user: process.env.POSTGRES_USERNAME,
|
||||
password: process.env.POSTGRES_PASSWORD,
|
||||
database: process.env.POSTGRES_DATABASE,
|
||||
})
|
||||
|
||||
try {
|
||||
await client.connect()
|
||||
console.log('Подключение к PostgreSQL установлено для инициализации пользователей')
|
||||
|
||||
// Создаем таблицу users, если она не существует
|
||||
await client.query(`
|
||||
CREATE TABLE IF NOT EXISTS "users" (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
username VARCHAR(50) UNIQUE NOT NULL,
|
||||
status VARCHAR(20) DEFAULT 'created',
|
||||
message TEXT DEFAULT '',
|
||||
is_registered BOOLEAN DEFAULT FALSE,
|
||||
has_account BOOLEAN DEFAULT FALSE,
|
||||
type VARCHAR(20) NOT NULL,
|
||||
public_key TEXT DEFAULT '',
|
||||
referer VARCHAR(100) DEFAULT '',
|
||||
email VARCHAR(255),
|
||||
role VARCHAR(20) DEFAULT 'user',
|
||||
is_email_verified BOOLEAN DEFAULT FALSE,
|
||||
subscriber_id VARCHAR(100) DEFAULT '',
|
||||
subscriber_hash VARCHAR(255) DEFAULT '',
|
||||
legacy_mongo_id VARCHAR(50),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`)
|
||||
|
||||
// Создаем пользователей
|
||||
for (const user of users) {
|
||||
// Проверяем, что username не null и не пустой
|
||||
if (!user.username || user.username.trim() === '') {
|
||||
console.warn(`Пропускаем пользователя с пустым username:`, user)
|
||||
continue
|
||||
}
|
||||
|
||||
await client.query(`
|
||||
INSERT INTO "users" (
|
||||
username, email, type, role, status, is_registered,
|
||||
has_account, is_email_verified, created_at, updated_at
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
||||
ON CONFLICT (username) DO NOTHING
|
||||
`, [
|
||||
user.username,
|
||||
user.email,
|
||||
user.type,
|
||||
user.role,
|
||||
user.status,
|
||||
user.is_registered,
|
||||
false, // has_account
|
||||
true, // is_email_verified
|
||||
])
|
||||
}
|
||||
|
||||
console.log(`Инициализировано ${users.length} пользователей в PostgreSQL`)
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Ошибка инициализации пользователей в PostgreSQL:', error)
|
||||
throw error
|
||||
}
|
||||
finally {
|
||||
await client.end()
|
||||
}
|
||||
}
|
||||
|
||||
export async function initVaultInPostgres() {
|
||||
const client = new Client({
|
||||
host: process.env.POSTGRES_HOST,
|
||||
port: parseInt(process.env.POSTGRES_PORT || '5432'),
|
||||
user: process.env.POSTGRES_USERNAME,
|
||||
password: process.env.POSTGRES_PASSWORD,
|
||||
database: process.env.POSTGRES_DATABASE,
|
||||
})
|
||||
|
||||
try {
|
||||
await client.connect()
|
||||
console.log('Подключение к PostgreSQL установлено для инициализации vault')
|
||||
|
||||
// Создаем таблицу vaults, если она не существует
|
||||
await client.query(`
|
||||
CREATE TABLE IF NOT EXISTS "vaults" (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
username VARCHAR(50) NOT NULL,
|
||||
permission VARCHAR(20) DEFAULT 'active',
|
||||
wif TEXT NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(username, permission)
|
||||
)
|
||||
`)
|
||||
|
||||
// Создаем индексы
|
||||
await client.query(`CREATE INDEX IF NOT EXISTS idx_vaults_username ON "vaults"(username)`)
|
||||
await client.query(`CREATE INDEX IF NOT EXISTS idx_vaults_username_permission ON "vaults"(username, permission)`)
|
||||
|
||||
// Сохраняем зашифрованный ключ в vault
|
||||
await client.query(`
|
||||
INSERT INTO "vaults" (username, permission, wif, created_at, updated_at)
|
||||
VALUES ($1, $2, $3, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
||||
ON CONFLICT (username, permission) DO NOTHING
|
||||
`, [
|
||||
'voskhod',
|
||||
'active',
|
||||
'9d6479a9d77ead53fb0e5e54b3608a95:2046ee3c1577d48aecbee49e8f25c4c2df37ab02f15d73d0d1b6352f53a4b774cb9e71b6028fd7caf64568e195c7878dfbb5d2bf10a3766d90ba9e92ea724428',
|
||||
])
|
||||
|
||||
console.log('Vault инициализирован в PostgreSQL')
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Ошибка инициализации vault в PostgreSQL:', error)
|
||||
throw error
|
||||
}
|
||||
finally {
|
||||
await client.end()
|
||||
}
|
||||
}
|
||||
|
||||
export async function initExtensionsInPostgres() {
|
||||
const client = new Client({
|
||||
host: process.env.POSTGRES_HOST,
|
||||
port: parseInt(process.env.POSTGRES_PORT || '5432'),
|
||||
user: process.env.POSTGRES_USERNAME,
|
||||
password: process.env.POSTGRES_PASSWORD,
|
||||
database: process.env.POSTGRES_DATABASE,
|
||||
})
|
||||
|
||||
try {
|
||||
await client.connect()
|
||||
console.log('Подключение к PostgreSQL установлено для инициализации extensions')
|
||||
|
||||
// Создаем таблицу extensions по аналогии с ExtensionEntity
|
||||
await client.query(`
|
||||
CREATE TABLE IF NOT EXISTS "extensions" (
|
||||
name VARCHAR(12) PRIMARY KEY,
|
||||
enabled BOOLEAN DEFAULT true,
|
||||
config JSONB DEFAULT '{}',
|
||||
schema_version INTEGER DEFAULT 1,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`)
|
||||
|
||||
// Создаем индексы
|
||||
await client.query(`CREATE INDEX IF NOT EXISTS idx_extensions_name ON "extensions"(name)`)
|
||||
await client.query(`CREATE INDEX IF NOT EXISTS idx_extensions_enabled ON "extensions"(enabled)`)
|
||||
|
||||
// Вставляем запись для capital extension
|
||||
const capitalConfig = {
|
||||
level_depth_base: 100000000,
|
||||
github_repository: "coopenomics/results-test",
|
||||
onboarding_init_at: "2026-02-09T07:16:18.380Z",
|
||||
expense_pool_percent: 100,
|
||||
onboarding_expire_at: "2026-03-11T07:16:18.380Z",
|
||||
voting_period_in_days: 1,
|
||||
authors_voting_percent: 62.8,
|
||||
creators_voting_percent: 62.8,
|
||||
energy_gain_coefficient: 1,
|
||||
level_growth_coefficient: 1.5,
|
||||
coordinator_bonus_percent: 5,
|
||||
energy_decay_rate_per_day: 0.02,
|
||||
coordinator_invite_validity_days: 30,
|
||||
onboarding_blagorost_provision_done: true,
|
||||
onboarding_blagorost_provision_hash: "CDE57D987E3C945E79E108920CE02A4A80CFA7980CAA912949BB6C2111B7027A",
|
||||
onboarding_blagorost_offer_template_done: true,
|
||||
onboarding_blagorost_offer_template_hash: "5CA88BBD303E5CCDA01E565FFE47E51855515176EC6C957F0FBDBCA4C53DBFD2",
|
||||
onboarding_generator_offer_template_done: true,
|
||||
onboarding_generator_offer_template_hash: "8DA31574E8CC764C3A1FCAE1172726656A3DCDB1BB82AB0E567E2732070C3A44",
|
||||
onboarding_generator_program_template_done: true,
|
||||
onboarding_generator_program_template_hash: "E55564D8946C55C93490B5277968FC890FDCB10A049DB5B2E0FE9F67FDA80896",
|
||||
onboarding_generation_contract_template_done: true,
|
||||
onboarding_generation_contract_template_hash: "A4BD579D6130CCE2D8C34337DFA591807C1F028A148DD53689881B12AC2627E2"
|
||||
}
|
||||
|
||||
await client.query(`
|
||||
INSERT INTO "extensions" (name, enabled, config, schema_version, created_at, updated_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)
|
||||
ON CONFLICT (name) DO UPDATE SET
|
||||
enabled = EXCLUDED.enabled,
|
||||
config = EXCLUDED.config,
|
||||
schema_version = EXCLUDED.schema_version,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
`, [
|
||||
'capital',
|
||||
true,
|
||||
JSON.stringify(capitalConfig),
|
||||
1,
|
||||
new Date('2026-02-09T02:13:06.620Z'),
|
||||
new Date('2026-02-09T02:27:57.155Z')
|
||||
])
|
||||
|
||||
console.log('Extensions инициализированы в PostgreSQL')
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Ошибка инициализации extensions в PostgreSQL:', error)
|
||||
throw error
|
||||
}
|
||||
finally {
|
||||
await client.end()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,454 @@
|
||||
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
|
||||
import { CapitalContract, SovietContract } from 'cooptypes'
|
||||
import Blockchain from '../blockchain'
|
||||
import config from '../configs'
|
||||
import { getTotalRamUsage, globalRamStats } from '../utils/getTotalRamUsage'
|
||||
import { addUser, addUser2 } from '../init/participant'
|
||||
import { generateRandomUsername } from '../utils/randomUsername'
|
||||
import { generateRandomSHA256 } from '../utils/randomHash'
|
||||
import { sleep } from '../utils'
|
||||
import { CooperativeClass } from '../init/cooperative'
|
||||
import { getCirculationAccount, getCoopProgramWallet, getLedgerAccountById, getUserProgramWalletAmount } from './wallet/walletUtils'
|
||||
import { registerContributor } from './capital/registerContributor'
|
||||
import { signAppendix } from './capital/signAppendix'
|
||||
import { capitalProgramId, circulationAccountId, sourceProgramId } from './capital/consts'
|
||||
import { commitToResult } from './capital/commitToResult'
|
||||
import { refreshSegment } from './capital/refreshSegment'
|
||||
import { signWalletAgreement } from './wallet/signWalletAgreement'
|
||||
import { fakeDocument } from './shared/fakeDocument'
|
||||
import { depositToWallet } from './wallet/depositToWallet'
|
||||
import { signCapitalAgreement } from './capital/signCapitalAgreement'
|
||||
import { addAuthor } from './capital/addAuthor'
|
||||
import { getSegment } from './capital/getSegment'
|
||||
import { investInProject } from './capital/investInProject'
|
||||
import { processDebt } from './capital/processDebt'
|
||||
import { processCreateProjectProperty } from './capital/processCreateProjectProperty'
|
||||
import { processCreateProgramProperty } from './capital/processCreateProgramProperty'
|
||||
import { processStartVoting } from './capital/processStartVoting'
|
||||
import { createVoteDistribution, submitVote } from './capital/submitVote'
|
||||
import { processCompleteVoting } from './capital/processCompleteVoting'
|
||||
import { processPushResult } from './capital/processPushResult'
|
||||
import { processConvertSegment } from './capital/processConvertSegment'
|
||||
import { processCalculateVotes } from './capital/processCalculateVotes'
|
||||
import { processAddContributor } from './capital/processAddContributor'
|
||||
import { processDeleteProject } from './capital/processDeleteProject'
|
||||
import { getProject } from './capital/getProject'
|
||||
import { processFundProgram } from './capital/processFundProgram'
|
||||
import { processRefreshProg } from './capital/processRefreshProg'
|
||||
import { processFundProject } from './capital/processFundProject'
|
||||
import { processRefreshProj } from './capital/processRefreshProj'
|
||||
// const CLI_PATH = 'src/index.ts'
|
||||
|
||||
const blockchain = new Blockchain(config.network, config.private_keys)
|
||||
let metaProject: CapitalContract.Actions.CreateProject.ICreateProject
|
||||
let componentProject: CapitalContract.Actions.CreateProject.ICreateProject
|
||||
let newComponentProject: CapitalContract.Actions.CreateProject.ICreateProject
|
||||
let testCoop: string
|
||||
let chairman: string
|
||||
|
||||
let tester1: string
|
||||
let tester2: string
|
||||
let tester3: string
|
||||
let tester4: string
|
||||
let tester5: string
|
||||
|
||||
let investor1: string
|
||||
let investor2: string
|
||||
let investor3: string
|
||||
// let result1: CapitalContract.Actions.CreateResult.ICreateResult
|
||||
// let result2: CapitalContract.Actions.CreateResult.ICreateResult
|
||||
// let result3: CapitalContract.Actions.CreateResult.ICreateResult
|
||||
|
||||
const commits: string[] = []
|
||||
const tester1CommitHours = 10
|
||||
const tester2CommitHours = 20
|
||||
const tester3CommitHours = 100
|
||||
const investAmount1 = 100000
|
||||
const investAmount2 = 25000
|
||||
const investAmount3 = 25000
|
||||
|
||||
const totalToCapitalConvertAmount = 0
|
||||
const totalToProjectConvertAmount = 0
|
||||
// Хранение хэшей долгов для каждого пользователя
|
||||
let userDebtHashes: { [username: string]: string[] } = {}
|
||||
beforeAll(async () => {
|
||||
testCoop = generateRandomUsername()
|
||||
chairman = generateRandomUsername()
|
||||
|
||||
await blockchain.update_pass_instance()
|
||||
|
||||
const coop = new CooperativeClass(blockchain)
|
||||
|
||||
await coop.createCooperative(testCoop, {
|
||||
privateKey: process.env.EOSIO_PRV_KEY!,
|
||||
publicKey: process.env.EOSIO_PUB_KEY!,
|
||||
})
|
||||
|
||||
await blockchain.preInit({
|
||||
coopname: testCoop,
|
||||
username: config.provider,
|
||||
status: 'active',
|
||||
})
|
||||
|
||||
await addUser2(testCoop, chairman)
|
||||
|
||||
await blockchain.createBoard({
|
||||
coopname: testCoop,
|
||||
username: testCoop,
|
||||
type: 'soviet',
|
||||
members: [{
|
||||
username: chairman,
|
||||
is_voting: true,
|
||||
position_title: 'Председатель совета',
|
||||
position: 'chairman',
|
||||
}],
|
||||
name: 'Совет',
|
||||
description: '',
|
||||
})
|
||||
|
||||
console.log('testcoop: ', testCoop)
|
||||
|
||||
investor1 = generateRandomUsername()
|
||||
console.log('investor1: ', investor1)
|
||||
await addUser2(testCoop, investor1)
|
||||
|
||||
tester1 = generateRandomUsername()
|
||||
console.log('tester1: ', tester1)
|
||||
await addUser2(testCoop, tester1)
|
||||
|
||||
tester2 = generateRandomUsername()
|
||||
console.log('tester2: ', tester2)
|
||||
await addUser2(testCoop, tester2)
|
||||
|
||||
tester3 = generateRandomUsername()
|
||||
console.log('tester3: ', tester3)
|
||||
await addUser2(testCoop, tester3)
|
||||
|
||||
tester4 = generateRandomUsername()
|
||||
console.log('tester4: ', tester4)
|
||||
await addUser2(testCoop, tester4)
|
||||
|
||||
tester5 = generateRandomUsername()
|
||||
console.log('tester5: ', tester5)
|
||||
await addUser2(testCoop, tester5)
|
||||
|
||||
// Инициализируем массивы хэшей долгов для каждого пользователя
|
||||
userDebtHashes = {
|
||||
[tester1]: [],
|
||||
[tester2]: [],
|
||||
[tester3]: [],
|
||||
[tester4]: [],
|
||||
[tester5]: [],
|
||||
[investor1]: [],
|
||||
[investor2]: [],
|
||||
[investor3]: [],
|
||||
}
|
||||
|
||||
investor2 = generateRandomUsername()
|
||||
console.log('investor2: ', investor2)
|
||||
await addUser2(testCoop, investor2, tester4)
|
||||
|
||||
investor3 = generateRandomUsername()
|
||||
console.log('investor3: ', investor3)
|
||||
await addUser2(testCoop, investor3, tester5)
|
||||
|
||||
// const { stdout } = await execa('esno', [CLI_PATH, 'boot'], { stdio: 'inherit' })
|
||||
// expect(stdout).toContain('Boot process completed')
|
||||
}, 500_000)
|
||||
|
||||
afterAll(() => {
|
||||
console.log('\n📊 **RAM USAGE SUMMARY** 📊')
|
||||
let totalGlobalRam = 0
|
||||
|
||||
for (const [key, ramUsed] of Object.entries(globalRamStats)) {
|
||||
const ramKb = (ramUsed / 1024).toFixed(2)
|
||||
console.log(` ${key} = ${ramKb} kb`)
|
||||
totalGlobalRam += ramUsed
|
||||
}
|
||||
|
||||
console.log(`\n💾 **TOTAL RAM USED IN TESTS**: ${(totalGlobalRam / 1024).toFixed(2)} kb\n`)
|
||||
|
||||
console.log(`testCoop: ${testCoop}\ntester1: ${tester1}\ntester2: ${tester2}\ntester3: ${tester3}\ntester4: ${tester4}\ntester5: ${tester5}\ninvestor1: ${investor1}\ninvestor2: ${investor2}\ninvestor3: ${investor3}`)
|
||||
})
|
||||
|
||||
describe('тест контракта CAPITAL', () => {
|
||||
it('создаём программу генерации', async () => {
|
||||
const program = await getCoopProgramWallet(blockchain, testCoop, sourceProgramId)
|
||||
if (!program) {
|
||||
const data: SovietContract.Actions.Programs.CreateProgram.ICreateProgram = {
|
||||
coopname: testCoop,
|
||||
is_can_coop_spend_share_contributions: true,
|
||||
username: chairman,
|
||||
title: 'Договор УХД',
|
||||
announce: '',
|
||||
description: '',
|
||||
preview: '',
|
||||
images: '',
|
||||
calculation_type: 'free',
|
||||
fixed_membership_contribution: '0.0000 RUB',
|
||||
membership_percent_fee: '0',
|
||||
meta: '',
|
||||
type: 'generator',
|
||||
}
|
||||
|
||||
const result = await blockchain.api.transact(
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
account: SovietContract.contractName.production,
|
||||
name: SovietContract.Actions.Programs.CreateProgram.actionName,
|
||||
authorization: [
|
||||
{
|
||||
actor: chairman,
|
||||
permission: 'active',
|
||||
},
|
||||
],
|
||||
data,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
blocksBehind: 3,
|
||||
expireSeconds: 30,
|
||||
},
|
||||
)
|
||||
getTotalRamUsage(result)
|
||||
expect(result.transaction_id).toBeDefined()
|
||||
}
|
||||
})
|
||||
|
||||
it('создаём программу капитализации', async () => {
|
||||
const program = await getCoopProgramWallet(blockchain, testCoop, capitalProgramId)
|
||||
|
||||
if (!program) {
|
||||
const data: SovietContract.Actions.Programs.CreateProgram.ICreateProgram = {
|
||||
coopname: testCoop,
|
||||
is_can_coop_spend_share_contributions: false,
|
||||
username: chairman,
|
||||
title: 'Капитализация',
|
||||
announce: '',
|
||||
description: '',
|
||||
preview: '',
|
||||
images: '',
|
||||
calculation_type: 'free',
|
||||
fixed_membership_contribution: '0.0000 RUB',
|
||||
membership_percent_fee: '0',
|
||||
meta: '',
|
||||
type: 'blagorost',
|
||||
}
|
||||
|
||||
const result = await blockchain.api.transact(
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
account: SovietContract.contractName.production,
|
||||
name: SovietContract.Actions.Programs.CreateProgram.actionName,
|
||||
authorization: [
|
||||
{
|
||||
actor: chairman,
|
||||
permission: 'active',
|
||||
},
|
||||
],
|
||||
data,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
blocksBehind: 3,
|
||||
expireSeconds: 30,
|
||||
},
|
||||
)
|
||||
getTotalRamUsage(result)
|
||||
expect(result.transaction_id).toBeDefined()
|
||||
}
|
||||
})
|
||||
|
||||
it('импорт вкладчика ДО установки конфига - должен работать', async () => {
|
||||
// Создаем тестового пользователя для импорта
|
||||
const importUsername = generateRandomUsername()
|
||||
console.log('importUsername: ', importUsername)
|
||||
await addUser2(testCoop, importUsername)
|
||||
|
||||
const contributorHash = generateRandomSHA256()
|
||||
const contributionAmount = '10000.0000 RUB'
|
||||
|
||||
// Получаем состояние паевого счета ДО импорта
|
||||
const shareFundBefore = await getCirculationAccount(blockchain, testCoop)
|
||||
const shareFundBalanceBefore = parseFloat(shareFundBefore.available)
|
||||
|
||||
console.log('Share fund before import:', shareFundBalanceBefore)
|
||||
|
||||
// Импортируем вкладчика
|
||||
const importData: CapitalContract.Actions.ImportContributor.IImportContributor = {
|
||||
coopname: testCoop,
|
||||
username: importUsername,
|
||||
contributor_hash: contributorHash,
|
||||
contribution_amount: contributionAmount,
|
||||
memo: 'договор №123 от такого то числа',
|
||||
}
|
||||
|
||||
const result = await blockchain.api.transact(
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
account: CapitalContract.contractName.production,
|
||||
name: CapitalContract.Actions.ImportContributor.actionName,
|
||||
authorization: [
|
||||
{
|
||||
actor: testCoop,
|
||||
permission: 'active',
|
||||
},
|
||||
],
|
||||
data: importData,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
blocksBehind: 3,
|
||||
expireSeconds: 30,
|
||||
},
|
||||
)
|
||||
getTotalRamUsage(result)
|
||||
expect(result.transaction_id).toBeDefined()
|
||||
|
||||
// Проверяем, что вкладчик был создан
|
||||
const contributorTable = await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
testCoop,
|
||||
'contributors',
|
||||
)
|
||||
console.log('contributorTable: ', contributorTable)
|
||||
|
||||
expect(contributorTable.length).toBe(1)
|
||||
const contributor = contributorTable[0]
|
||||
expect(contributor.username).toBe(importUsername)
|
||||
expect(contributor.status).toBe('active')
|
||||
|
||||
// Проверяем, что кошелек программы был создан
|
||||
const wallet = await getUserProgramWalletAmount(blockchain, testCoop, importUsername, capitalProgramId)
|
||||
expect(parseFloat(wallet)).toBe(parseFloat(contributionAmount))
|
||||
|
||||
// Проверяем увеличение паевого счета
|
||||
const shareFundAfter = await getCirculationAccount(blockchain, testCoop)
|
||||
const shareFundBalanceAfter = parseFloat(shareFundAfter.available)
|
||||
console.log('Share fund after import:', shareFundBalanceAfter)
|
||||
|
||||
expect(shareFundBalanceAfter).toBe(shareFundBalanceBefore + parseFloat(contributionAmount))
|
||||
})
|
||||
|
||||
it('инициализируем контракт CAPITAL и сверяем конфиг', async () => {
|
||||
const data: CapitalContract.Actions.SetConfig.ISetConfig = {
|
||||
coopname: testCoop,
|
||||
config: {
|
||||
coordinator_bonus_percent: 4,
|
||||
expense_pool_percent: 100,
|
||||
coordinator_invite_validity_days: 30,
|
||||
voting_period_in_days: 7,
|
||||
authors_voting_percent: 38.2,
|
||||
creators_voting_percent: 38.2,
|
||||
energy_decay_rate_per_day: 0.11,
|
||||
level_depth_base: 1000,
|
||||
level_growth_coefficient: 1.5,
|
||||
energy_gain_coefficient: 0.01,
|
||||
},
|
||||
}
|
||||
|
||||
const result = await blockchain.api.transact(
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
account: CapitalContract.contractName.production,
|
||||
name: CapitalContract.Actions.SetConfig.actionName,
|
||||
authorization: [
|
||||
{
|
||||
actor: testCoop,
|
||||
permission: 'active',
|
||||
},
|
||||
],
|
||||
data,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
blocksBehind: 3,
|
||||
expireSeconds: 30,
|
||||
},
|
||||
)
|
||||
getTotalRamUsage(result)
|
||||
expect(result.transaction_id).toBeDefined()
|
||||
|
||||
const state = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
CapitalContract.contractName.production,
|
||||
'state',
|
||||
1,
|
||||
testCoop,
|
||||
testCoop,
|
||||
))[0]
|
||||
console.log('state: ', state)
|
||||
expect(state).toBeDefined()
|
||||
expect(state.coopname).toBe(testCoop)
|
||||
expect(parseFloat(state.config.coordinator_bonus_percent)).toBe(data.config.coordinator_bonus_percent)
|
||||
expect(parseFloat(state.config.expense_pool_percent)).toBe(data.config.expense_pool_percent)
|
||||
expect(parseFloat(state.config.coordinator_invite_validity_days)).toBe(data.config.coordinator_invite_validity_days)
|
||||
expect(parseFloat(state.config.voting_period_in_days)).toBe(data.config.voting_period_in_days)
|
||||
expect(parseFloat(state.config.authors_voting_percent)).toBe(data.config.authors_voting_percent)
|
||||
expect(parseFloat(state.config.creators_voting_percent)).toBe(data.config.creators_voting_percent)
|
||||
})
|
||||
|
||||
it('импорт вкладчика ПОСЛЕ установки конфига - должен провалиться', async () => {
|
||||
// Создаем еще одного тестового пользователя для импорта
|
||||
const importUsername2 = generateRandomUsername()
|
||||
console.log('importUsername2: ', importUsername2)
|
||||
await addUser(importUsername2)
|
||||
|
||||
const contributorHash2 = generateRandomSHA256()
|
||||
const contributionAmount2 = '5000.0000 RUB'
|
||||
|
||||
// Пытаемся импортировать вкладчика после установки конфига
|
||||
const importData2: CapitalContract.Actions.ImportContributor.IImportContributor = {
|
||||
coopname: testCoop,
|
||||
username: importUsername2,
|
||||
contributor_hash: contributorHash2,
|
||||
contribution_amount: contributionAmount2,
|
||||
memo: 'договор №123 от такого то числа',
|
||||
}
|
||||
|
||||
// Ожидаем, что транзакция провалится
|
||||
await expect(
|
||||
blockchain.api.transact(
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
account: CapitalContract.contractName.production,
|
||||
name: CapitalContract.Actions.ImportContributor.actionName,
|
||||
authorization: [
|
||||
{
|
||||
actor: testCoop,
|
||||
permission: 'active',
|
||||
},
|
||||
],
|
||||
data: importData2,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
blocksBehind: 3,
|
||||
expireSeconds: 30,
|
||||
},
|
||||
),
|
||||
).rejects.toThrow() // Ожидаем ошибку
|
||||
|
||||
// Проверяем, что вкладчик НЕ был создан
|
||||
const contributorTable = await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
testCoop,
|
||||
'contributors',
|
||||
)
|
||||
expect(contributorTable.length).toBe(1)
|
||||
|
||||
// Проверяем, что кошелек программы НЕ был создан
|
||||
const wallet = await getUserProgramWalletAmount(blockchain, testCoop, importUsername2, capitalProgramId)
|
||||
expect(wallet).toBe('0.0000 RUB')
|
||||
})
|
||||
})
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,75 @@
|
||||
import { CapitalContract } from 'cooptypes'
|
||||
import { expect } from 'vitest'
|
||||
import type Blockchain from '../../blockchain'
|
||||
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
|
||||
import { makeCombinedChecksum256NameIndexKey } from '../shared/combinedKeys'
|
||||
|
||||
export async function addAuthor(blockchain: Blockchain, coopname: string, project_hash: string, author: string) {
|
||||
const data: CapitalContract.Actions.AddAuthor.IAddAuthor = {
|
||||
coopname,
|
||||
project_hash,
|
||||
author,
|
||||
}
|
||||
|
||||
const result = await blockchain.api.transact(
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
account: CapitalContract.contractName.production,
|
||||
name: CapitalContract.Actions.AddAuthor.actionName,
|
||||
authorization: [
|
||||
{
|
||||
actor: coopname,
|
||||
permission: 'active',
|
||||
},
|
||||
],
|
||||
data,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
blocksBehind: 3,
|
||||
expireSeconds: 30,
|
||||
},
|
||||
)
|
||||
getTotalRamUsage(result)
|
||||
expect(result.transaction_id).toBeDefined()
|
||||
|
||||
const key = makeCombinedChecksum256NameIndexKey(project_hash, author)
|
||||
const rows = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
CapitalContract.Tables.Segments.tableName,
|
||||
1000,
|
||||
key,
|
||||
key,
|
||||
3,
|
||||
'i128',
|
||||
))
|
||||
|
||||
const segment = rows[0]
|
||||
|
||||
console.log('segment: ', segment)
|
||||
expect(segment).toBeDefined()
|
||||
expect(segment.username).toBe(author)
|
||||
expect(segment.project_hash).toBe(project_hash)
|
||||
expect(segment.is_author).toBe(1)
|
||||
|
||||
const project = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
CapitalContract.Tables.Projects.tableName,
|
||||
1000,
|
||||
project_hash,
|
||||
project_hash,
|
||||
3,
|
||||
'sha256',
|
||||
))[0]
|
||||
|
||||
expect(project).toBeDefined()
|
||||
|
||||
return {
|
||||
project,
|
||||
segment,
|
||||
}
|
||||
}
|
||||
@@ -2,34 +2,20 @@ import { expect } from 'vitest'
|
||||
import { CapitalContract, SovietContract } from 'cooptypes'
|
||||
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
|
||||
import { generateRandomSHA256 } from '../../utils/randomHash'
|
||||
import { fakeDocument } from '../soviet/fakeDocument'
|
||||
import { processDecision } from '../soviet/processDecision'
|
||||
import { ratePerHour } from './consts'
|
||||
import { fakeDocument } from '../shared/fakeDocument'
|
||||
import { processMasterApprove } from './processMasterApprove'
|
||||
|
||||
export async function commitToResult(
|
||||
blockchain: any,
|
||||
coopname: string,
|
||||
resultHash: string,
|
||||
projectHash: string,
|
||||
creator: string,
|
||||
spendHours: number,
|
||||
) {
|
||||
const commitHash = generateRandomSHA256()
|
||||
const totalSpended = `${(spendHours * parseFloat(ratePerHour)).toFixed(4)} RUB`
|
||||
console.log(`\n🔹 Начало коммита: ${commitHash}, часы: ${spendHours}, сумма: ${totalSpended}`)
|
||||
|
||||
// Получение текущих данных перед коммитом
|
||||
const prevResult = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'results',
|
||||
1,
|
||||
resultHash,
|
||||
resultHash,
|
||||
2,
|
||||
'sha256',
|
||||
))[0] || { spended: '0.0000 RUB', commits_count: 0 }
|
||||
console.log(`\n🔹 Начало коммита: ${commitHash}, проект: ${projectHash}, часы: ${spendHours}`)
|
||||
|
||||
// Получение текущих данных проекта перед коммитом
|
||||
const prevProject = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
@@ -53,21 +39,34 @@ export async function commitToResult(
|
||||
))[0] || { contributed_hours: 0, available: '0.0000 RUB' }
|
||||
|
||||
console.log('📊 Балансы до коммита:')
|
||||
console.log('▶ Результат:', prevResult)
|
||||
console.log('▶ Проект:', prevProject)
|
||||
console.log('▶ Контрибьютор:', prevContributor)
|
||||
|
||||
// Обновим состояние проекта после возможной смены статуса
|
||||
const projectBefore = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'projects',
|
||||
1,
|
||||
projectHash,
|
||||
projectHash,
|
||||
3,
|
||||
'sha256',
|
||||
))[0]
|
||||
const prevTotalCommits = projectBefore?.counts?.total_commits ?? 0
|
||||
const prevCreatorsHours = projectBefore?.fact?.creators_hours ?? 0
|
||||
|
||||
// Создание коммита
|
||||
const commitData: CapitalContract.Actions.CreateCommit.ICommit = {
|
||||
coopname,
|
||||
application: coopname,
|
||||
result_hash: resultHash,
|
||||
commit_hash: commitHash,
|
||||
contributed_hours: spendHours,
|
||||
username: creator,
|
||||
project_hash: projectHash,
|
||||
commit_hash: commitHash,
|
||||
creator_hours: spendHours,
|
||||
description: `Коммит ${commitHash}`,
|
||||
meta: `{"hours": ${spendHours}, "creator": "${creator}"}`,
|
||||
}
|
||||
|
||||
console.log(`\n🚀 Отправка транзакции CreateCommit для результата ${resultHash}`)
|
||||
const createCommitResult = await blockchain.api.transact(
|
||||
{
|
||||
actions: [
|
||||
@@ -89,7 +88,7 @@ export async function commitToResult(
|
||||
expect(createCommitResult.transaction_id).toBeDefined()
|
||||
|
||||
// Проверка созданного коммита
|
||||
let blockchainCommit = (await blockchain.getTableRows(
|
||||
const blockchainCommit = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'commits',
|
||||
@@ -103,39 +102,30 @@ export async function commitToResult(
|
||||
console.log('🔍 Коммит после создания:', blockchainCommit)
|
||||
expect(blockchainCommit).toBeDefined()
|
||||
expect(blockchainCommit.commit_hash).toBe(commitHash)
|
||||
expect(blockchainCommit.spended).toBe(totalSpended)
|
||||
expect(blockchainCommit.project_hash).toBe(projectHash)
|
||||
expect(blockchainCommit.status).toBe('created')
|
||||
|
||||
// Утверждение коммита
|
||||
const approveCommitData: SovietContract.Actions.Approves.ConfirmApprove.IConfirmApprove = {
|
||||
// Получение данных проекта для определения мастера
|
||||
const projectData = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
approval_hash: commitHash,
|
||||
approved_document: fakeDocument,
|
||||
}
|
||||
'projects',
|
||||
1,
|
||||
projectHash,
|
||||
projectHash,
|
||||
3,
|
||||
'sha256',
|
||||
))[0]
|
||||
|
||||
console.log(`\n✅ Подтверждение коммита ${commitHash}`)
|
||||
const approveCommitResult = await blockchain.api.transact(
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
account: SovietContract.contractName.production,
|
||||
name: SovietContract.Actions.Approves.ConfirmApprove.actionName,
|
||||
authorization: [{ actor: coopname, permission: 'active' }],
|
||||
data: approveCommitData,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
blocksBehind: 3,
|
||||
expireSeconds: 30,
|
||||
},
|
||||
)
|
||||
expect(projectData).toBeDefined()
|
||||
expect(projectData.master).toBeDefined()
|
||||
|
||||
getTotalRamUsage(approveCommitResult)
|
||||
expect(approveCommitResult.transaction_id).toBeDefined()
|
||||
// Утверждение коммита мастером проекта
|
||||
console.log(`\n✅ Подтверждение коммита ${commitHash} мастером ${projectData.master}`)
|
||||
const approveCommitResult = await processMasterApprove(blockchain, coopname, commitHash, projectData.master)
|
||||
|
||||
// Проверка утвержденного коммита
|
||||
blockchainCommit = (await blockchain.getTableRows(
|
||||
const blockchainEmptyCommit = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'commits',
|
||||
@@ -147,96 +137,9 @@ export async function commitToResult(
|
||||
))[0]
|
||||
|
||||
// Коммит удаляем после утверждения
|
||||
expect(blockchainCommit).toBeUndefined()
|
||||
|
||||
// // Получение всех решений и выполнение последнего
|
||||
// const decisions = await blockchain.getTableRows(
|
||||
// SovietContract.contractName.production,
|
||||
// coopname,
|
||||
// 'decisions',
|
||||
// 1000,
|
||||
// )
|
||||
// const lastDecision = decisions[decisions.length - 1]
|
||||
|
||||
// console.log(`\n📜 Выполнение последнего решения: ${lastDecision.id}`)
|
||||
// await processDecision(blockchain, lastDecision.id)
|
||||
|
||||
// {
|
||||
// // Утверждение коммита
|
||||
// const act1Data: CapitalContract.Actions.SetAct1.ISetAct1 = {
|
||||
// coopname,
|
||||
// application: coopname,
|
||||
// username: creator,
|
||||
// commit_hash: commitHash,
|
||||
// act: fakeDocument,
|
||||
// }
|
||||
|
||||
// console.log(`\n✅ Установка акта1 по коммиту ${commitHash}`)
|
||||
// const act1Result = await blockchain.api.transact(
|
||||
// {
|
||||
// actions: [
|
||||
// {
|
||||
// account: CapitalContract.contractName.production,
|
||||
// name: CapitalContract.Actions.SetAct1.actionName,
|
||||
// authorization: [{ actor: coopname, permission: 'active' }],
|
||||
// data: act1Data,
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// blocksBehind: 3,
|
||||
// expireSeconds: 30,
|
||||
// },
|
||||
// )
|
||||
|
||||
// getTotalRamUsage(act1Result)
|
||||
// expect(act1Result.transaction_id).toBeDefined()
|
||||
// }
|
||||
|
||||
// {
|
||||
// // Утверждение коммита
|
||||
// const act2Data: CapitalContract.Actions.SetAct2.ISetAct2 = {
|
||||
// coopname,
|
||||
// application: coopname,
|
||||
// username: creator,
|
||||
// commit_hash: commitHash,
|
||||
// act: fakeDocument,
|
||||
// }
|
||||
|
||||
// console.log(`\n✅ Установка акта2 по коммиту ${commitHash}`)
|
||||
// const act2Result = await blockchain.api.transact(
|
||||
// {
|
||||
// actions: [
|
||||
// {
|
||||
// account: CapitalContract.contractName.production,
|
||||
// name: CapitalContract.Actions.SetAct2.actionName,
|
||||
// authorization: [{ actor: coopname, permission: 'active' }],
|
||||
// data: act2Data,
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// blocksBehind: 3,
|
||||
// expireSeconds: 30,
|
||||
// },
|
||||
// )
|
||||
|
||||
// getTotalRamUsage(act2Result)
|
||||
// expect(act2Result.transaction_id).toBeDefined()
|
||||
// }
|
||||
|
||||
// Проверка результата после утверждения коммита
|
||||
const finalResult = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'results',
|
||||
1,
|
||||
resultHash,
|
||||
resultHash,
|
||||
2,
|
||||
'sha256',
|
||||
))[0]
|
||||
expect(blockchainEmptyCommit).toBeUndefined()
|
||||
|
||||
// Проверка проекта после утверждения коммита
|
||||
const finalProject = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
@@ -260,23 +163,19 @@ export async function commitToResult(
|
||||
))[0]
|
||||
|
||||
console.log('\n📊 Балансы после утверждения коммита:')
|
||||
console.log('▶ Результат:', finalResult)
|
||||
console.log('▶ Проект:', finalProject)
|
||||
console.log('▶ Контрибьютор:', finalContributor)
|
||||
|
||||
// expect(parseFloat(finalResult.spended)).toBe(parseFloat(prevResult.spended) + parseFloat(totalSpended))
|
||||
// expect(parseFloat(finalProject.spended)).toBe(parseFloat(prevProject.spended))
|
||||
|
||||
// Проверка, что у контрибьютора увеличились contributed_hours и available
|
||||
// expect(parseFloat(finalContributor.contributed_hours)).toBe(parseFloat(prevContributor.contributed_hours) + spendHours)
|
||||
// expect(parseFloat(finalContributor.available)).toBe(parseFloat(prevContributor.available) + parseFloat(totalSpended))
|
||||
// Проверки по проекту
|
||||
expect(finalProject.counts.total_commits).toBe(prevTotalCommits + 1)
|
||||
expect(parseInt(finalProject.fact.creators_hours)).toBe(prevCreatorsHours + spendHours)
|
||||
|
||||
console.log(`\n✅ Коммит ${commitHash} завершен успешно!`)
|
||||
|
||||
return {
|
||||
commitHash,
|
||||
commit: blockchainCommit,
|
||||
transactionId: approveCommitResult.transaction_id,
|
||||
finalResult,
|
||||
transaction: approveCommitResult,
|
||||
finalProject,
|
||||
finalContributor,
|
||||
}
|
||||
|
||||
@@ -2,5 +2,6 @@ export const sourceProgramId = 3
|
||||
export const capitalProgramId = 4
|
||||
export const walletProgramId = 1
|
||||
export const ratePerHour = '1000.0000 RUB'
|
||||
export const sourceProgramName = 'source'
|
||||
export const capitalProgramName = 'capital'
|
||||
export const sourceProgramName = 'generator'
|
||||
export const capitalProgramName = 'blagorost'
|
||||
export const circulationAccountId = 80
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { CapitalContract } from 'cooptypes'
|
||||
import type Blockchain from '../../blockchain'
|
||||
|
||||
/**
|
||||
* Получает кошелек капитализации пользователя
|
||||
* @param blockchain экземпляр блокчейна
|
||||
* @param coopname название кооператива
|
||||
* @param username имя пользователя
|
||||
* @returns кошелек капитализации или null если не найден
|
||||
*/
|
||||
export async function getCapitalWallet(
|
||||
blockchain: Blockchain,
|
||||
coopname: string,
|
||||
username: string,
|
||||
) {
|
||||
const capitalWallets = await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'capwallets',
|
||||
100,
|
||||
username,
|
||||
username,
|
||||
2, // by_username index
|
||||
)
|
||||
|
||||
return capitalWallets[0] || null
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { CapitalContract } from 'cooptypes'
|
||||
import type Blockchain from '../../blockchain'
|
||||
import { makeCombinedChecksum256NameIndexKey } from '../shared/combinedKeys'
|
||||
|
||||
export async function getProject(blockchain: Blockchain, coopname: string, project_hash: string): Promise<CapitalContract.Tables.Projects.IProject> {
|
||||
const rows = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
CapitalContract.Tables.Projects.tableName,
|
||||
1000,
|
||||
project_hash,
|
||||
project_hash,
|
||||
3,
|
||||
'sha256',
|
||||
))
|
||||
|
||||
return rows[0]
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { CapitalContract } from 'cooptypes'
|
||||
import type Blockchain from '../../blockchain'
|
||||
|
||||
/**
|
||||
* Получает кошелек проекта пользователя
|
||||
* @param blockchain экземпляр блокчейна
|
||||
* @param coopname название кооператива
|
||||
* @param projectHash хеш проекта
|
||||
* @param username имя пользователя
|
||||
* @returns кошелек проекта или null если не найден
|
||||
*/
|
||||
export async function getProjectWallet(
|
||||
blockchain: Blockchain,
|
||||
coopname: string,
|
||||
projectHash: string,
|
||||
username: string,
|
||||
) {
|
||||
const projectWallets = await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'projwallets',
|
||||
100,
|
||||
username,
|
||||
username,
|
||||
3, // by_username index
|
||||
)
|
||||
|
||||
// Ищем кошелек для конкретного проекта
|
||||
return projectWallets.find((wallet: any) => wallet.project_hash === projectHash) || null
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { CapitalContract } from 'cooptypes'
|
||||
import type Blockchain from '../../blockchain'
|
||||
import { makeCombinedChecksum256NameIndexKey } from '../shared/combinedKeys'
|
||||
|
||||
export async function getSegment(blockchain: Blockchain, coopname: string, project_hash: string, author: string): Promise<CapitalContract.Tables.Segments.ISegment> {
|
||||
const key = makeCombinedChecksum256NameIndexKey(project_hash, author)
|
||||
const rows = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
CapitalContract.Tables.Segments.tableName,
|
||||
1000,
|
||||
key,
|
||||
key,
|
||||
3,
|
||||
'i128',
|
||||
))
|
||||
|
||||
return rows[0]
|
||||
}
|
||||
@@ -4,7 +4,10 @@ import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
|
||||
import { generateRandomSHA256 } from '../../utils/randomHash'
|
||||
import { getCoopProgramWallet, getUserProgramWallet } from '../wallet/walletUtils'
|
||||
import { processDecision } from '../soviet/processDecision'
|
||||
import { capitalProgramId, sourceProgramId } from './consts'
|
||||
import { consoleIt } from '../shared'
|
||||
import { processApprove } from './processApprove'
|
||||
import { getSegment } from './getSegment'
|
||||
import { capitalProgramId, walletProgramId } from './consts'
|
||||
|
||||
export async function investInProject(
|
||||
blockchain: any,
|
||||
@@ -17,18 +20,6 @@ export async function investInProject(
|
||||
const investHash = generateRandomSHA256()
|
||||
console.log(`\n🔹 Начало инвестиции: ${investHash}`)
|
||||
|
||||
// Получение текущих данных перед инвестицией
|
||||
const prevContributor = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'contributors',
|
||||
1,
|
||||
investor,
|
||||
investor,
|
||||
2,
|
||||
'i64',
|
||||
))[0] || { invested: '0.0000 RUB' }
|
||||
|
||||
const prevProject = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
@@ -40,19 +31,19 @@ export async function investInProject(
|
||||
'sha256',
|
||||
))[0] || { invested: '0.0000 RUB', available: '0.0000 RUB' }
|
||||
|
||||
const prevWalletWallet = await getUserProgramWallet(blockchain, coopname, investor, walletProgramId) || { blocked: '0.0000 RUB' }
|
||||
const prevUserWallet = await getUserProgramWallet(blockchain, coopname, investor, capitalProgramId) || { blocked: '0.0000 RUB' }
|
||||
const prevProgramWallet = await getCoopProgramWallet(blockchain, coopname, capitalProgramId) || { blocked: '0.0000 RUB', share_contributions: '0.0000 RUB' }
|
||||
|
||||
console.log('📊 Балансы до инвестиции:')
|
||||
console.log('▶ Контрибьютор:', prevContributor)
|
||||
console.log('▶ Проект:', prevProject)
|
||||
console.log('▶ Кошелек пользователя:', prevUserWallet)
|
||||
console.log('▶ Кошелек программы:', prevProgramWallet)
|
||||
|
||||
console.log('▶ Главный кошелек пользователя:', prevWalletWallet)
|
||||
console.log('▶ Кошелек пользователя (благорост):', prevUserWallet)
|
||||
console.log('▶ Кошелек программы (благорост):', prevProgramWallet)
|
||||
console.log('▶ Сумма инвестиции: ', investAmount)
|
||||
// Создание инвестиции
|
||||
const createInvestData: CapitalContract.Actions.CreateInvest.ICreateInvest = {
|
||||
const createInvestData: CapitalContract.Actions.CreateProjectInvest.ICreateInvest = {
|
||||
coopname,
|
||||
application: coopname,
|
||||
project_hash: projectHash,
|
||||
username: investor,
|
||||
invest_hash: investHash,
|
||||
@@ -66,7 +57,7 @@ export async function investInProject(
|
||||
actions: [
|
||||
{
|
||||
account: CapitalContract.contractName.production,
|
||||
name: CapitalContract.Actions.CreateInvest.actionName,
|
||||
name: CapitalContract.Actions.CreateProjectInvest.actionName,
|
||||
authorization: [{ actor: coopname, permission: 'active' }],
|
||||
data: createInvestData,
|
||||
},
|
||||
@@ -77,56 +68,31 @@ export async function investInProject(
|
||||
expireSeconds: 30,
|
||||
},
|
||||
)
|
||||
|
||||
consoleIt(createInvestResult)
|
||||
getTotalRamUsage(createInvestResult)
|
||||
expect(createInvestResult.transaction_id).toBeDefined()
|
||||
|
||||
let blockchainInvest = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'invests',
|
||||
1,
|
||||
investHash,
|
||||
investHash,
|
||||
2,
|
||||
'sha256',
|
||||
))[0]
|
||||
// const blockchainInvest = (await blockchain.getTableRows(
|
||||
// CapitalContract.contractName.production,
|
||||
// coopname,
|
||||
// 'invests',
|
||||
// 1,
|
||||
// investHash,
|
||||
// investHash,
|
||||
// 2,
|
||||
// 'sha256',
|
||||
// ))[0]
|
||||
|
||||
console.log('🔍 Инвестиция в блокчейне:', blockchainInvest)
|
||||
expect(blockchainInvest).toBeDefined()
|
||||
expect(blockchainInvest.status).toBe('created')
|
||||
// console.log('🔍 Инвестиция в блокчейне:', blockchainInvest)
|
||||
// expect(blockchainInvest).toBeDefined()
|
||||
// expect(blockchainInvest.status).toBe('created')
|
||||
|
||||
// Утверждение инвестиции
|
||||
const approveInvestData: CapitalContract.Actions.ApproveInvest.IApproveInvest = {
|
||||
coopname,
|
||||
application: coopname,
|
||||
approver: 'ant',
|
||||
invest_hash: investHash,
|
||||
approved_statement: fakeDocument,
|
||||
}
|
||||
// console.log(`\n✅ Подтверждение инвестиции ${investHash}`)
|
||||
// const approveInvestResult = await processApprove(blockchain, coopname, investHash)
|
||||
|
||||
console.log(`\n✅ Подтверждение инвестиции ${investHash}`)
|
||||
const approveInvestResult = await blockchain.api.transact(
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
account: CapitalContract.contractName.production,
|
||||
name: CapitalContract.Actions.ApproveInvest.actionName,
|
||||
authorization: [{ actor: coopname, permission: 'active' }],
|
||||
data: approveInvestData,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
blocksBehind: 3,
|
||||
expireSeconds: 30,
|
||||
},
|
||||
)
|
||||
|
||||
getTotalRamUsage(approveInvestResult)
|
||||
expect(approveInvestResult.transaction_id).toBeDefined()
|
||||
|
||||
blockchainInvest = (await blockchain.getTableRows(
|
||||
// Проверка утвержденной инвестиции
|
||||
const blockchainEmptyInvest = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'invests',
|
||||
@@ -137,49 +103,10 @@ export async function investInProject(
|
||||
'sha256',
|
||||
))[0]
|
||||
|
||||
console.log('🔍 Инвестиция после утверждения:', blockchainInvest)
|
||||
expect(blockchainInvest).toBeDefined()
|
||||
expect(blockchainInvest.status).toBe('approved')
|
||||
// Инвестиция удаляется после утверждения
|
||||
expect(blockchainEmptyInvest).toBeUndefined()
|
||||
|
||||
// Получение всех решений и выполнение последнего
|
||||
const decisions = await blockchain.getTableRows(
|
||||
SovietContract.contractName.production,
|
||||
coopname,
|
||||
'decisions',
|
||||
1000,
|
||||
)
|
||||
const lastDecision = decisions[decisions.length - 1]
|
||||
|
||||
console.log(`\n📜 Выполнение последнего решения: ${lastDecision.id}`)
|
||||
await processDecision(blockchain, lastDecision.id)
|
||||
|
||||
blockchainInvest = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'invests',
|
||||
1,
|
||||
investHash,
|
||||
investHash,
|
||||
2,
|
||||
'sha256',
|
||||
))[0]
|
||||
|
||||
console.log('📌 Инвестиция после завершения:', blockchainInvest)
|
||||
expect(blockchainInvest).toBeUndefined()
|
||||
|
||||
// Получение новых данных после инвестиции
|
||||
const contributor = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'contributors',
|
||||
1,
|
||||
investor,
|
||||
investor,
|
||||
2,
|
||||
'i64',
|
||||
))[0]
|
||||
|
||||
const project = (await blockchain.getTableRows(
|
||||
const finalProject = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'projects',
|
||||
@@ -190,24 +117,30 @@ export async function investInProject(
|
||||
'sha256',
|
||||
))[0]
|
||||
|
||||
const userWallet = await getUserProgramWallet(blockchain, coopname, investor, capitalProgramId)
|
||||
const programWallet = await getCoopProgramWallet(blockchain, coopname, capitalProgramId)
|
||||
const finalUserWallet = await getUserProgramWallet(blockchain, coopname, investor, capitalProgramId)
|
||||
const finalProgramWallet = await getCoopProgramWallet(blockchain, coopname, capitalProgramId)
|
||||
|
||||
// Получение сегмента инвестора для данного проекта
|
||||
const segment = await getSegment(blockchain, coopname, projectHash, investor)
|
||||
|
||||
console.log('\n📊 Балансы после инвестиции:')
|
||||
console.log('▶ Контрибьютор:', contributor)
|
||||
console.log('▶ Проект:', project)
|
||||
console.log('▶ Кошелек пользователя:', userWallet)
|
||||
console.log('▶ Кошелек программы:', programWallet)
|
||||
console.log('▶ Проект:', finalProject)
|
||||
console.log('▶ Сегмент:', segment)
|
||||
console.log('▶ Кошелек пользователя (благорост):', finalUserWallet)
|
||||
console.log('▶ Кошелек программы (благорост):', finalProgramWallet)
|
||||
|
||||
// Проверка изменения балансов
|
||||
expect(parseFloat(contributor.invested)).toBe(parseFloat(prevContributor.invested) + parseFloat(investAmount))
|
||||
expect(parseFloat(project.invested)).toBe(parseFloat(prevProject.invested) + parseFloat(investAmount))
|
||||
expect(parseFloat(project.available)).toBe(parseFloat(prevProject.available) + parseFloat(investAmount))
|
||||
expect(parseFloat(userWallet.blocked)).toBe(parseFloat(prevUserWallet.blocked) + parseFloat(investAmount))
|
||||
expect(parseFloat(programWallet.blocked)).toBe(parseFloat(prevProgramWallet.blocked) + parseFloat(investAmount))
|
||||
expect(parseFloat(programWallet.share_contributions)).toBe(parseFloat(prevProgramWallet.share_contributions) + parseFloat(investAmount))
|
||||
expect(parseFloat(finalUserWallet.blocked)).toBeCloseTo(parseFloat(prevUserWallet.blocked) + parseFloat(investAmount), 1)
|
||||
expect(parseFloat(finalProgramWallet.blocked)).toBeCloseTo(parseFloat(prevProgramWallet.blocked) + parseFloat(investAmount), 1)
|
||||
|
||||
console.log(`\n✅ Инвестирование на ${investAmount} завершено успешно!`)
|
||||
|
||||
return { investHash, transactionId: approveInvestResult.transaction_id }
|
||||
return {
|
||||
investHash,
|
||||
// invest: blockchainInvest,
|
||||
transactionId: createInvestResult.transaction_id,
|
||||
prevProject,
|
||||
project: finalProject,
|
||||
segment,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import { expect } from 'vitest'
|
||||
import { CapitalContract } from 'cooptypes'
|
||||
import type Blockchain from '../../blockchain'
|
||||
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
|
||||
import { getSegment } from './getSegment'
|
||||
|
||||
export async function processAddContributor(
|
||||
blockchain: Blockchain,
|
||||
coopname: string,
|
||||
project_hash: string,
|
||||
username: string,
|
||||
) {
|
||||
// Состояние сегмента до добавления (может не существовать)
|
||||
let segmentBefore: any = null
|
||||
try {
|
||||
segmentBefore = await getSegment(blockchain, coopname, project_hash, username)
|
||||
}
|
||||
catch (error) {
|
||||
// Сегмент может не существовать - это нормально
|
||||
}
|
||||
|
||||
const data: CapitalContract.Actions.RegisterShare.IRegisterShare = {
|
||||
coopname,
|
||||
project_hash,
|
||||
username,
|
||||
}
|
||||
|
||||
// Выполняем добавление участника в проект
|
||||
const txAdd = await blockchain.api.transact(
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
account: CapitalContract.contractName.production,
|
||||
name: CapitalContract.Actions.RegisterShare.actionName,
|
||||
authorization: [{ actor: coopname, permission: 'active' }],
|
||||
data,
|
||||
},
|
||||
],
|
||||
},
|
||||
{ blocksBehind: 3, expireSeconds: 30 },
|
||||
)
|
||||
getTotalRamUsage(txAdd)
|
||||
expect(txAdd.transaction_id).toBeDefined()
|
||||
|
||||
// Состояние сегмента после добавления
|
||||
const segmentAfter = await getSegment(blockchain, coopname, project_hash, username)
|
||||
|
||||
return {
|
||||
transactionId: txAdd.transaction_id,
|
||||
segmentBefore,
|
||||
segmentAfter,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { expect } from 'vitest'
|
||||
import { SovietContract } from 'cooptypes'
|
||||
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
|
||||
import { fakeDocument } from '../shared/fakeDocument'
|
||||
|
||||
export async function processApprove(blockchain: any, coopname: string, approvalHash: string) {
|
||||
fakeDocument.signatures[0].signer = 'ant'
|
||||
|
||||
const data: SovietContract.Actions.Approves.ConfirmApprove.IConfirmApprove = {
|
||||
coopname,
|
||||
username: 'ant',
|
||||
approval_hash: approvalHash,
|
||||
approved_document: fakeDocument,
|
||||
}
|
||||
|
||||
const result = await blockchain.api.transact(
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
account: SovietContract.contractName.production,
|
||||
name: SovietContract.Actions.Approves.ConfirmApprove.actionName,
|
||||
authorization: [{ actor: coopname, permission: 'active' }],
|
||||
data,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
blocksBehind: 3,
|
||||
expireSeconds: 30,
|
||||
},
|
||||
)
|
||||
|
||||
getTotalRamUsage(result)
|
||||
expect(result.transaction_id).toBeDefined()
|
||||
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
import { expect } from 'vitest'
|
||||
import { CapitalContract } from 'cooptypes'
|
||||
import type Blockchain from '../../blockchain'
|
||||
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
|
||||
import { makeCombinedChecksum256NameIndexKey } from '../shared/combinedKeys'
|
||||
|
||||
/**
|
||||
* Функция для расчета голосов участника по методу Водянова
|
||||
* @param blockchain экземпляр блокчейна
|
||||
* @param data данные для расчета голосов
|
||||
*/
|
||||
export async function processCalculateVotes(
|
||||
blockchain: Blockchain,
|
||||
data: CapitalContract.Actions.CalculateVotes.IFinalVoting,
|
||||
) {
|
||||
const { coopname, username, project_hash } = data
|
||||
|
||||
console.log(`\n🧮 Начало расчета голосов для участника ${username} в проекте ${project_hash}`)
|
||||
|
||||
// Состояние до: данные проекта
|
||||
const projectBefore = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'projects',
|
||||
1,
|
||||
project_hash,
|
||||
project_hash,
|
||||
3,
|
||||
'sha256',
|
||||
))[0]
|
||||
|
||||
// Состояние до: данные сегмента участника
|
||||
const segmentKey = makeCombinedChecksum256NameIndexKey(project_hash, username)
|
||||
const segmentBefore = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'segments',
|
||||
1,
|
||||
segmentKey,
|
||||
segmentKey,
|
||||
3,
|
||||
'i128',
|
||||
))[0]
|
||||
|
||||
// console.log('📊 Состояние до расчета голосов:')
|
||||
// console.log('▶ Статус проекта:', projectBefore.status)
|
||||
// console.log('▶ Голосов получено:', projectBefore.voting.votes_received)
|
||||
// console.log('▶ Всего участников голосования:', projectBefore.voting.total_voters)
|
||||
// console.log(`▶ Сегмент участника ${username}:`)
|
||||
// console.log(' - voting_bonus:', segmentBefore.voting_bonus)
|
||||
// console.log(' - equal_author_bonus:', segmentBefore.equal_author_bonus)
|
||||
// console.log(' - direct_creator_bonus:', segmentBefore.direct_creator_bonus)
|
||||
// console.log(' - total_segment_cost:', segmentBefore.total_segment_cost)
|
||||
|
||||
// Отправляем транзакцию расчета голосов
|
||||
const txResult = await blockchain.api.transact(
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
account: CapitalContract.contractName.production,
|
||||
name: CapitalContract.Actions.CalculateVotes.actionName,
|
||||
authorization: [{ actor: coopname, permission: 'active' }],
|
||||
data,
|
||||
},
|
||||
],
|
||||
},
|
||||
{ blocksBehind: 3, expireSeconds: 30 },
|
||||
)
|
||||
getTotalRamUsage(txResult)
|
||||
expect(txResult.transaction_id).toBeDefined()
|
||||
|
||||
// Состояние после: данные проекта
|
||||
const projectAfter = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'projects',
|
||||
1,
|
||||
project_hash,
|
||||
project_hash,
|
||||
3,
|
||||
'sha256',
|
||||
))[0]
|
||||
|
||||
// Состояние после: данные сегмента участника
|
||||
const segmentAfter = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'segments',
|
||||
1,
|
||||
segmentKey,
|
||||
segmentKey,
|
||||
3,
|
||||
'i128',
|
||||
))[0]
|
||||
|
||||
console.log('📊 Состояние после расчета голосов:')
|
||||
console.log('▶ Статус проекта:', projectAfter.status)
|
||||
console.log(`▶ Сегмент участника ${username}:`)
|
||||
console.log(' - voting_bonus:', segmentAfter.voting_bonus)
|
||||
console.log(' - total_voting_pool:', projectAfter.voting.amounts.total_voting_pool)
|
||||
console.log(' - active_voting_amount:', projectAfter.voting.amounts.active_voting_amount)
|
||||
console.log(' - equal_voting_amount:', projectAfter.voting.amounts.equal_voting_amount)
|
||||
|
||||
return {
|
||||
txId: txResult.transaction_id,
|
||||
projectBefore,
|
||||
projectAfter,
|
||||
segmentBefore,
|
||||
segmentAfter,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
import { expect } from 'vitest'
|
||||
import { CapitalContract } from 'cooptypes'
|
||||
import type Blockchain from '../../blockchain'
|
||||
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
|
||||
|
||||
/**
|
||||
* Функция для завершения голосования по проекту
|
||||
* @param blockchain экземпляр блокчейна
|
||||
* @param data данные для завершения голосования
|
||||
*/
|
||||
export async function processCompleteVoting(
|
||||
blockchain: Blockchain,
|
||||
data: CapitalContract.Actions.CompleteVoting.ICompleteVoting,
|
||||
) {
|
||||
const { coopname, project_hash } = data
|
||||
|
||||
console.log(`\n✅ Начало завершения голосования для проекта ${project_hash}`)
|
||||
|
||||
// Состояние до: данные проекта
|
||||
const projectBefore = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'projects',
|
||||
1,
|
||||
project_hash,
|
||||
project_hash,
|
||||
3,
|
||||
'sha256',
|
||||
))[0]
|
||||
|
||||
console.log('📊 Состояние до завершения голосования:')
|
||||
console.log('▶ Статус проекта:', projectBefore.status)
|
||||
console.log('▶ Голосов получено:', projectBefore.voting.votes_received)
|
||||
console.log('▶ Всего участников голосования:', projectBefore.voting.total_voters)
|
||||
console.log('▶ Дедлайн голосования:', projectBefore.voting.voting_deadline)
|
||||
|
||||
// Отправляем транзакцию завершения голосования
|
||||
const txResult = await blockchain.api.transact(
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
account: CapitalContract.contractName.production,
|
||||
name: CapitalContract.Actions.CompleteVoting.actionName,
|
||||
authorization: [{ actor: coopname, permission: 'active' }],
|
||||
data,
|
||||
},
|
||||
],
|
||||
},
|
||||
{ blocksBehind: 3, expireSeconds: 30 },
|
||||
)
|
||||
getTotalRamUsage(txResult)
|
||||
expect(txResult.transaction_id).toBeDefined()
|
||||
|
||||
// Состояние после: данные проекта
|
||||
const projectAfter = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'projects',
|
||||
1,
|
||||
project_hash,
|
||||
project_hash,
|
||||
3,
|
||||
'sha256',
|
||||
))[0]
|
||||
|
||||
console.log('📊 Состояние после завершения голосования:')
|
||||
console.log('▶ Статус проекта:', projectAfter.status)
|
||||
console.log('▶ Голосов получено:', projectAfter.voting.votes_received)
|
||||
console.log('▶ Всего участников голосования:', projectAfter.voting.total_voters)
|
||||
|
||||
// Проверяем что статус изменился на result
|
||||
expect(projectAfter.status).toBe('result')
|
||||
|
||||
console.log(`\n✅ Голосование для проекта ${project_hash} успешно завершено!`)
|
||||
|
||||
return {
|
||||
projectHash: project_hash,
|
||||
txId: txResult.transaction_id,
|
||||
projectBefore,
|
||||
projectAfter,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
import { expect } from 'vitest'
|
||||
import { CapitalContract } from 'cooptypes'
|
||||
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
|
||||
import { generateRandomSHA256 } from '../../utils/randomHash'
|
||||
import { fakeDocument } from '../shared/fakeDocument'
|
||||
import type Blockchain from '../../blockchain'
|
||||
import { getCoopProgramWallet, getUserProgramWallet } from '../wallet/walletUtils'
|
||||
import { getSegment } from './getSegment'
|
||||
import { getProjectWallet } from './getProjectWallet'
|
||||
import { capitalProgramId, walletProgramId } from './consts'
|
||||
import { getProject } from './getProject'
|
||||
|
||||
/**
|
||||
* Функция для конвертации сегмента участника
|
||||
* @param blockchain экземпляр блокчейна
|
||||
* @param coopname название кооператива
|
||||
* @param username имя пользователя
|
||||
* @param projectHash хеш проекта
|
||||
* @param walletAmount сумма для конвертации в главный кошелек
|
||||
* @param capitalAmount сумма для конвертации в капитализацию
|
||||
* @param projectAmount сумма для конвертации в кошелек проекта
|
||||
*/
|
||||
export async function processConvertSegment(
|
||||
blockchain: Blockchain,
|
||||
coopname: string,
|
||||
username: string,
|
||||
projectHash: string,
|
||||
walletAmount: string,
|
||||
capitalAmount: string,
|
||||
projectAmount: string,
|
||||
) {
|
||||
const convertHash = generateRandomSHA256()
|
||||
console.log(`\n🔹 Начало процесса конвертации сегмента: ${convertHash}`)
|
||||
|
||||
// Получаем сегмент до конвертации
|
||||
const segmentBefore = await getSegment(blockchain, coopname, projectHash, username)
|
||||
console.log('📊 Сегмент до конвертации:', segmentBefore)
|
||||
|
||||
// Получаем кошельки до конвертации
|
||||
const mainWalletBefore = await getCoopProgramWallet(blockchain, coopname, walletProgramId)
|
||||
const capitalWalletBefore = await getCoopProgramWallet(blockchain, coopname, capitalProgramId)
|
||||
const userCapitalWalletBefore = await getUserProgramWallet(blockchain, coopname, username, capitalProgramId)
|
||||
const projectWalletBefore = await getProjectWallet(blockchain, coopname, projectHash, username)
|
||||
const projectBefore = await getProject(blockchain, coopname, projectHash)
|
||||
|
||||
console.log('💰 Кошельки до конвертации:')
|
||||
console.log(' ▶ Главный кошелек программы:', mainWalletBefore)
|
||||
console.log(' ▶ Глобальный кошелек программы капитализации:', capitalWalletBefore)
|
||||
console.log(' ▶ Кошелек пользователя в программе капитализации:', userCapitalWalletBefore)
|
||||
console.log(' ▶ Кошелек проекта:', projectWalletBefore)
|
||||
console.log(' ▶ Проект до конвертации: ', projectBefore)
|
||||
|
||||
// 1. Конвертируем сегмент
|
||||
const convertSegmentData: CapitalContract.Actions.ConvertSegment.IConvertSegment = {
|
||||
coopname,
|
||||
username,
|
||||
project_hash: projectHash,
|
||||
convert_hash: convertHash,
|
||||
wallet_amount: walletAmount,
|
||||
capital_amount: capitalAmount,
|
||||
convert_statement: fakeDocument,
|
||||
}
|
||||
|
||||
console.log(`\n🚀 Отправка транзакции ConvertSegment для ${username}`)
|
||||
console.log(` В главный кошелек: ${walletAmount}`)
|
||||
console.log(` В капитализацию: ${capitalAmount}`)
|
||||
console.log(` В проект: ${projectAmount}`)
|
||||
|
||||
const convertResult = await blockchain.api.transact(
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
account: CapitalContract.contractName.production,
|
||||
name: CapitalContract.Actions.ConvertSegment.actionName,
|
||||
authorization: [{ actor: coopname, permission: 'active' }],
|
||||
data: convertSegmentData,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
blocksBehind: 3,
|
||||
expireSeconds: 30,
|
||||
},
|
||||
)
|
||||
|
||||
getTotalRamUsage(convertResult)
|
||||
expect(convertResult.transaction_id).toBeDefined()
|
||||
console.log('✅ Сегмент конвертирован:', convertHash)
|
||||
|
||||
// Получаем сегмент после конвертации (должен быть удален)
|
||||
const segmentAfter = await getSegment(blockchain, coopname, projectHash, username)
|
||||
|
||||
// Получаем кошельки после конвертации
|
||||
const mainWalletAfter = await getCoopProgramWallet(blockchain, coopname, walletProgramId)
|
||||
const capitalWalletAfter = await getCoopProgramWallet(blockchain, coopname, capitalProgramId)
|
||||
const userCapitalWalletAfter = await getUserProgramWallet(blockchain, coopname, username, capitalProgramId)
|
||||
const projectWalletAfter = await getProjectWallet(blockchain, coopname, projectHash, username)
|
||||
|
||||
console.log('💰 Кошельки после конвертации:')
|
||||
console.log(' ▶ Главный кошелек программы:', mainWalletAfter)
|
||||
console.log(' ▶ Глобальный кошелек программы капитализации:', capitalWalletAfter)
|
||||
console.log(' ▶ Кошелек пользователя в программе капитализации:', userCapitalWalletAfter)
|
||||
console.log(' ▶ Кошелек проекта:', projectWalletAfter)
|
||||
|
||||
// Проверяем что сегмент удален после конвертации
|
||||
expect(segmentAfter).toBeUndefined()
|
||||
console.log('🔍 Сегмент после конвертации: ', segmentAfter)
|
||||
|
||||
// Проверяем изменения в кошельках
|
||||
console.log('\n🔍 Проверка изменений в кошельках:')
|
||||
|
||||
// Проверяем главный кошелек программы (wallet_amount)
|
||||
if (walletAmount !== '0.0000 RUB') {
|
||||
const walletAmountValue = parseFloat(walletAmount.split(' ')[0])
|
||||
const beforeAvailable = mainWalletBefore ? parseFloat(mainWalletBefore.available.split(' ')[0]) : 0
|
||||
const afterAvailable = mainWalletAfter ? parseFloat(mainWalletAfter.available.split(' ')[0]) : 0
|
||||
const expectedIncrease = beforeAvailable + walletAmountValue
|
||||
console.log(`✅ Главный кошелек программы: ${beforeAvailable} → ${afterAvailable} (+${walletAmountValue})`)
|
||||
expect(afterAvailable).toBeCloseTo(expectedIncrease, 1)
|
||||
}
|
||||
|
||||
// Проверяем глобальный кошелек программы капитализации (capital_amount)
|
||||
// investor_base уже заблокирован в _capital_program при инвестировании (createinvest)
|
||||
// В capitalAmount теперь передается только чистая дельта интеллектуального вклада
|
||||
if (capitalAmount !== '0.0000 RUB') {
|
||||
const capitalAmountValue = parseFloat(capitalAmount.split(' ')[0])
|
||||
const actualCapitalIncrease = capitalAmountValue
|
||||
const beforeBlocked = capitalWalletBefore ? parseFloat(capitalWalletBefore.blocked.split(' ')[0]) : 0
|
||||
const afterBlocked = capitalWalletAfter ? parseFloat(capitalWalletAfter.blocked.split(' ')[0]) : 0
|
||||
const expectedIncrease = beforeBlocked + actualCapitalIncrease
|
||||
console.log(`✅ Глобальный кошелек программы капитализации: ${beforeBlocked} → ${afterBlocked} (+${actualCapitalIncrease})`)
|
||||
expect(afterBlocked).toBeCloseTo(expectedIncrease, 1)
|
||||
}
|
||||
|
||||
// Проверяем кошелек пользователя в программе капитализации (capital_amount)
|
||||
if (capitalAmount !== '0.0000 RUB') {
|
||||
const capitalAmountValue = parseFloat(capitalAmount.split(' ')[0])
|
||||
const actualCapitalIncrease = capitalAmountValue
|
||||
const beforeBlocked = userCapitalWalletBefore ? parseFloat(userCapitalWalletBefore.blocked.split(' ')[0]) : 0
|
||||
const afterBlocked = userCapitalWalletAfter ? parseFloat(userCapitalWalletAfter.blocked.split(' ')[0]) : 0
|
||||
const expectedIncrease = beforeBlocked + actualCapitalIncrease
|
||||
console.log(`✅ Кошелек пользователя в программе капитализации: ${beforeBlocked} → ${afterBlocked} (+${actualCapitalIncrease})`)
|
||||
expect(afterBlocked).toBeCloseTo(expectedIncrease, 1)
|
||||
}
|
||||
|
||||
// Проверяем кошелек проекта (project_amount)
|
||||
if (projectAmount !== '0.0000 RUB' && projectBefore.parent_hash.includes('0000000000')) {
|
||||
const projectAmountValue = parseFloat(projectAmount.split(' ')[0])
|
||||
const beforeShares = projectWalletBefore ? parseFloat(projectWalletBefore.shares.split(' ')[0]) : 0
|
||||
const afterShares = projectWalletAfter ? parseFloat(projectWalletAfter.shares.split(' ')[0]) : 0
|
||||
const expectedIncrease = beforeShares + projectAmountValue
|
||||
console.log(`✅ Кошелек проекта: ${beforeShares} → ${afterShares} (+${projectAmountValue})`)
|
||||
expect(afterShares).toBeCloseTo(expectedIncrease, 1)
|
||||
}
|
||||
|
||||
console.log('\n📊 Результаты после конвертации сегмента:')
|
||||
console.log('▶ Сегмент до:', segmentBefore)
|
||||
console.log('▶ Сегмент после удалён: ', segmentAfter)
|
||||
|
||||
console.log(`\n✅ Процесс конвертации сегмента завершен успешно!`)
|
||||
|
||||
return {
|
||||
convertHash,
|
||||
segmentBefore,
|
||||
segmentAfter,
|
||||
mainWalletBefore,
|
||||
mainWalletAfter,
|
||||
capitalWalletBefore,
|
||||
capitalWalletAfter,
|
||||
userCapitalWalletBefore,
|
||||
userCapitalWalletAfter,
|
||||
projectWalletBefore,
|
||||
projectWalletAfter,
|
||||
transactionId: convertResult.transaction_id,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
import { expect } from 'vitest'
|
||||
import { CapitalContract } from 'cooptypes'
|
||||
import type Blockchain from '../../blockchain'
|
||||
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
|
||||
import { getCoopProgramWallet, getLedgerAccountById } from '../wallet/walletUtils'
|
||||
import { processLastDecision } from '../soviet/processLastDecision'
|
||||
import { processApprove } from './processApprove'
|
||||
import { capitalProgramId, circulationAccountId } from './consts'
|
||||
|
||||
export async function processCreateProgramProperty(
|
||||
blockchain: Blockchain,
|
||||
data: CapitalContract.Actions.CreateProgramProperty.ICreateProgramProperty,
|
||||
fakeDocument: any,
|
||||
) {
|
||||
const { coopname, username, property_hash } = data
|
||||
|
||||
// Состояния до: кошелек программы капитализации и паевой счет в бухгалтерии
|
||||
const programWalletBefore = await getCoopProgramWallet(blockchain, coopname, capitalProgramId)
|
||||
const ledgerShareBefore = await getLedgerAccountById(blockchain, coopname, circulationAccountId)
|
||||
|
||||
// 1) Создаём программное предложение имущественного взноса
|
||||
const txCreate = await blockchain.api.transact(
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
account: CapitalContract.contractName.production,
|
||||
name: CapitalContract.Actions.CreateProgramProperty.actionName,
|
||||
authorization: [{ actor: coopname, permission: 'active' }],
|
||||
data,
|
||||
},
|
||||
],
|
||||
},
|
||||
{ blocksBehind: 3, expireSeconds: 30 },
|
||||
)
|
||||
getTotalRamUsage(txCreate)
|
||||
expect(txCreate.transaction_id).toBeDefined()
|
||||
|
||||
// 2) Подтверждение одобрения председателем (через soviet)
|
||||
await processApprove(blockchain as any, coopname, property_hash as unknown as string)
|
||||
|
||||
// 3) Обработка решения совета (AUTHORIZE_PROGRAM_PROPERTY)
|
||||
await processLastDecision(blockchain, coopname)
|
||||
|
||||
// 4) Акт 1 — подписывает участник-вноситель (username)
|
||||
fakeDocument.signatures[0].signer = username
|
||||
const act1Data = {
|
||||
coopname,
|
||||
username,
|
||||
property_hash,
|
||||
act: fakeDocument,
|
||||
} as CapitalContract.Actions.Act1ProgramProperty.IAct1ProgramProperty
|
||||
|
||||
const txAct1 = await blockchain.api.transact(
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
account: CapitalContract.contractName.production,
|
||||
name: CapitalContract.Actions.Act1ProgramProperty.actionName,
|
||||
authorization: [{ actor: coopname, permission: 'active' }],
|
||||
data: act1Data,
|
||||
},
|
||||
],
|
||||
},
|
||||
{ blocksBehind: 3, expireSeconds: 30 },
|
||||
)
|
||||
getTotalRamUsage(txAct1)
|
||||
expect(txAct1.transaction_id).toBeDefined()
|
||||
|
||||
// 5) Акт 2 — подписывает председатель совета кооператива ('ant')
|
||||
fakeDocument.signatures[0].signer = 'ant'
|
||||
|
||||
const act2Data = {
|
||||
coopname,
|
||||
property_hash,
|
||||
username: 'ant',
|
||||
act: fakeDocument,
|
||||
} as CapitalContract.Actions.Act2ProgramProperty.IAct2ProgramProperty
|
||||
|
||||
const txAct2 = await blockchain.api.transact(
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
account: CapitalContract.contractName.production,
|
||||
name: CapitalContract.Actions.Act2ProgramProperty.actionName,
|
||||
authorization: [{ actor: coopname, permission: 'active' }],
|
||||
data: act2Data,
|
||||
},
|
||||
],
|
||||
},
|
||||
{ blocksBehind: 3, expireSeconds: 30 },
|
||||
)
|
||||
getTotalRamUsage(txAct2)
|
||||
expect(txAct2.transaction_id).toBeDefined()
|
||||
|
||||
// Состояния после
|
||||
const programWalletAfter = await getCoopProgramWallet(blockchain, coopname, capitalProgramId)
|
||||
const ledgerShareAfter = await getLedgerAccountById(blockchain, coopname, circulationAccountId)
|
||||
|
||||
return {
|
||||
propertyHash: property_hash,
|
||||
txCreateId: txCreate.transaction_id,
|
||||
txAct1Id: txAct1.transaction_id,
|
||||
txAct2Id: txAct2.transaction_id,
|
||||
programWalletBefore,
|
||||
programWalletAfter,
|
||||
ledgerShareBefore,
|
||||
ledgerShareAfter,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import { expect } from 'vitest'
|
||||
import { CapitalContract } from 'cooptypes'
|
||||
import type Blockchain from '../../blockchain'
|
||||
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
|
||||
import { processLastDecision } from '../soviet/processLastDecision'
|
||||
import { processApprove } from './processApprove'
|
||||
import { getSegment } from './getSegment'
|
||||
|
||||
export async function processCreateProjectProperty(
|
||||
blockchain: Blockchain,
|
||||
data: CapitalContract.Actions.CreateProjectProperty.ICreateProjectProperty,
|
||||
) {
|
||||
const { coopname, username, project_hash, property_hash } = data
|
||||
|
||||
// Состояния до: сегмент проекта и данные проекта
|
||||
const segmentBefore = await getSegment(blockchain, coopname, project_hash, username)
|
||||
const projectBefore = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'projects',
|
||||
1,
|
||||
project_hash,
|
||||
project_hash,
|
||||
3,
|
||||
'sha256',
|
||||
))[0]
|
||||
|
||||
// 1) Создаём проектное предложение имущественного взноса
|
||||
const txCreate = await blockchain.api.transact(
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
account: CapitalContract.contractName.production,
|
||||
name: CapitalContract.Actions.CreateProjectProperty.actionName,
|
||||
authorization: [{ actor: coopname, permission: 'active' }],
|
||||
data,
|
||||
},
|
||||
],
|
||||
},
|
||||
{ blocksBehind: 3, expireSeconds: 30 },
|
||||
)
|
||||
getTotalRamUsage(txCreate)
|
||||
expect(txCreate.transaction_id).toBeDefined()
|
||||
|
||||
// 2) Подтверждение одобрения председателем (через soviet)
|
||||
await processApprove(blockchain as any, coopname, property_hash as unknown as string)
|
||||
|
||||
// Состояния после: сегмент проекта и данные проекта
|
||||
const segmentAfter = await getSegment(blockchain, coopname, project_hash, username)
|
||||
const projectAfter = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'projects',
|
||||
1,
|
||||
project_hash,
|
||||
project_hash,
|
||||
3,
|
||||
'sha256',
|
||||
))[0]
|
||||
|
||||
return {
|
||||
propertyHash: property_hash,
|
||||
txCreateId: txCreate.transaction_id,
|
||||
segmentBefore,
|
||||
segmentAfter,
|
||||
projectBefore,
|
||||
projectAfter,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
import { expect } from 'vitest'
|
||||
import { CapitalContract, GatewayContract } from 'cooptypes'
|
||||
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
|
||||
import { generateRandomSHA256 } from '../../utils/randomHash'
|
||||
import { processLastDecision } from '../soviet/processLastDecision'
|
||||
import type Blockchain from '../../blockchain'
|
||||
import { formatDateToISOString } from '../shared/formatDateToISOString'
|
||||
import { processApprove } from './processApprove'
|
||||
import { getSegment } from './getSegment'
|
||||
|
||||
export async function processDebt(
|
||||
blockchain: Blockchain,
|
||||
coopname: string,
|
||||
username: string,
|
||||
projectHash: string,
|
||||
debtAmount: string,
|
||||
fakeDocument: any,
|
||||
repaidAtDate: Date,
|
||||
) {
|
||||
const debtHash = generateRandomSHA256()
|
||||
console.log(`\n🔹 Начало процесса создания долга: ${debtHash}`)
|
||||
|
||||
// Дата возврата через месяц
|
||||
const repaidAt = formatDateToISOString(repaidAtDate)
|
||||
|
||||
// Получаем сегмент до создания долга
|
||||
const segmentBefore = await getSegment(blockchain, coopname, projectHash, username)
|
||||
console.log('📊 Сегмент до долга:', segmentBefore)
|
||||
|
||||
// Получаем контрибьютора до создания долга
|
||||
const contributorsBefore = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'contributors',
|
||||
1,
|
||||
username,
|
||||
username,
|
||||
2,
|
||||
'i64',
|
||||
))
|
||||
const contributorBefore = contributorsBefore[0]
|
||||
console.log('📊 Контрибьютор до долга:', contributorBefore)
|
||||
|
||||
fakeDocument.signatures[0].signer = username
|
||||
|
||||
// 1. Создаем долг
|
||||
const createDebtData: CapitalContract.Actions.CreateDebt.ICreateDebt = {
|
||||
coopname,
|
||||
username,
|
||||
debt_hash: debtHash,
|
||||
project_hash: projectHash,
|
||||
amount: debtAmount,
|
||||
repaid_at: repaidAt,
|
||||
statement: fakeDocument,
|
||||
}
|
||||
|
||||
console.log(`\n🚀 Отправка транзакции CreateDebt для ${username} на сумму ${debtAmount}`)
|
||||
const createDebtResult = await blockchain.api.transact(
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
account: CapitalContract.contractName.production,
|
||||
name: CapitalContract.Actions.CreateDebt.actionName,
|
||||
authorization: [{ actor: coopname, permission: 'active' }],
|
||||
data: createDebtData,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
blocksBehind: 3,
|
||||
expireSeconds: 30,
|
||||
},
|
||||
)
|
||||
|
||||
getTotalRamUsage(createDebtResult)
|
||||
expect(createDebtResult.transaction_id).toBeDefined()
|
||||
console.log('✅ Долг создан:', debtHash)
|
||||
|
||||
// Проверяем что долг создался
|
||||
const debts = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'debts',
|
||||
1,
|
||||
debtHash,
|
||||
debtHash,
|
||||
3,
|
||||
'sha256',
|
||||
))
|
||||
const createdDebt = debts[0]
|
||||
console.log('🔍 Долг в блокчейне:', createdDebt)
|
||||
expect(createdDebt).toBeDefined()
|
||||
expect(createdDebt.status).toBe('created')
|
||||
|
||||
// 2. Одобряем долг через processApprove (soviet контракт)
|
||||
console.log(`\n✅ Подтверждение долга ${debtHash} через soviet`)
|
||||
await processApprove(blockchain, coopname, debtHash)
|
||||
console.log('✅ Долг одобрен председателем (создана agenda)')
|
||||
|
||||
// 3. Процессим решение совета (agenda для авторизации долга)
|
||||
await processLastDecision(blockchain, coopname)
|
||||
console.log('✅ Решение совета принято (долг авторизован, outcome создан)')
|
||||
|
||||
// 4. Подтверждаем завершение вывода (gateway сам вызовет callback на capital)
|
||||
const confirmOutcomeData: GatewayContract.Actions.CompleteOutcome.ICompleteOutcome = {
|
||||
coopname,
|
||||
outcome_hash: debtHash,
|
||||
}
|
||||
|
||||
console.log(`\n✅ Подтверждение завершения вывода`)
|
||||
const confirmResult = await blockchain.api.transact(
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
account: GatewayContract.contractName.production,
|
||||
name: GatewayContract.Actions.CompleteOutcome.actionName,
|
||||
authorization: [{ actor: coopname, permission: 'active' }],
|
||||
data: confirmOutcomeData,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
blocksBehind: 3,
|
||||
expireSeconds: 30,
|
||||
},
|
||||
)
|
||||
|
||||
getTotalRamUsage(confirmResult)
|
||||
expect(confirmResult.transaction_id).toBeDefined()
|
||||
console.log('✅ Оплата долга подтверждена')
|
||||
|
||||
// Получаем финальные состояния
|
||||
const segmentAfter = await getSegment(blockchain, coopname, projectHash, username)
|
||||
|
||||
const contributorsAfter = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'contributors',
|
||||
1,
|
||||
username,
|
||||
username,
|
||||
2,
|
||||
'i64',
|
||||
))
|
||||
const contributorAfter = contributorsAfter[0]
|
||||
|
||||
// Получаем долг после подтверждения оплаты
|
||||
const debtsAfter = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'debts',
|
||||
1,
|
||||
debtHash,
|
||||
debtHash,
|
||||
3,
|
||||
'sha256',
|
||||
))
|
||||
const debtAfter = debtsAfter[0]
|
||||
|
||||
console.log('\n📊 Результаты после обработки долга:')
|
||||
console.log('▶ Сегмент до:', segmentBefore)
|
||||
console.log('▶ Сегмент после:', segmentAfter)
|
||||
console.log('▶ Контрибьютор до:', contributorBefore)
|
||||
console.log('▶ Контрибьютор после:', contributorAfter)
|
||||
console.log('▶ Долг после оплаты:', debtAfter)
|
||||
|
||||
console.log(`\n✅ Процесс долга на ${debtAmount} завершен успешно!`)
|
||||
|
||||
return {
|
||||
debtHash,
|
||||
debt: createdDebt,
|
||||
debtAfter,
|
||||
segmentBefore,
|
||||
segmentAfter,
|
||||
contributorBefore,
|
||||
contributorAfter,
|
||||
transactionId: confirmResult.transaction_id,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import { expect } from 'vitest'
|
||||
import { CapitalContract } from 'cooptypes'
|
||||
import type Blockchain from '../../blockchain'
|
||||
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
|
||||
|
||||
export async function processDeleteProject(
|
||||
blockchain: Blockchain,
|
||||
coopname: string,
|
||||
project_hash: string,
|
||||
) {
|
||||
// Состояние проекта до удаления
|
||||
const projectBefore = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'projects',
|
||||
1,
|
||||
project_hash,
|
||||
project_hash,
|
||||
3,
|
||||
'sha256',
|
||||
))[0]
|
||||
|
||||
const data: CapitalContract.Actions.DeleteProject.IDeleteProject = {
|
||||
coopname,
|
||||
project_hash,
|
||||
}
|
||||
|
||||
// Выполняем удаление проекта
|
||||
const txDelete = await blockchain.api.transact(
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
account: CapitalContract.contractName.production,
|
||||
name: CapitalContract.Actions.DeleteProject.actionName,
|
||||
authorization: [{ actor: coopname, permission: 'active' }],
|
||||
data,
|
||||
},
|
||||
],
|
||||
},
|
||||
{ blocksBehind: 3, expireSeconds: 30 },
|
||||
)
|
||||
getTotalRamUsage(txDelete)
|
||||
expect(txDelete.transaction_id).toBeDefined()
|
||||
|
||||
// Проверяем что проект удален
|
||||
const projectsAfter = await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'projects',
|
||||
1,
|
||||
project_hash,
|
||||
project_hash,
|
||||
3,
|
||||
'sha256',
|
||||
)
|
||||
|
||||
return {
|
||||
transactionId: txDelete.transaction_id,
|
||||
projectBefore,
|
||||
projectAfter: projectsAfter[0] || null, // null если удален
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import { expect } from 'vitest'
|
||||
import { CapitalContract } from 'cooptypes'
|
||||
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
|
||||
import { getCapitalWallet } from './getCapitalWallet'
|
||||
|
||||
export async function processFundProgram(
|
||||
blockchain: any,
|
||||
coopname: string,
|
||||
amount: string,
|
||||
memo: string = 'Тестовое финансирование программы',
|
||||
) {
|
||||
console.log(`\n🔹 Начало финансирования программы капитализации: сумма=${amount}, memo=${memo}`)
|
||||
|
||||
// Получение данных ДО финансирования
|
||||
const programWalletBefore = await getCapitalWallet(blockchain, coopname, coopname)
|
||||
|
||||
console.log('📊 Состояние программы ДО финансирования:')
|
||||
console.log('▶ Кошелек программы:', programWalletBefore)
|
||||
|
||||
// Финансирование программы капитализации
|
||||
const fundData: CapitalContract.Actions.FundProgram.IFundProgram = {
|
||||
coopname,
|
||||
amount,
|
||||
memo,
|
||||
}
|
||||
|
||||
const fundResult = await blockchain.api.transact(
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
account: CapitalContract.contractName.production,
|
||||
name: CapitalContract.Actions.FundProgram.actionName,
|
||||
authorization: [{ actor: coopname, permission: 'active' }],
|
||||
data: fundData,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
blocksBehind: 3,
|
||||
expireSeconds: 30,
|
||||
},
|
||||
)
|
||||
|
||||
getTotalRamUsage(fundResult)
|
||||
expect(fundResult.transaction_id).toBeDefined()
|
||||
|
||||
// Получение данных ПОСЛЕ финансирования
|
||||
const programWalletAfter = await getCapitalWallet(blockchain, coopname, coopname)
|
||||
|
||||
console.log('📊 Состояние программы ПОСЛЕ финансирования:')
|
||||
console.log('▶ Кошелек программы:', programWalletAfter)
|
||||
|
||||
console.log(`\n✅ Финансирование программы капитализации завершено успешно!`)
|
||||
|
||||
return {
|
||||
transactionId: fundResult.transaction_id,
|
||||
transaction: fundResult,
|
||||
programWalletBefore,
|
||||
programWalletAfter,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import { expect } from 'vitest'
|
||||
import { CapitalContract } from 'cooptypes'
|
||||
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
|
||||
import { getProjectWallet } from './getProjectWallet'
|
||||
|
||||
export async function processFundProject(
|
||||
blockchain: any,
|
||||
coopname: string,
|
||||
projectHash: string,
|
||||
amount: string,
|
||||
memo: string = 'Тестовое финансирование проекта',
|
||||
) {
|
||||
console.log(`\n🔹 Начало финансирования проекта: проект=${projectHash}, сумма=${amount}, memo=${memo}`)
|
||||
|
||||
// Получение данных ДО финансирования
|
||||
const projectWalletBefore = await getProjectWallet(blockchain, coopname, projectHash, coopname)
|
||||
|
||||
console.log('📊 Состояние проекта ДО финансирования:')
|
||||
console.log('▶ Кошелек проекта:', projectWalletBefore)
|
||||
|
||||
// Финансирование проекта
|
||||
const fundData: CapitalContract.Actions.FundProject.IFundProject = {
|
||||
coopname,
|
||||
project_hash: projectHash,
|
||||
amount,
|
||||
memo,
|
||||
}
|
||||
|
||||
const fundResult = await blockchain.api.transact(
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
account: CapitalContract.contractName.production,
|
||||
name: CapitalContract.Actions.FundProject.actionName,
|
||||
authorization: [{ actor: coopname, permission: 'active' }],
|
||||
data: fundData,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
blocksBehind: 3,
|
||||
expireSeconds: 30,
|
||||
},
|
||||
)
|
||||
|
||||
getTotalRamUsage(fundResult)
|
||||
expect(fundResult.transaction_id).toBeDefined()
|
||||
|
||||
// Получение данных ПОСЛЕ финансирования
|
||||
const projectWalletAfter = await getProjectWallet(blockchain, coopname, projectHash, coopname)
|
||||
|
||||
console.log('📊 Состояние проекта ПОСЛЕ финансирования:')
|
||||
console.log('▶ Кошелек проекта:', projectWalletAfter)
|
||||
|
||||
console.log(`\n✅ Финансирование проекта завершено успешно!`)
|
||||
|
||||
return {
|
||||
transactionId: fundResult.transaction_id,
|
||||
transaction: fundResult,
|
||||
projectWalletBefore,
|
||||
projectWalletAfter,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { expect } from 'vitest'
|
||||
import { CapitalContract } from 'cooptypes'
|
||||
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
|
||||
|
||||
export async function processMasterApprove(
|
||||
blockchain: any,
|
||||
coopname: string,
|
||||
commitHash: string,
|
||||
master: string,
|
||||
) {
|
||||
const data: CapitalContract.Actions.CommitApprove.ICommitApprove = {
|
||||
coopname,
|
||||
master,
|
||||
commit_hash: commitHash,
|
||||
}
|
||||
|
||||
const result = await blockchain.transactWithLogs([
|
||||
{
|
||||
account: CapitalContract.contractName.production,
|
||||
name: CapitalContract.Actions.CommitApprove.actionName,
|
||||
authorization: [{ actor: coopname, permission: 'active' }],
|
||||
data,
|
||||
},
|
||||
])
|
||||
|
||||
getTotalRamUsage(result)
|
||||
expect(result.transaction_id).toBeDefined()
|
||||
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
import { expect } from 'vitest'
|
||||
import { CapitalContract, SovietContract } from 'cooptypes'
|
||||
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
|
||||
import { generateRandomSHA256 } from '../../utils/randomHash'
|
||||
import { setDocumentSignatures } from '../../utils/setDocumentSignatures'
|
||||
import { processLastDecision } from '../soviet/processLastDecision'
|
||||
import { fakeDocument } from '../shared/fakeDocument'
|
||||
import type Blockchain from '../../blockchain'
|
||||
import { getSegment } from './getSegment'
|
||||
import { processApprove } from './processApprove'
|
||||
|
||||
/**
|
||||
* Функция для внесения результата в кооператив
|
||||
* @param blockchain экземпляр блокчейна
|
||||
* @param coopname название кооператива
|
||||
* @param username имя пользователя
|
||||
* @param projectHash хеш проекта
|
||||
* @param contributionAmount сумма взноса
|
||||
* @param debtAmount сумма долга к погашению
|
||||
* @param debtHashes вектор хэшей долгов для погашения
|
||||
*/
|
||||
export async function processPushResult(
|
||||
blockchain: Blockchain,
|
||||
coopname: string,
|
||||
username: string,
|
||||
projectHash: string,
|
||||
contributionAmount: string,
|
||||
debtAmount: string,
|
||||
debtHashes: string[] = [],
|
||||
) {
|
||||
const resultHash = generateRandomSHA256()
|
||||
console.log(`\n🔹 Начало процесса внесения результата: ${resultHash}`)
|
||||
|
||||
// Получаем сегмент до внесения результата
|
||||
const segmentBefore = await getSegment(blockchain, coopname, projectHash, username)
|
||||
console.log('📊 Сегмент до внесения результата:', segmentBefore)
|
||||
|
||||
// Устанавливаем подписи документа
|
||||
const documentWithSignatures = setDocumentSignatures([username])
|
||||
|
||||
// 1. Вносим результат
|
||||
const pushResultData: CapitalContract.Actions.PushResult.IPushResult = {
|
||||
coopname,
|
||||
username,
|
||||
project_hash: projectHash,
|
||||
result_hash: resultHash,
|
||||
contribution_amount: contributionAmount,
|
||||
debt_amount: debtAmount,
|
||||
statement: documentWithSignatures,
|
||||
debt_hashes: debtHashes,
|
||||
}
|
||||
|
||||
console.log(`\n🚀 Отправка транзакции PushResult для ${username} на сумму ${contributionAmount}`)
|
||||
const pushResult = await blockchain.api.transact(
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
account: CapitalContract.contractName.production,
|
||||
name: CapitalContract.Actions.PushResult.actionName,
|
||||
authorization: [{ actor: coopname, permission: 'active' }],
|
||||
data: pushResultData,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
blocksBehind: 3,
|
||||
expireSeconds: 30,
|
||||
},
|
||||
)
|
||||
|
||||
getTotalRamUsage(pushResult)
|
||||
expect(pushResult.transaction_id).toBeDefined()
|
||||
console.log('✅ Результат внесен:', resultHash)
|
||||
|
||||
// Проверяем что результат создался
|
||||
const results = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'results',
|
||||
1,
|
||||
resultHash,
|
||||
resultHash,
|
||||
3,
|
||||
'sha256',
|
||||
))
|
||||
const createdResult = results[0]
|
||||
console.log('🔍 Результат в блокчейне:', createdResult)
|
||||
expect(createdResult).toBeDefined()
|
||||
expect(createdResult.status).toBe('created')
|
||||
|
||||
// 2. Одобряем результат через processApprove (soviet контракт)
|
||||
console.log(`\n✅ Одобрение результата ${resultHash} через soviet`)
|
||||
await processApprove(blockchain, coopname, resultHash)
|
||||
console.log('✅ Результат одобрен советом')
|
||||
|
||||
// 3. Процессим решение совета
|
||||
await processLastDecision(blockchain, coopname)
|
||||
console.log('✅ Решение совета принято')
|
||||
|
||||
console.log('🔍 Подписываем акт №1 вкладчиком', documentWithSignatures)
|
||||
// 4. Подписываем акт №1 вкладчиком
|
||||
const signAct1Data: CapitalContract.Actions.SignAct1.ISignAct1 = {
|
||||
coopname,
|
||||
username,
|
||||
result_hash: resultHash,
|
||||
act: documentWithSignatures,
|
||||
}
|
||||
|
||||
console.log(`\n✅ Подписание акта №1 вкладчиком ${username}`)
|
||||
const signAct1Result = await blockchain.api.transact(
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
account: CapitalContract.contractName.production,
|
||||
name: CapitalContract.Actions.SignAct1.actionName,
|
||||
authorization: [{ actor: coopname, permission: 'active' }],
|
||||
data: signAct1Data,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
blocksBehind: 3,
|
||||
expireSeconds: 30,
|
||||
},
|
||||
)
|
||||
|
||||
getTotalRamUsage(signAct1Result)
|
||||
expect(signAct1Result.transaction_id).toBeDefined()
|
||||
console.log('✅ Акт №1 подписан вкладчиком')
|
||||
|
||||
// Создаем документ с подписями вкладчика и председателя
|
||||
const documentWithChairmanSignature = setDocumentSignatures([username, 'ant'])
|
||||
|
||||
// 5. Подписываем акт №2 председателем
|
||||
const signAct2Data: CapitalContract.Actions.SignAct2.ISignAct2 = {
|
||||
coopname,
|
||||
result_hash: resultHash,
|
||||
act: documentWithChairmanSignature,
|
||||
chairman: 'ant',
|
||||
}
|
||||
|
||||
console.log(`\n✅ Подписание акта №2 председателем`, documentWithChairmanSignature)
|
||||
const signAct2Result = await blockchain.api.transact(
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
account: CapitalContract.contractName.production,
|
||||
name: CapitalContract.Actions.SignAct2.actionName,
|
||||
authorization: [{ actor: coopname, permission: 'active' }],
|
||||
data: signAct2Data,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
blocksBehind: 3,
|
||||
expireSeconds: 30,
|
||||
},
|
||||
)
|
||||
|
||||
getTotalRamUsage(signAct2Result)
|
||||
expect(signAct2Result.transaction_id).toBeDefined()
|
||||
console.log('✅ Акт №2 подписан председателем')
|
||||
|
||||
// Получаем финальные состояния
|
||||
const segmentAfter = await getSegment(blockchain, coopname, projectHash, username)
|
||||
|
||||
console.log('\n📊 Результаты после внесения результата:')
|
||||
console.log('▶ Сегмент до:', segmentBefore)
|
||||
console.log('▶ Сегмент после:', segmentAfter)
|
||||
|
||||
console.log(`\n✅ Процесс внесения результата на ${contributionAmount} завершен успешно!`)
|
||||
|
||||
return {
|
||||
resultHash,
|
||||
result: createdResult,
|
||||
segmentBefore,
|
||||
segmentAfter,
|
||||
transactionId: signAct2Result.transaction_id,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import { expect } from 'vitest'
|
||||
import { CapitalContract } from 'cooptypes'
|
||||
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
|
||||
import { getCapitalWallet } from './getCapitalWallet'
|
||||
|
||||
export async function processRefreshProg(
|
||||
blockchain: any,
|
||||
coopname: string,
|
||||
username: string,
|
||||
) {
|
||||
console.log(`\n🔹 Начало обновления CRPS пайщика в программе капитализации: пользователь=${username}`)
|
||||
|
||||
// Получение данных ДО обновления
|
||||
const programWalletBefore = await getCapitalWallet(blockchain, coopname, username)
|
||||
|
||||
console.log('📊 Состояние пайщика ДО обновления CRPS:')
|
||||
console.log('▶ Кошелек пайщика:', programWalletBefore)
|
||||
|
||||
// Обновление CRPS пайщика в программе капитализации
|
||||
const refreshData: CapitalContract.Actions.RefreshProgram.IRefreshProgram = {
|
||||
coopname,
|
||||
username,
|
||||
}
|
||||
|
||||
const refreshResult = await blockchain.api.transact(
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
account: CapitalContract.contractName.production,
|
||||
name: CapitalContract.Actions.RefreshProgram.actionName,
|
||||
authorization: [{ actor: coopname, permission: 'active' }],
|
||||
data: refreshData,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
blocksBehind: 3,
|
||||
expireSeconds: 30,
|
||||
},
|
||||
)
|
||||
|
||||
getTotalRamUsage(refreshResult)
|
||||
expect(refreshResult.transaction_id).toBeDefined()
|
||||
|
||||
// Получение данных ПОСЛЕ обновления
|
||||
const programWalletAfter = await getCapitalWallet(blockchain, coopname, username)
|
||||
|
||||
console.log('📊 Состояние пайщика ПОСЛЕ обновления CRPS:')
|
||||
console.log('▶ Кошелек пайщика:', programWalletAfter)
|
||||
|
||||
console.log(`\n✅ Обновление CRPS пайщика в программе капитализации завершено успешно!`)
|
||||
|
||||
return {
|
||||
transactionId: refreshResult.transaction_id,
|
||||
transaction: refreshResult,
|
||||
programWalletBefore,
|
||||
programWalletAfter,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import { expect } from 'vitest'
|
||||
import { CapitalContract } from 'cooptypes'
|
||||
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
|
||||
import { getProjectWallet } from './getProjectWallet'
|
||||
|
||||
export async function processRefreshProj(
|
||||
blockchain: any,
|
||||
coopname: string,
|
||||
projectHash: string,
|
||||
username: string,
|
||||
) {
|
||||
console.log(`\n🔹 Начало обновления CRPS пайщика в проекте: проект=${projectHash}, пользователь=${username}`)
|
||||
|
||||
// Получение данных ДО обновления
|
||||
const projectWalletBefore = await getProjectWallet(blockchain, coopname, projectHash, username)
|
||||
|
||||
console.log('📊 Состояние кошелька проекта ДО обновления CRPS:')
|
||||
console.log('▶ Кошелек проекта:', projectWalletBefore)
|
||||
|
||||
// Обновление CRPS пайщика в проекте
|
||||
const refreshData: CapitalContract.Actions.RefreshProject.IRefreshProject = {
|
||||
coopname,
|
||||
project_hash: projectHash,
|
||||
username,
|
||||
}
|
||||
|
||||
const refreshResult = await blockchain.api.transact(
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
account: CapitalContract.contractName.production,
|
||||
name: CapitalContract.Actions.RefreshProject.actionName,
|
||||
authorization: [{ actor: coopname, permission: 'active' }],
|
||||
data: refreshData,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
blocksBehind: 3,
|
||||
expireSeconds: 30,
|
||||
},
|
||||
)
|
||||
|
||||
getTotalRamUsage(refreshResult)
|
||||
expect(refreshResult.transaction_id).toBeDefined()
|
||||
|
||||
// Получение данных ПОСЛЕ обновления
|
||||
const projectWalletAfter = await getProjectWallet(blockchain, coopname, projectHash, username)
|
||||
|
||||
console.log('📊 Состояние кошелька проекта ПОСЛЕ обновления CRPS:')
|
||||
console.log('▶ Кошелек проекта:', projectWalletAfter)
|
||||
|
||||
console.log(`\n✅ Обновление CRPS пайщика в проекте завершено успешно!`)
|
||||
|
||||
return {
|
||||
transactionId: refreshResult.transaction_id,
|
||||
transaction: refreshResult,
|
||||
projectWalletBefore,
|
||||
projectWalletAfter,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import { expect } from 'vitest'
|
||||
import { CapitalContract } from 'cooptypes'
|
||||
import type Blockchain from '../../blockchain'
|
||||
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
|
||||
|
||||
export async function processStartVoting(
|
||||
blockchain: Blockchain,
|
||||
data: CapitalContract.Actions.StartVoting.IStartVoting,
|
||||
) {
|
||||
const { coopname, project_hash } = data
|
||||
|
||||
// Состояние до: данные проекта
|
||||
const projectBefore = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'projects',
|
||||
1,
|
||||
project_hash,
|
||||
project_hash,
|
||||
3,
|
||||
'sha256',
|
||||
))[0]
|
||||
console.log('projectBefore: ', projectBefore)
|
||||
// 1) Запускаем голосование по проекту
|
||||
const txStart = await blockchain.api.transact(
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
account: CapitalContract.contractName.production,
|
||||
name: CapitalContract.Actions.StartVoting.actionName,
|
||||
authorization: [{ actor: coopname, permission: 'active' }],
|
||||
data,
|
||||
},
|
||||
],
|
||||
},
|
||||
{ blocksBehind: 3, expireSeconds: 30 },
|
||||
)
|
||||
getTotalRamUsage(txStart)
|
||||
expect(txStart.transaction_id).toBeDefined()
|
||||
|
||||
// Состояние после: данные проекта
|
||||
const projectAfter = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'projects',
|
||||
1,
|
||||
project_hash,
|
||||
project_hash,
|
||||
3,
|
||||
'sha256',
|
||||
))[0]
|
||||
console.log('projectAfter: ', projectAfter)
|
||||
return {
|
||||
projectHash: project_hash,
|
||||
txStartId: txStart.transaction_id,
|
||||
projectBefore,
|
||||
projectAfter,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import { expect } from 'vitest'
|
||||
import { CapitalContract } from 'cooptypes'
|
||||
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
|
||||
import type Blockchain from '../../blockchain'
|
||||
|
||||
export async function refreshSegment(
|
||||
blockchain: Blockchain,
|
||||
coopname: string,
|
||||
projectHash: string,
|
||||
username: string,
|
||||
) {
|
||||
console.log(`\n🔄 Обновляем сегмент CRPS для ${username} в проекте ${projectHash}`)
|
||||
|
||||
// Получение данных сегмента перед обновлением
|
||||
const prevSegment = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
CapitalContract.Tables.Segments.tableName,
|
||||
1000,
|
||||
)).filter((s: any) => s.project_hash === projectHash && s.username === username)[0]
|
||||
|
||||
// console.log('📊 Сегмент до обновления:', prevSegment)
|
||||
|
||||
// Обновление сегмента через rfrshsegment
|
||||
const refreshSegmentData: CapitalContract.Actions.RefreshSegment.IRefreshSegment = {
|
||||
coopname,
|
||||
project_hash: projectHash,
|
||||
username,
|
||||
}
|
||||
|
||||
const refreshResult = await blockchain.transactWithLogs(
|
||||
[
|
||||
{
|
||||
account: CapitalContract.contractName.production,
|
||||
name: CapitalContract.Actions.RefreshSegment.actionName,
|
||||
authorization: [{ actor: coopname, permission: 'active' }],
|
||||
data: refreshSegmentData,
|
||||
},
|
||||
],
|
||||
)
|
||||
|
||||
getTotalRamUsage(refreshResult)
|
||||
expect(refreshResult.transaction_id).toBeDefined()
|
||||
|
||||
// Получение обновленного сегмента
|
||||
const updatedSegment = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
CapitalContract.Tables.Segments.tableName,
|
||||
1000,
|
||||
)).filter((s: any) => s.project_hash === projectHash && s.username === username)[0]
|
||||
|
||||
// console.log('📊 Сегмент после обновления:', updatedSegment)
|
||||
console.log(`✅ Сегмент для ${username} обновлен успешно!`)
|
||||
|
||||
return {
|
||||
transactionId: refreshResult.transaction_id,
|
||||
prevSegment,
|
||||
updatedSegment,
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,69 @@
|
||||
export function registerContributor() {
|
||||
import { expect } from 'vitest'
|
||||
import { CapitalContract } from 'cooptypes'
|
||||
import { fakeDocument } from '../shared/fakeDocument'
|
||||
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
|
||||
import { processApprove } from './processApprove'
|
||||
|
||||
export async function registerContributor(
|
||||
blockchain: any,
|
||||
coopname: string,
|
||||
username: string,
|
||||
contributorHash: string,
|
||||
ratePerHour: string,
|
||||
hoursPerDay: number = 8,
|
||||
) {
|
||||
const contract = fakeDocument
|
||||
contract.signatures[0].signer = username
|
||||
const data: CapitalContract.Actions.RegisterContributor.IRegisterContributor = {
|
||||
coopname,
|
||||
username,
|
||||
contributor_hash: contributorHash,
|
||||
rate_per_hour: ratePerHour,
|
||||
hours_per_day: hoursPerDay,
|
||||
is_external_contract: false,
|
||||
contract,
|
||||
storage_agreement: fakeDocument,
|
||||
generator_agreement: fakeDocument,
|
||||
blagorost_agreement: fakeDocument,
|
||||
}
|
||||
|
||||
const result = await blockchain.api.transact(
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
account: CapitalContract.contractName.production,
|
||||
name: CapitalContract.Actions.RegisterContributor.actionName,
|
||||
authorization: [{ actor: coopname, permission: 'active' }],
|
||||
data,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
blocksBehind: 3,
|
||||
expireSeconds: 30,
|
||||
},
|
||||
)
|
||||
getTotalRamUsage(result)
|
||||
expect(result.transaction_id).toBeDefined()
|
||||
|
||||
// Аппрув председателя (через совет) по хэшу договора УХД
|
||||
await processApprove(blockchain, coopname, contributorHash)
|
||||
|
||||
// Проверяем, что контрибьютор активирован и договор сохранён
|
||||
const contributor = (
|
||||
await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'contributors',
|
||||
1,
|
||||
username,
|
||||
username,
|
||||
2,
|
||||
'i64',
|
||||
)
|
||||
)[0]
|
||||
|
||||
expect(contributor).toBeDefined()
|
||||
expect(contributor.status).toBe('active')
|
||||
expect(contributor.contract).toBeDefined()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import { expect } from 'vitest'
|
||||
import { CapitalContract } from 'cooptypes'
|
||||
import { fakeDocument } from '../shared/fakeDocument'
|
||||
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
|
||||
import { processApprove } from './processApprove'
|
||||
|
||||
export async function signAppendix(
|
||||
blockchain: any,
|
||||
coopname: string,
|
||||
username: string,
|
||||
projectHash: string,
|
||||
appendixHash: string,
|
||||
) {
|
||||
// Подписание приложения пайщиком (через кооператив, как и в контракте)
|
||||
const data: CapitalContract.Actions.GetClearance.IGetClearance = {
|
||||
coopname,
|
||||
username,
|
||||
project_hash: projectHash,
|
||||
appendix_hash: appendixHash,
|
||||
document: fakeDocument,
|
||||
}
|
||||
|
||||
const result = await blockchain.api.transact(
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
account: CapitalContract.contractName.production,
|
||||
name: CapitalContract.Actions.GetClearance.actionName,
|
||||
authorization: [{ actor: coopname, permission: 'active' }],
|
||||
data,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
blocksBehind: 3,
|
||||
expireSeconds: 30,
|
||||
},
|
||||
)
|
||||
|
||||
getTotalRamUsage(result)
|
||||
expect(result.transaction_id).toBeDefined()
|
||||
|
||||
// Аппрув председателя (через совет) по хэшу приложения
|
||||
await processApprove(blockchain, coopname, appendixHash)
|
||||
|
||||
// Проверяем, что приложение применилось к контрибьютору, а запись удалена
|
||||
const contributor = (
|
||||
await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'contributors',
|
||||
1,
|
||||
username,
|
||||
username,
|
||||
2,
|
||||
'i64',
|
||||
)
|
||||
)[0]
|
||||
|
||||
expect(contributor).toBeDefined()
|
||||
expect(contributor.appendixes).toContain(projectHash)
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import { signAgreement } from '../soviet/signAgreement'
|
||||
import { getCoopProgramWallet, getUserProgramWallet } from '../wallet/walletUtils'
|
||||
import { sourceProgramId, sourceProgramName } from './consts'
|
||||
|
||||
export async function signGenerationAgreement(
|
||||
export async function signGenerationContract(
|
||||
blockchain: any,
|
||||
coopname: string,
|
||||
username: string,
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
import { expect } from 'vitest'
|
||||
import { CapitalContract } from 'cooptypes'
|
||||
import type Blockchain from '../../blockchain'
|
||||
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
|
||||
|
||||
/**
|
||||
* Интерфейс для распределения голосов
|
||||
*/
|
||||
export interface VoteInput {
|
||||
recipient: string
|
||||
amount: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Создает равномерное распределение голосующей суммы между участниками (кроме самого голосующего)
|
||||
* @param voters список всех участников голосования
|
||||
* @param voter участник который голосует (исключается из распределения)
|
||||
* @param votingAmount общая голосующая сумма для распределения
|
||||
* @returns массив распределения голосов
|
||||
*/
|
||||
export function createVoteDistribution(
|
||||
voters: string[],
|
||||
voter: string,
|
||||
votingAmount: string,
|
||||
): VoteInput[] {
|
||||
const otherVoters = voters.filter(v => v !== voter)
|
||||
const votingAmountFloat = parseFloat(votingAmount.split(' ')[0])
|
||||
const currency = votingAmount.split(' ')[1]
|
||||
|
||||
// Равномерно распределяем сумму между остальными участниками
|
||||
const amountPerVoter = votingAmountFloat / otherVoters.length
|
||||
|
||||
return otherVoters.map(recipient => ({
|
||||
recipient,
|
||||
amount: `${amountPerVoter.toFixed(4)} ${currency}`,
|
||||
}))
|
||||
}
|
||||
|
||||
/**
|
||||
* Функция для голосования по методу Водянова
|
||||
* @param blockchain экземпляр блокчейна
|
||||
* @param coopname имя кооператива
|
||||
* @param voter участник который голосует
|
||||
* @param projectHash хэш проекта
|
||||
* @param voteDistribution массив голосов для распределения
|
||||
*/
|
||||
export async function submitVote(
|
||||
blockchain: Blockchain,
|
||||
coopname: string,
|
||||
voter: string,
|
||||
projectHash: string,
|
||||
voteDistribution: VoteInput[],
|
||||
) {
|
||||
console.log(`\n🗳️ Начало голосования участника ${voter}`)
|
||||
|
||||
// Состояние до: данные проекта
|
||||
const projectBefore = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'projects',
|
||||
1,
|
||||
projectHash,
|
||||
projectHash,
|
||||
3,
|
||||
'sha256',
|
||||
))[0]
|
||||
|
||||
// Получаем все голоса по проекту до голосования
|
||||
const votesBefore = await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'votes',
|
||||
100,
|
||||
projectHash,
|
||||
projectHash,
|
||||
2,
|
||||
'sha256',
|
||||
)
|
||||
|
||||
// Получаем голоса конкретного участника до голосования
|
||||
const voterVotesBefore = votesBefore.filter((vote: any) =>
|
||||
vote.project_hash === projectHash && vote.voter === voter,
|
||||
)
|
||||
|
||||
console.log('📊 Состояние до голосования:')
|
||||
console.log('▶ Проект votes_received:', projectBefore.voting.votes_received)
|
||||
console.log('▶ Всего голосов в таблице:', votesBefore.filter((v: any) => v.project_hash === projectHash).length)
|
||||
console.log('▶ Голосов от участника:', voterVotesBefore.length)
|
||||
|
||||
// Формируем данные для транзакции
|
||||
const votes = voteDistribution.map(v => ({
|
||||
recipient: v.recipient,
|
||||
amount: v.amount,
|
||||
}))
|
||||
|
||||
const data: any = {
|
||||
coopname,
|
||||
voter,
|
||||
project_hash: projectHash,
|
||||
votes,
|
||||
}
|
||||
|
||||
console.log('📝 Данные голосования:', JSON.stringify(data, null, 2))
|
||||
|
||||
// Отправляем транзакцию голосования
|
||||
const txResult = await blockchain.api.transact(
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
account: CapitalContract.contractName.production,
|
||||
name: CapitalContract.Actions.SubmitVote.actionName,
|
||||
authorization: [{ actor: coopname, permission: 'active' }],
|
||||
data,
|
||||
},
|
||||
],
|
||||
},
|
||||
{ blocksBehind: 3, expireSeconds: 30 },
|
||||
)
|
||||
getTotalRamUsage(txResult)
|
||||
expect(txResult.transaction_id).toBeDefined()
|
||||
|
||||
// Состояние после: данные проекта
|
||||
const projectAfter = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'projects',
|
||||
1,
|
||||
projectHash,
|
||||
projectHash,
|
||||
3,
|
||||
'sha256',
|
||||
))[0]
|
||||
|
||||
// Получаем все голоса по проекту после голосования
|
||||
const votesAfter = await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'votes',
|
||||
100,
|
||||
projectHash,
|
||||
projectHash,
|
||||
2,
|
||||
'sha256',
|
||||
)
|
||||
|
||||
// Получаем голоса конкретного участника после голосования
|
||||
const voterVotesAfter = votesAfter.filter((vote: any) =>
|
||||
vote.project_hash === projectHash && vote.voter === voter,
|
||||
)
|
||||
|
||||
console.log('📊 Состояние после голосования:')
|
||||
console.log('▶ Проект votes_received:', projectAfter.voting.votes_received)
|
||||
console.log('▶ Всего голосов в таблице:', votesAfter.filter((v: any) => v.project_hash === projectHash).length)
|
||||
console.log('▶ Голосов от участника:', voterVotesAfter.length)
|
||||
|
||||
return {
|
||||
txId: txResult.transaction_id,
|
||||
projectBefore,
|
||||
projectAfter,
|
||||
votesBefore: votesBefore.filter((v: any) => v.project_hash === projectHash),
|
||||
votesAfter: votesAfter.filter((v: any) => v.project_hash === projectHash),
|
||||
voterVotesBefore,
|
||||
voterVotesAfter,
|
||||
voteInput: voteDistribution,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// Комбинирование индекса (project_hash + username) как в C++ combine_checksum_ids
|
||||
// Возвращает десятичную строку uint128 для использования с key_type 'i128'
|
||||
|
||||
function charToSymbol(c: string): number {
|
||||
if (c === '.') return 0
|
||||
if (c >= '1' && c <= '5') return c.charCodeAt(0) - '1'.charCodeAt(0) + 1
|
||||
if (c >= 'a' && c <= 'z') return c.charCodeAt(0) - 'a'.charCodeAt(0) + 6
|
||||
return 0
|
||||
}
|
||||
|
||||
function nameToUint64(name: string): bigint {
|
||||
let value = 0n
|
||||
let i = 0
|
||||
for (; i < 12 && i < name.length; i++) {
|
||||
value <<= 5n
|
||||
value |= BigInt(charToSymbol(name[i]))
|
||||
}
|
||||
value <<= 4n + 5n * BigInt(12 - i)
|
||||
if (i < name.length) {
|
||||
value |= BigInt(charToSymbol(name[i])) & 0x0fn
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
function hexToBytes(hex: string): Uint8Array {
|
||||
const clean = hex.startsWith('0x') ? hex.slice(2) : hex
|
||||
if (clean.length !== 64) throw new Error('Invalid sha256 hex')
|
||||
const out = new Uint8Array(32)
|
||||
for (let i = 0; i < 32; i++) {
|
||||
out[i] = parseInt(clean.slice(i * 2, i * 2 + 2), 16)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
function readBigUInt64LE(bytes: Uint8Array, offset = 0): bigint {
|
||||
let result = 0n
|
||||
for (let i = 0; i < 8; i++) {
|
||||
result |= BigInt(bytes[offset + i]) << (8n * BigInt(i))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export function makeCombinedChecksum256NameIndexKey(projectHashHex: string, username: string): string {
|
||||
const bytes = hexToBytes(projectHashHex)
|
||||
const truncatedHashLE = readBigUInt64LE(bytes, 0)
|
||||
const nameValue = nameToUint64(username)
|
||||
const key128 = (truncatedHashLE << 64n) | nameValue
|
||||
return key128.toString()
|
||||
}
|
||||
|
||||
export function makeCombinedChecksum256Uint64IndexKey(projectHashHex: string, id: number): string {
|
||||
const bytes = hexToBytes(projectHashHex)
|
||||
const truncatedHashLE = readBigUInt64LE(bytes, 0)
|
||||
const idValue = BigInt(id)
|
||||
const key128 = (truncatedHashLE << 64n) | idValue
|
||||
return key128.toString()
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
interface ActionTrace {
|
||||
act?: {
|
||||
account: string
|
||||
name: string
|
||||
authorization?: { actor: string }[]
|
||||
}
|
||||
console?: string
|
||||
inline_traces?: ActionTrace[]
|
||||
}
|
||||
|
||||
interface TransactionResult {
|
||||
processed?: {
|
||||
action_traces?: ActionTrace[]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Извлекает и печатает все консоль логи из результата транзакции
|
||||
* @param transaction - результат транзакции blockchain.api.transact
|
||||
*/
|
||||
export function consoleIt(transaction: TransactionResult): void {
|
||||
if (!transaction?.processed?.action_traces) {
|
||||
console.log('❌ Нет данных о транзакции или action traces')
|
||||
return
|
||||
}
|
||||
|
||||
console.log('\n📋 КОНСОЛЬ ЛОГИ ТРАНЗАКЦИИ:')
|
||||
console.log('='.repeat(50))
|
||||
|
||||
let hasConsoleLogs = false
|
||||
|
||||
function processActionTrace(trace: ActionTrace, depth = 0): void {
|
||||
const indent = ' '.repeat(depth)
|
||||
|
||||
// Получаем информацию о действии
|
||||
const account = trace.act?.account || 'unknown'
|
||||
const action = trace.act?.name || 'unknown'
|
||||
const actor = trace.act?.authorization?.[0]?.actor || 'unknown'
|
||||
|
||||
// Печатаем заголовок действия
|
||||
console.log(`${indent}🔧 ${account}:${action} (actor: ${actor})`)
|
||||
|
||||
// Если есть консоль логи, печатаем их
|
||||
if (trace.console) {
|
||||
hasConsoleLogs = true
|
||||
const logs = trace.console.split('\n').filter(log => log.trim())
|
||||
|
||||
if (logs.length > 0) {
|
||||
console.log(`${indent}📝 Console output:`)
|
||||
logs.forEach((log) => {
|
||||
console.log(`${indent} ${log}`)
|
||||
})
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.log(`${indent} (нет консоль логов)`)
|
||||
}
|
||||
|
||||
// Обрабатываем inline traces рекурсивно
|
||||
if (trace.inline_traces?.length) {
|
||||
console.log(`${indent} ↳ Inline actions:`)
|
||||
trace.inline_traces.forEach((inlineTrace) => {
|
||||
processActionTrace(inlineTrace, depth + 1)
|
||||
})
|
||||
}
|
||||
|
||||
if (depth === 0) {
|
||||
console.log('') // Пустая строка между действиями верхнего уровня
|
||||
}
|
||||
}
|
||||
|
||||
// Обрабатываем все action traces
|
||||
transaction.processed.action_traces.forEach((trace, index) => {
|
||||
console.log(`\n📍 Action ${index + 1}:`)
|
||||
processActionTrace(trace, 0)
|
||||
})
|
||||
|
||||
if (!hasConsoleLogs) {
|
||||
console.log('ℹ️ В транзакции не найдено консоль логов')
|
||||
}
|
||||
|
||||
console.log('='.repeat(50))
|
||||
console.log('🏁 Конец логов транзакции\n')
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
export const fakeDocument = {
|
||||
version: '1.0.0',
|
||||
hash: '157192b276da23cc84ab078fc8755c051c5f0430bf4802e55718221e6b76c777',
|
||||
doc_hash: '157192b276da23cc84ab078fc8755c051c5f0430bf4802e55718221e6b76c777',
|
||||
meta_hash: '157192b276da23cc84ab078fc8755c051c5f0430bf4802e55718221e6b76c777',
|
||||
meta: '{}',
|
||||
signatures: [{
|
||||
id: 1,
|
||||
signed_hash: '157192b276da23cc84ab078fc8755c051c5f0430bf4802e55718221e6b76c777',
|
||||
signer: 'cooperative1',
|
||||
public_key: 'EOS5JhMfxbsNebajHcTEK8yC9uNN9Dit9hEmzE8ri8yMhhzxrLg3J',
|
||||
signature: 'SIG_K1_KmKWPBC8dZGGDGhbKEoZEzPr3h5crRrR2uLdGRF5DJbeibY1MY1bZ9sPwHsgmPfiGFv9psfoCVsXFh9TekcLuvaeuxRKA8',
|
||||
signed_at: '2025-05-14T12:22:26',
|
||||
meta: '{}',
|
||||
}],
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
export const fakeVote = {
|
||||
version: '1.0.0',
|
||||
coopname: 'voskhod',
|
||||
username: 'ant',
|
||||
decision_id: '1',
|
||||
signed_at: '2025-05-14T12:22:26.000',
|
||||
signed_hash: '157192B276DA23CC84AB078FC8755C051C5F0430BF4802E55718221E6B76C777',
|
||||
signature: 'SIG_K1_KmKWPBC8dZGGDGhbKEoZEzPr3h5crRrR2uLdGRF5DJbeibY1MY1bZ9sPwHsgmPfiGFv9psfoCVsXFh9TekcLuvaeuxRKA8',
|
||||
public_key: 'PUB_K1_5JhMfxbsNebajHcTEK8yC9uNN9Dit9hEmzE8ri8yMhhzzEtUA4',
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Форматирует дату в строку формата 'YYYY-MM-DDTHH:mm:ss'
|
||||
* @param date - Дата для форматирования
|
||||
* @returns Строка в формате 'YYYY-MM-DDTHH:mm:ss'
|
||||
*/
|
||||
export function formatDateToISOString(date: Date): string {
|
||||
return date.toISOString().slice(0, 19);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export * from './combinedKeys'
|
||||
export * from './consoleIt'
|
||||
export * from './fakeDocument'
|
||||
export * from './fakeVote'
|
||||
export * from './formatDateToISOString'
|
||||
@@ -1,14 +1,12 @@
|
||||
import { SovietContract } from 'cooptypes'
|
||||
import { expect } from 'vitest'
|
||||
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
|
||||
import { fakeDocument } from './fakeDocument'
|
||||
import { fakeDocument } from '../shared/fakeDocument'
|
||||
import type Blockchain from '../../blockchain'
|
||||
import { fakeVote } from '../shared/fakeVote'
|
||||
|
||||
export async function processDecision(blockchain: any, decisionId: number) {
|
||||
const voteData: SovietContract.Actions.Decisions.VoteFor.IVoteForDecision = {
|
||||
coopname: 'voskhod',
|
||||
member: 'ant',
|
||||
decision_id: decisionId,
|
||||
}
|
||||
export async function processDecision(blockchain: Blockchain, decisionId: number) {
|
||||
const voteData: SovietContract.Actions.Decisions.VoteFor.IVoteForDecision = fakeVote
|
||||
voteData.decision_id = decisionId
|
||||
|
||||
const authData: SovietContract.Actions.Decisions.Authorize.IAuthorize = {
|
||||
coopname: 'voskhod',
|
||||
@@ -53,5 +51,4 @@ export async function processDecision(blockchain: any, decisionId: number) {
|
||||
)
|
||||
|
||||
getTotalRamUsage(result)
|
||||
expect(result.transaction_id).toBeDefined()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { SovietContract } from 'cooptypes'
|
||||
import type Blockchain from '../../blockchain'
|
||||
import { processDecision } from './processDecision'
|
||||
|
||||
export async function processLastDecision(blockchain: Blockchain, coopname: string) {
|
||||
// Получаем последнее решение для голосования
|
||||
const decisions = await blockchain.getTableRows(
|
||||
SovietContract.contractName.production,
|
||||
coopname,
|
||||
SovietContract.Tables.Decisions.tableName,
|
||||
100, // берем больше, чтобы гарантированно получить все
|
||||
)
|
||||
console.log('Активные вопросы на повестке: ', decisions)
|
||||
// Берем ПОСЛЕДНИЙ элемент массива (самый свежий)
|
||||
const lastDecision = decisions[decisions.length - 1]
|
||||
|
||||
// 3. Голосуем за решение совета
|
||||
await processDecision(blockchain, lastDecision.id)
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import { GatewayContract, WalletContract } from 'cooptypes'
|
||||
import { expect } from 'vitest'
|
||||
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
|
||||
import { generateRandomSHA256 } from '../../utils/randomHash'
|
||||
import { compareTokenAmounts, getCoopProgramWallet, getCoopWallet, getDeposit, getUserProgramWallet } from './walletUtils'
|
||||
import { compareTokenAmounts, getCirculationAccount, getCoopProgramWallet, getDeposit, getUserProgramWallet } from './walletUtils'
|
||||
|
||||
export async function depositToWallet(blockchain: any, coopname: string, username: string, amount: number) {
|
||||
const depositId = randomInt(100000)
|
||||
@@ -48,8 +48,7 @@ export async function depositToWallet(blockchain: any, coopname: string, usernam
|
||||
const prevUserWallet = await getUserProgramWallet(blockchain, coopname, username, 1)
|
||||
const prevUserWalletAvailable = prevUserWallet?.available || '0.0000 RUB'
|
||||
|
||||
const prevCoopWallet = await getCoopWallet(blockchain, coopname)
|
||||
const prevCoopWalletAvailable = prevCoopWallet?.circulating_account?.available || '0.0000 RUB'
|
||||
const prevCoopWalletAvailable = (await getCirculationAccount(blockchain, coopname)).available
|
||||
|
||||
const data2: GatewayContract.Actions.CompleteIncome.ICompleteIncome = {
|
||||
coopname,
|
||||
@@ -85,7 +84,7 @@ export async function depositToWallet(blockchain: any, coopname: string, usernam
|
||||
|
||||
// Проверяем изменение балансов
|
||||
compareTokenAmounts(prevUserWalletAvailable, userWallet.available, amount)
|
||||
compareTokenAmounts(prevCoopWalletAvailable, (await getCoopWallet(blockchain, coopname)).circulating_account.available, amount)
|
||||
compareTokenAmounts(prevCoopWalletAvailable, (await getCirculationAccount(blockchain, coopname)).available, amount)
|
||||
|
||||
return { depositId, program, userWallet }
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { FundContract, SovietContract, WalletContract } from 'cooptypes'
|
||||
import { FundContract, LedgerContract, SovietContract, WalletContract } from 'cooptypes'
|
||||
import { expect } from 'vitest'
|
||||
import type Blockchain from '../../blockchain'
|
||||
import { circulationAccountId } from '../capital/consts'
|
||||
|
||||
export async function getUserProgramWallet(blockchain: any, coopname: string, username: string, program_id: number) {
|
||||
const wallets = await blockchain.getTableRows(
|
||||
@@ -16,6 +17,11 @@ export async function getUserProgramWallet(blockchain: any, coopname: string, us
|
||||
return wallets.find((el: any) => el.program_id === program_id)
|
||||
}
|
||||
|
||||
export async function getUserProgramWalletAmount(blockchain: any, coopname: string, username: string, program_id: number) {
|
||||
const wallet = await getUserProgramWallet(blockchain, coopname, username, program_id)
|
||||
return wallet ? `${(parseFloat(wallet.available) + parseFloat(wallet.blocked)).toFixed(4)} RUB` : '0.0000 RUB'
|
||||
}
|
||||
|
||||
export async function getCoopProgramWallet(blockchain: any, coopname: string, program_id: number) {
|
||||
const program = await blockchain.getTableRows(
|
||||
SovietContract.contractName.production,
|
||||
@@ -29,6 +35,25 @@ export async function getCoopProgramWallet(blockchain: any, coopname: string, pr
|
||||
return program[0]
|
||||
}
|
||||
|
||||
export async function getLedgerAccounts(blockchain: Blockchain, coopname: string) {
|
||||
return (await blockchain.getTableRows(
|
||||
LedgerContract.contractName.production,
|
||||
coopname,
|
||||
LedgerContract.Tables.Laccount.tableName,
|
||||
100,
|
||||
))
|
||||
}
|
||||
|
||||
export async function getLedgerAccountById(blockchain: Blockchain, coopname: string, accountId: number) {
|
||||
const accounts = await getLedgerAccounts(blockchain, coopname)
|
||||
return accounts.find((account: any) => account.id === accountId)
|
||||
}
|
||||
|
||||
export async function getCirculationAccount(blockchain: Blockchain, coopname: string) {
|
||||
const accounts = await getLedgerAccounts(blockchain, coopname)
|
||||
return accounts.find((account: any) => account.id === circulationAccountId) || { available: '0.0000 RUB' }
|
||||
}
|
||||
|
||||
export async function getCoopWallet(blockchain: any, coopname: string) {
|
||||
return (await blockchain.getTableRows(
|
||||
FundContract.contractName.production,
|
||||
@@ -56,5 +81,5 @@ export function compareTokenAmounts(prevAmount: string, currentAmount: string, e
|
||||
const current = parseFloat(currentAmount.split(' ')[0])
|
||||
const expected = prev + expectedIncrease
|
||||
|
||||
expect(current).toBe(expected)
|
||||
expect(current).toBeCloseTo(expected, 4)
|
||||
}
|
||||
|
||||
@@ -16,3 +16,5 @@ export async function sendPostToCoopbackWithSecret(url: string, data: any) {
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export * from './randomData'
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
import { randomBytes } from 'node:crypto'
|
||||
|
||||
/**
|
||||
* Генерирует случайное текстовое содержимое для проектов
|
||||
* @param minLength минимальная длина текста (по умолчанию 500)
|
||||
* @param maxLength максимальная длина текста (по умолчанию 2000)
|
||||
* @returns строка с случайным текстовым содержимым
|
||||
*/
|
||||
export function generateRandomProjectData(minLength = 500, maxLength = 2000): string {
|
||||
const templates = [
|
||||
'Описание проекта',
|
||||
'Цели и задачи',
|
||||
'Ожидаемые результаты',
|
||||
'Этапы реализации',
|
||||
'Требования к участникам',
|
||||
'Технические спецификации',
|
||||
'План работ',
|
||||
'Бюджет проекта',
|
||||
'Риски и ограничения',
|
||||
'Методология',
|
||||
]
|
||||
|
||||
const words = [
|
||||
'разработка',
|
||||
'система',
|
||||
'модуль',
|
||||
'компонент',
|
||||
'интерфейс',
|
||||
'база данных',
|
||||
'архитектура',
|
||||
'тестирование',
|
||||
'оптимизация',
|
||||
'интеграция',
|
||||
'документация',
|
||||
'анализ',
|
||||
'проектирование',
|
||||
'внедрение',
|
||||
'поддержка',
|
||||
'масштабирование',
|
||||
'безопасность',
|
||||
'производительность',
|
||||
'надежность',
|
||||
'мониторинг',
|
||||
'отчетность',
|
||||
'функциональность',
|
||||
'пользователь',
|
||||
'администратор',
|
||||
'контент',
|
||||
'данные',
|
||||
'процесс',
|
||||
'workflow',
|
||||
'автоматизация',
|
||||
'управление',
|
||||
'контроль',
|
||||
]
|
||||
|
||||
const targetLength = Math.floor(Math.random() * (maxLength - minLength)) + minLength
|
||||
let content = ''
|
||||
|
||||
// Добавляем секции
|
||||
while (content.length < targetLength) {
|
||||
const template = templates[Math.floor(Math.random() * templates.length)]
|
||||
content += `\n\n## ${template}\n\n`
|
||||
|
||||
// Добавляем параграфы
|
||||
const paragraphCount = Math.floor(Math.random() * 3) + 1
|
||||
for (let i = 0; i < paragraphCount; i++) {
|
||||
const sentenceCount = Math.floor(Math.random() * 5) + 3
|
||||
for (let j = 0; j < sentenceCount; j++) {
|
||||
const wordCount = Math.floor(Math.random() * 10) + 5
|
||||
const sentence = []
|
||||
for (let k = 0; k < wordCount; k++) {
|
||||
sentence.push(words[Math.floor(Math.random() * words.length)])
|
||||
}
|
||||
content += `${sentence.join(' ')}. `
|
||||
}
|
||||
content += '\n\n'
|
||||
}
|
||||
|
||||
// Добавляем случайные данные для разнообразия
|
||||
if (Math.random() > 0.7) {
|
||||
content += `\n### Дополнительная информация\n\n`
|
||||
content += `ID: ${randomBytes(8).toString('hex')}\n`
|
||||
content += `Timestamp: ${new Date().toISOString()}\n`
|
||||
content += `Hash: ${randomBytes(16).toString('hex')}\n\n`
|
||||
}
|
||||
}
|
||||
|
||||
return content.substring(0, targetLength)
|
||||
}
|
||||
|
||||
/**
|
||||
* Генерирует расширенное описание проекта
|
||||
*/
|
||||
export function generateRandomDescription(): string {
|
||||
const descriptions = [
|
||||
'Инновационный проект по разработке современного решения для автоматизации бизнес-процессов',
|
||||
'Комплексная система управления ресурсами предприятия с использованием передовых технологий',
|
||||
'Платформа для координации работы распределенных команд и управления проектами',
|
||||
'Решение для цифровой трансформации традиционных процессов с акцентом на эффективность',
|
||||
'Система интеграции данных из различных источников с возможностью аналитики в реальном времени',
|
||||
]
|
||||
|
||||
const extras = [
|
||||
'Включает модули отчетности, мониторинга и аналитики.',
|
||||
'Поддерживает интеграцию с внешними системами через API.',
|
||||
'Обеспечивает высокую производительность и масштабируемость.',
|
||||
'Реализует современные подходы к безопасности данных.',
|
||||
'Предоставляет интуитивный пользовательский интерфейс.',
|
||||
]
|
||||
|
||||
let result = descriptions[Math.floor(Math.random() * descriptions.length)]
|
||||
result += ` ${extras[Math.floor(Math.random() * extras.length)]}`
|
||||
result += ` ${extras[Math.floor(Math.random() * extras.length)]}`
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Генерирует метаданные проекта в виде JSON
|
||||
*/
|
||||
export function generateRandomMeta(): string {
|
||||
const meta = {
|
||||
version: `${Math.floor(Math.random() * 5) + 1}.${Math.floor(Math.random() * 10)}.${Math.floor(Math.random() * 100)}`,
|
||||
category: ['development', 'infrastructure', 'analytics', 'automation'][Math.floor(Math.random() * 4)],
|
||||
priority: ['low', 'medium', 'high', 'critical'][Math.floor(Math.random() * 4)],
|
||||
tags: Array.from({ length: Math.floor(Math.random() * 5) + 3 }, () =>
|
||||
['backend', 'frontend', 'devops', 'testing', 'documentation', 'security', 'performance'][
|
||||
Math.floor(Math.random() * 7)
|
||||
]),
|
||||
created_by: 'system',
|
||||
created_at: new Date().toISOString(),
|
||||
last_modified: new Date().toISOString(),
|
||||
complexity: Math.floor(Math.random() * 10) + 1,
|
||||
estimated_duration_days: Math.floor(Math.random() * 180) + 30,
|
||||
}
|
||||
|
||||
return JSON.stringify(meta)
|
||||
}
|
||||
|
||||
/**
|
||||
* Генерирует описание имущества
|
||||
*/
|
||||
export function generateRandomPropertyDescription(): string {
|
||||
const types = [
|
||||
'Серверное оборудование',
|
||||
'Лицензии на программное обеспечение',
|
||||
'Офисное оборудование',
|
||||
'Интеллектуальная собственность',
|
||||
'Доменные имена и хостинг',
|
||||
'Техническая документация',
|
||||
'Базы данных',
|
||||
'Исследовательские материалы',
|
||||
]
|
||||
|
||||
const details = [
|
||||
'включает все необходимые компоненты и документацию',
|
||||
'с полным комплектом сопроводительных материалов',
|
||||
'в отличном техническом состоянии',
|
||||
'прошедшее полную проверку и тестирование',
|
||||
'с гарантийным обслуживанием',
|
||||
'соответствует всем требованиям и стандартам',
|
||||
]
|
||||
|
||||
const type = types[Math.floor(Math.random() * types.length)]
|
||||
const detail = details[Math.floor(Math.random() * details.length)]
|
||||
const additional = `ID: ${randomBytes(4).toString('hex').toUpperCase()}`
|
||||
|
||||
return `${type} - ${detail}. ${additional}. ${generateRandomDescription().slice(0, 100)}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Генерирует случайную сумму взноса для контрибьютора
|
||||
* @param min минимальная сумма (по умолчанию 500)
|
||||
* @param max максимальная сумма (по умолчанию 5000)
|
||||
*/
|
||||
export function generateRandomContributionAmount(min = 500, max = 2000): string {
|
||||
const amount = Math.floor(Math.random() * (max - min)) + min
|
||||
return `${amount}.0000 RUB`
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { fakeDocument } from '../tests/shared/fakeDocument'
|
||||
|
||||
/**
|
||||
* Устанавливает подписи документа на основе массива имен аккаунтов
|
||||
* @param usernames массив имен аккаунтов для подписи документа
|
||||
* @returns копия fakeDocument с установленными подписями
|
||||
*/
|
||||
export function setDocumentSignatures(usernames: string[]) {
|
||||
const document = JSON.parse(JSON.stringify(fakeDocument))
|
||||
|
||||
// Очищаем существующие подписи
|
||||
document.signatures = []
|
||||
|
||||
// Создаем подписи для каждого пользователя
|
||||
usernames.forEach((username, index) => {
|
||||
const signature = {
|
||||
id: index + 1,
|
||||
signed_hash: '157192b276da23cc84ab078fc8755c051c5f0430bf4802e55718221e6b76c777',
|
||||
signer: username,
|
||||
public_key: 'EOS5JhMfxbsNebajHcTEK8yC9uNN9Dit9hEmzE8ri8yMhhzxrLg3J',
|
||||
signature: 'SIG_K1_KmKWPBC8dZGGDGhbKEoZEzPr3h5crRrR2uLdGRF5DJbeibY1MY1bZ9sPwHsgmPfiGFv9psfoCVsXFh9TekcLuvaeuxRKA8',
|
||||
signed_at: '2025-05-14T12:22:26',
|
||||
meta: '{}',
|
||||
}
|
||||
document.signatures.push(signature)
|
||||
})
|
||||
|
||||
return document
|
||||
}
|
||||
Executable
+115
@@ -0,0 +1,115 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Конфигурация
|
||||
TOTAL_RUNS=100
|
||||
LOG_FILE="stress-test-$(date +%Y%m%d-%H%M%S).log"
|
||||
FAILED=false
|
||||
SUCCESS_COUNT=0
|
||||
FAILED_COUNT=0
|
||||
START_TIME=$(date +%s)
|
||||
|
||||
# Функция для отображения прогресса
|
||||
show_progress() {
|
||||
local current=$1
|
||||
local total=$2
|
||||
local success=$3
|
||||
local failed=$4
|
||||
|
||||
local percent=0
|
||||
if [ $total -gt 0 ]; then
|
||||
percent=$((current * 100 / total))
|
||||
fi
|
||||
local progress_bar=""
|
||||
local bar_width=50
|
||||
local filled=0
|
||||
if [ $total -gt 0 ]; then
|
||||
filled=$((current * bar_width / total))
|
||||
fi
|
||||
|
||||
for ((j=1; j<=bar_width; j++)); do
|
||||
if [ $j -le $filled ]; then
|
||||
progress_bar="${progress_bar}█"
|
||||
else
|
||||
progress_bar="${progress_bar}░"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Прогресс: [${progress_bar}] ${percent}% (${current}/${total}) | ✅ ${success} | ❌ ${failed}"
|
||||
}
|
||||
|
||||
echo "════════════════════════════════════════════════════" | tee -a "$LOG_FILE"
|
||||
echo " СТРЕСС-ТЕСТИРОВАНИЕ CAPITAL CONTRACT" | tee -a "$LOG_FILE"
|
||||
echo "════════════════════════════════════════════════════" | tee -a "$LOG_FILE"
|
||||
echo "Запуск: $(date)" | tee -a "$LOG_FILE"
|
||||
echo "Количество прогонов: $TOTAL_RUNS" | tee -a "$LOG_FILE"
|
||||
echo "Лог-файл: $LOG_FILE" | tee -a "$LOG_FILE"
|
||||
echo "════════════════════════════════════════════════════" | tee -a "$LOG_FILE"
|
||||
echo "" | tee -a "$LOG_FILE"
|
||||
|
||||
for ((i=1; i<=TOTAL_RUNS; i++)); do
|
||||
RUN_START=$(date +%s)
|
||||
|
||||
echo "════════════════════════════════════════" | tee -a "$LOG_FILE"
|
||||
echo "📊 Запуск #$i из $TOTAL_RUNS" | tee -a "$LOG_FILE"
|
||||
echo "════════════════════════════════════════" | tee -a "$LOG_FILE"
|
||||
|
||||
# Запускаем тест и сохраняем вывод
|
||||
npx vitest run capital.test --run --testTimeout=60000 2>&1 | tee -a "$LOG_FILE"
|
||||
TEST_EXIT_CODE=${PIPESTATUS[0]}
|
||||
|
||||
if [ $TEST_EXIT_CODE -eq 0 ]; then
|
||||
RUN_END=$(date +%s)
|
||||
RUN_DURATION=$((RUN_END - RUN_START))
|
||||
SUCCESS_COUNT=$((SUCCESS_COUNT + 1))
|
||||
echo "✅ Запуск #$i успешен (время: ${RUN_DURATION}с)" | tee -a "$LOG_FILE"
|
||||
echo "" | tee -a "$LOG_FILE"
|
||||
else
|
||||
RUN_END=$(date +%s)
|
||||
RUN_DURATION=$((RUN_END - RUN_START))
|
||||
FAILED_COUNT=$((FAILED_COUNT + 1))
|
||||
echo "❌ ТЕСТ УПАЛ НА ЗАПУСКЕ #$i (время: ${RUN_DURATION}с)" | tee -a "$LOG_FILE"
|
||||
echo "⚠️ Проверка на access violation или другие ошибки..." | tee -a "$LOG_FILE"
|
||||
FAILED=true
|
||||
break
|
||||
fi
|
||||
|
||||
# Обновляем прогресс после каждого успешного теста
|
||||
show_progress $((SUCCESS_COUNT + FAILED_COUNT)) $TOTAL_RUNS $SUCCESS_COUNT $FAILED_COUNT
|
||||
echo "" | tee -a "$LOG_FILE"
|
||||
done
|
||||
|
||||
END_TIME=$(date +%s)
|
||||
TOTAL_DURATION=$((END_TIME - START_TIME))
|
||||
AVG_DURATION=$((SUCCESS_COUNT > 0 ? TOTAL_DURATION / SUCCESS_COUNT : 0))
|
||||
|
||||
# Финальный показ прогресса
|
||||
echo "" | tee -a "$LOG_FILE"
|
||||
show_progress $((SUCCESS_COUNT + FAILED_COUNT)) $TOTAL_RUNS $SUCCESS_COUNT $FAILED_COUNT
|
||||
echo "" | tee -a "$LOG_FILE"
|
||||
echo "════════════════════════════════════════════════════" | tee -a "$LOG_FILE"
|
||||
echo " ИТОГОВАЯ СТАТИСТИКА" | tee -a "$LOG_FILE"
|
||||
echo "════════════════════════════════════════════════════" | tee -a "$LOG_FILE"
|
||||
echo "Завершено: $(date)" | tee -a "$LOG_FILE"
|
||||
echo "Успешных прогонов: $SUCCESS_COUNT" | tee -a "$LOG_FILE"
|
||||
echo "Проваленных прогонов: $FAILED_COUNT" | tee -a "$LOG_FILE"
|
||||
echo "Всего прогонов: $((SUCCESS_COUNT + FAILED_COUNT)) из $TOTAL_RUNS" | tee -a "$LOG_FILE"
|
||||
echo "Общее время: ${TOTAL_DURATION}с ($(($TOTAL_DURATION / 60))м)" | tee -a "$LOG_FILE"
|
||||
if [ $SUCCESS_COUNT -gt 0 ]; then
|
||||
echo "Среднее время на прогон: ${AVG_DURATION}с" | tee -a "$LOG_FILE"
|
||||
fi
|
||||
|
||||
if [ "$FAILED" = true ]; then
|
||||
echo "Статус: ❌ ПРОВАЛЕН" | tee -a "$LOG_FILE"
|
||||
echo "════════════════════════════════════════════════════" | tee -a "$LOG_FILE"
|
||||
echo "" | tee -a "$LOG_FILE"
|
||||
echo "⚠️ Тестирование прервано из-за ошибки на запуске #$i" | tee -a "$LOG_FILE"
|
||||
echo "📋 Полный лог сохранен в: $LOG_FILE" | tee -a "$LOG_FILE"
|
||||
exit 1
|
||||
else
|
||||
echo "Статус: ✅ УСПЕШНО" | tee -a "$LOG_FILE"
|
||||
echo "════════════════════════════════════════════════════" | tee -a "$LOG_FILE"
|
||||
echo "" | tee -a "$LOG_FILE"
|
||||
echo "🎉 Все $TOTAL_RUNS запусков успешны!" | tee -a "$LOG_FILE"
|
||||
echo "📋 Полный лог сохранен в: $LOG_FILE" | tee -a "$LOG_FILE"
|
||||
exit 0
|
||||
fi
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@coopenomics/cleos",
|
||||
"version": "2025.5.5-2",
|
||||
"version": "2026.2.22-alpha-4",
|
||||
"private": true,
|
||||
"description": "",
|
||||
"scripts": {
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
./cleos.sh system newaccount eosio test "EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV" "EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV" --stake-net "0.0001 AXON" --stake-cpu "0.0001 AXON" --buy-ram "0.0001 AXON"
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
./cleos.sh push action eosio powerup '[eosio, test, 1, "150.0000 AXON", true]' -p eosio
|
||||
@@ -0,0 +1,537 @@
|
||||
---
|
||||
name: Конструктор программ регистрации
|
||||
overview: Создание гибкого конструктора программ регистрации с выбором дополнительных соглашений на основе типа аккаунта и кооператива. Для Восхода физики и ИП выбирают между программами "Генерация" и "Капитализация".
|
||||
todos:
|
||||
- id: backend-interfaces
|
||||
content: Создать новые интерфейсы для программ регистрации в agreement-config.interface.ts
|
||||
status: completed
|
||||
- id: backend-programs-config
|
||||
content: Создать конфигурацию программ registration-programs.config.ts с программами для voskhod
|
||||
status: completed
|
||||
- id: backend-agreement-service
|
||||
content: Обновить AgreementConfigurationService с методами работы с программами
|
||||
status: completed
|
||||
- id: backend-documents-service
|
||||
content: Добавить поддержку program_key в RegistrationDocumentsService
|
||||
status: completed
|
||||
- id: backend-dto
|
||||
content: Обновить DTO для добавления program_key и создать RegistrationConfigDTO
|
||||
status: completed
|
||||
- id: backend-resolver
|
||||
content: Добавить query getRegistrationConfig в SystemResolver
|
||||
status: completed
|
||||
- id: frontend-store
|
||||
content: Обновить Registrator store для хранения selectedProgramKey и добавить шаг SelectProgram
|
||||
status: completed
|
||||
- id: frontend-select-component
|
||||
content: Создать компонент SelectProgram.vue для выбора программы
|
||||
status: completed
|
||||
- id: frontend-conditional-rendering
|
||||
content: Добавить условный рендеринг шага SelectProgram в SignUp.vue
|
||||
status: completed
|
||||
- id: frontend-pass-program-key
|
||||
content: Обновить вызов generateRegistrationDocuments для передачи program_key
|
||||
status: completed
|
||||
- id: sdk-update
|
||||
content: Обновить SDK мутацию и добавить query getRegistrationConfig
|
||||
status: completed
|
||||
- id: add-generator-agreement
|
||||
content: Добавить соглашение generator_offer в registration-agreements.config.ts
|
||||
status: completed
|
||||
---
|
||||
|
||||
# План реализации конструктора программ регистрации
|
||||
|
||||
## Архитектура решения
|
||||
|
||||
Система будет состоять из трёх уровней:
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[Frontend: SelectProgram шаг] --> B[Backend: getRegistrationConfig query]
|
||||
A --> C[Backend: generateRegistrationDocuments mutation]
|
||||
B --> D[RegistrationProgramsConfig]
|
||||
C --> D
|
||||
D --> E[AgreementConfigurationService]
|
||||
E --> F[Генерация документов]
|
||||
```
|
||||
|
||||
## 1. Backend: Расширение конфигурации соглашений
|
||||
|
||||
### 1.1 Новые интерфейсы конфигурации
|
||||
|
||||
**Файл:** [`components/controller/src/domain/registration/config/agreement-config.interface.ts`](components/controller/src/domain/registration/config/agreement-config.interface.ts)
|
||||
|
||||
Добавить новые интерфейсы для программ регистрации:
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* Описание программы регистрации с её соглашениями
|
||||
*/
|
||||
export interface IRegistrationProgram {
|
||||
/** Уникальный ключ программы (например, "generation", "capitalization") */
|
||||
key: string;
|
||||
|
||||
/** Название программы для отображения */
|
||||
title: string;
|
||||
|
||||
/** Описание программы */
|
||||
description: string;
|
||||
|
||||
/** URL изображения (опционально) */
|
||||
image_url?: string;
|
||||
|
||||
/** Минимальные требования для участия */
|
||||
requirements?: string;
|
||||
|
||||
/** Для каких типов аккаунтов доступна эта программа */
|
||||
applicable_account_types: AccountType[];
|
||||
|
||||
/** Список ID соглашений, которые требуются для этой программы */
|
||||
agreement_ids: string[];
|
||||
|
||||
/** Порядок отображения */
|
||||
order: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Конфигурация программ регистрации для кооператива
|
||||
*/
|
||||
export interface ICooperativeRegistrationPrograms {
|
||||
/** Название кооператива */
|
||||
coopname: string;
|
||||
|
||||
/** Доступные программы */
|
||||
programs: IRegistrationProgram[];
|
||||
|
||||
/** Нужен ли выбор программы (если false - используется первая подходящая) */
|
||||
requires_selection: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
### 1.2 Создание конфигурации программ
|
||||
|
||||
**Файл:** `components/controller/src/domain/registration/config/registration-programs.config.ts` (новый)
|
||||
|
||||
```typescript
|
||||
export const REGISTRATION_PROGRAMS_CONFIG: ICooperativeRegistrationPrograms[] = [
|
||||
{
|
||||
coopname: 'voskhod',
|
||||
requires_selection: true,
|
||||
programs: [
|
||||
{
|
||||
key: 'generation',
|
||||
title: 'Программа Генерация',
|
||||
description: 'Участвовать в производстве Кооперативной Экономики через вклад временем, имуществом или деньгами в конкретные проекты. Минимальный взнос 10 часов в месяц.',
|
||||
applicable_account_types: [AccountType.individual, AccountType.entrepreneur],
|
||||
agreement_ids: ['generator_offer'], // новое соглашение
|
||||
order: 1,
|
||||
},
|
||||
{
|
||||
key: 'capitalization',
|
||||
title: 'Программа Капитализация',
|
||||
description: 'Участвовать в производстве Кооперативной Экономики через вклад имуществом или денег в систему. Минимальный взнос 100 000 руб в течение 14 дней.',
|
||||
applicable_account_types: [AccountType.individual, AccountType.entrepreneur],
|
||||
agreement_ids: ['blagorost_offer'],
|
||||
order: 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
```
|
||||
|
||||
### 1.3 Добавление нового соглашения generator_offer
|
||||
|
||||
**Файл:** [`components/controller/src/domain/registration/config/registration-agreements.config.ts`](components/controller/src/domain/registration/config/registration-agreements.config.ts)
|
||||
|
||||
Добавить новое соглашение:
|
||||
|
||||
```typescript
|
||||
{
|
||||
id: 'generator_offer',
|
||||
registry_id: Cooperative.Registry.GeneratorOffer.registry_id, // 996
|
||||
agreement_type: 'generator',
|
||||
title: 'Оферта по целевой потребительской программе "Генератор"',
|
||||
checkbox_text: 'Я прочитал и принимаю',
|
||||
link_text: 'оферту по целевой потребительской программе "Генератор"',
|
||||
is_blockchain_agreement: true,
|
||||
link_to_statement: true,
|
||||
applicable_account_types: [], // не используется напрямую, только через программы
|
||||
order: 6,
|
||||
}
|
||||
```
|
||||
|
||||
### 1.4 Обновление AgreementConfigurationService
|
||||
|
||||
**Файл:** [`components/controller/src/domain/registration/services/agreement-configuration.service.ts`](components/controller/src/domain/registration/services/agreement-configuration.service.ts)
|
||||
|
||||
Добавить методы для работы с программами:
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* Получить доступные программы регистрации для кооператива и типа аккаунта
|
||||
*/
|
||||
getAvailablePrograms(coopname: string, accountType: AccountType): IRegistrationProgram[] {
|
||||
const config = REGISTRATION_PROGRAMS_CONFIG.find(c => c.coopname === coopname);
|
||||
if (!config) return [];
|
||||
|
||||
return config.programs
|
||||
.filter(p => p.applicable_account_types.includes(accountType))
|
||||
.sort((a, b) => a.order - b.order);
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить конфигурацию программ для кооператива
|
||||
*/
|
||||
getCooperativeProgramsConfig(coopname: string): ICooperativeRegistrationPrograms | null {
|
||||
return REGISTRATION_PROGRAMS_CONFIG.find(c => c.coopname === coopname) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить соглашения для программы
|
||||
*/
|
||||
getAgreementsForProgram(programKey: string, coopname: string): IAgreementConfigItem[] {
|
||||
const config = REGISTRATION_PROGRAMS_CONFIG.find(c => c.coopname === coopname);
|
||||
if (!config) return [];
|
||||
|
||||
const program = config.programs.find(p => p.key === programKey);
|
||||
if (!program) return [];
|
||||
|
||||
return program.agreement_ids
|
||||
.map(id => this.getAgreementById(id))
|
||||
.filter((a): a is IAgreementConfigItem => a !== null);
|
||||
}
|
||||
```
|
||||
|
||||
Обновить метод `getAgreementsForAccountType`:
|
||||
|
||||
```typescript
|
||||
getAgreementsForAccountType(
|
||||
accountType: AccountType,
|
||||
coopname?: string,
|
||||
programKey?: string
|
||||
): IAgreementConfigItem[] {
|
||||
// Базовые соглашения (всегда включаются)
|
||||
const baseAgreements = REGISTRATION_AGREEMENTS_CONFIG.agreements
|
||||
.filter(a => a.applicable_account_types.includes(accountType))
|
||||
.filter(a => a.id !== 'blagorost_offer' && a.id !== 'generator_offer');
|
||||
|
||||
// Если указана программа - добавляем её соглашения
|
||||
if (programKey && coopname) {
|
||||
const programAgreements = this.getAgreementsForProgram(programKey, coopname);
|
||||
return [...baseAgreements, ...programAgreements].sort((a, b) => a.order - b.order);
|
||||
}
|
||||
|
||||
// Старая логика для обратной совместимости (voskhod без program_key)
|
||||
if (coopname === 'voskhod' && accountType === AccountType.individual) {
|
||||
const capitalization = this.getAgreementById('blagorost_offer');
|
||||
if (capitalization) {
|
||||
return [...baseAgreements, capitalization].sort((a, b) => a.order - b.order);
|
||||
}
|
||||
}
|
||||
|
||||
return baseAgreements.sort((a, b) => a.order - b.order);
|
||||
}
|
||||
```
|
||||
|
||||
### 1.5 Обновление RegistrationDocumentsService
|
||||
|
||||
**Файл:** [`components/controller/src/domain/registration/services/registration-documents.service.ts`](components/controller/src/domain/registration/services/registration-documents.service.ts)
|
||||
|
||||
Добавить `program_key` в интерфейс:
|
||||
|
||||
```typescript
|
||||
// В интерфейсе IGenerateRegistrationDocumentsInput
|
||||
program_key?: string;
|
||||
```
|
||||
|
||||
Обновить метод `generateRegistrationDocuments`:
|
||||
|
||||
```typescript
|
||||
const agreementsConfig = this.agreementConfigService.getAgreementsForAccountType(
|
||||
account_type,
|
||||
coopname,
|
||||
input.program_key
|
||||
);
|
||||
```
|
||||
|
||||
### 1.6 Обновление DTO
|
||||
|
||||
**Файл:** [`components/controller/src/application/user/dto/generate-registration-documents-input.dto.ts`](components/controller/src/application/user/dto/generate-registration-documents-input.dto.ts)
|
||||
|
||||
```typescript
|
||||
@Field({ nullable: true, description: 'Ключ выбранной программы регистрации (опционально)' })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
program_key?: string;
|
||||
```
|
||||
|
||||
### 1.7 Новый Query для получения конфигурации
|
||||
|
||||
**Файл:** [`components/controller/src/application/system/resolvers/system.resolver.ts`](components/controller/src/application/system/resolvers/system.resolver.ts)
|
||||
|
||||
Добавить новый query:
|
||||
|
||||
```typescript
|
||||
@Query(() => RegistrationConfigDTO, {
|
||||
name: 'getRegistrationConfig',
|
||||
description: 'Получить конфигурацию программ регистрации для кооператива',
|
||||
})
|
||||
async getRegistrationConfig(
|
||||
@Args('coopname') coopname: string,
|
||||
@Args('account_type', { type: () => AccountType }) accountType: AccountType,
|
||||
): Promise<RegistrationConfigDTO> {
|
||||
return this.systemService.getRegistrationConfig(coopname, accountType);
|
||||
}
|
||||
```
|
||||
|
||||
**Файл:** `components/controller/src/application/system/dto/registration-config.dto.ts` (новый)
|
||||
|
||||
```typescript
|
||||
@ObjectType('RegistrationProgram')
|
||||
export class RegistrationProgramDTO {
|
||||
@Field() key!: string;
|
||||
@Field() title!: string;
|
||||
@Field() description!: string;
|
||||
@Field({ nullable: true }) image_url?: string;
|
||||
@Field({ nullable: true }) requirements?: string;
|
||||
@Field(() => [AccountType]) applicable_account_types!: AccountType[];
|
||||
@Field(() => Int) order!: number;
|
||||
}
|
||||
|
||||
@ObjectType('RegistrationConfig')
|
||||
export class RegistrationConfigDTO {
|
||||
@Field() requires_selection!: boolean;
|
||||
@Field(() => [RegistrationProgramDTO]) programs!: RegistrationProgramDTO[];
|
||||
}
|
||||
```
|
||||
|
||||
**Файл:** [`components/controller/src/application/system/services/system.service.ts`](components/controller/src/application/system/services/system.service.ts)
|
||||
|
||||
```typescript
|
||||
async getRegistrationConfig(
|
||||
coopname: string,
|
||||
accountType: AccountType
|
||||
): Promise<RegistrationConfigDTO> {
|
||||
const config = this.agreementConfigService.getCooperativeProgramsConfig(coopname);
|
||||
|
||||
if (!config) {
|
||||
return new RegistrationConfigDTO({
|
||||
requires_selection: false,
|
||||
programs: [],
|
||||
});
|
||||
}
|
||||
|
||||
const programs = this.agreementConfigService.getAvailablePrograms(coopname, accountType);
|
||||
|
||||
return new RegistrationConfigDTO({
|
||||
requires_selection: config.requires_selection && programs.length > 1,
|
||||
programs,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## 2. Frontend: Новый шаг выбора программы
|
||||
|
||||
### 2.1 Обновление Registrator Store
|
||||
|
||||
**Файл:** [`components/desktop/src/entities/Registrator/model/store.ts`](components/desktop/src/entities/Registrator/model/store.ts)
|
||||
|
||||
Добавить в state:
|
||||
|
||||
```typescript
|
||||
const state = reactive({
|
||||
// ... существующие поля
|
||||
selectedProgramKey: '', // выбранный ключ программы
|
||||
});
|
||||
```
|
||||
|
||||
Добавить шаг `SelectProgram` в массив stepNames (между `SetUserData` и `GenerateAccount`).
|
||||
|
||||
### 2.2 Создание компонента SelectProgram
|
||||
|
||||
**Файл:** `components/desktop/src/pages/Registrator/SignUp/SelectProgram.vue` (новый)
|
||||
|
||||
```vue
|
||||
<template lang="pug">
|
||||
div
|
||||
q-step(
|
||||
:name='registratorStore.steps.SelectProgram',
|
||||
title='Выберите программу участия',
|
||||
:done='registratorStore.isStepDone("SelectProgram")'
|
||||
)
|
||||
div(v-if='isLoading').full-width.text-center.q-mt-lg
|
||||
Loader(text='Загружаем доступные программы...')
|
||||
|
||||
div(v-else-if='programs.length > 0')
|
||||
p.text-body1 Выберите программу, в которой вы хотите участвовать:
|
||||
|
||||
q-list.q-mt-md
|
||||
q-item(
|
||||
v-for='program in programs',
|
||||
:key='program.key',
|
||||
clickable,
|
||||
v-ripple,
|
||||
:active='registratorStore.state.selectedProgramKey === program.key',
|
||||
@click='selectProgram(program.key)'
|
||||
)
|
||||
q-item-section
|
||||
q-item-label.text-h6 {{ program.title }}
|
||||
q-item-label(caption).q-mt-sm {{ program.description }}
|
||||
q-item-label(v-if='program.requirements', caption).q-mt-xs.text-weight-bold
|
||||
| {{ program.requirements }}
|
||||
q-item-section(side)
|
||||
q-radio(
|
||||
:model-value='registratorStore.state.selectedProgramKey',
|
||||
:val='program.key',
|
||||
@update:model-value='selectProgram(program.key)'
|
||||
)
|
||||
|
||||
div.q-mt-lg
|
||||
q-btn.col-md-6.col-xs-12(flat @click='registratorStore.prev()')
|
||||
i.fa.fa-arrow-left
|
||||
span.q-ml-md назад
|
||||
|
||||
q-btn.q-mt-lg.q-mb-lg(
|
||||
color='primary',
|
||||
label='Продолжить',
|
||||
:disabled='!registratorStore.state.selectedProgramKey',
|
||||
@click='registratorStore.next()'
|
||||
)
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useRegistratorStore } from 'src/entities/Registrator';
|
||||
import { useSystemStore } from 'src/entities/System/model';
|
||||
import { Loader } from 'src/shared/ui/Loader';
|
||||
import { FailAlert } from 'src/shared/api';
|
||||
// TODO: добавить API метод для получения конфигурации
|
||||
|
||||
const registratorStore = useRegistratorStore();
|
||||
const systemStore = useSystemStore();
|
||||
|
||||
const isLoading = ref(false);
|
||||
const programs = ref<any[]>([]);
|
||||
|
||||
const loadPrograms = async () => {
|
||||
try {
|
||||
isLoading.value = true;
|
||||
// TODO: вызов API getRegistrationConfig
|
||||
const config = await getRegistrationConfig(
|
||||
systemStore.info.coopname,
|
||||
registratorStore.state.userData.type
|
||||
);
|
||||
programs.value = config.programs;
|
||||
} catch (e: any) {
|
||||
FailAlert(e);
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const selectProgram = (key: string) => {
|
||||
registratorStore.state.selectedProgramKey = key;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
if (registratorStore.state.step === registratorStore.steps.SelectProgram) {
|
||||
loadPrograms();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
### 2.3 Условный рендеринг шага
|
||||
|
||||
**Файл:** [`components/desktop/src/pages/Registrator/SignUp/SignUp.vue`](components/desktop/src/pages/Registrator/SignUp/SignUp.vue)
|
||||
|
||||
Добавить условный рендеринг:
|
||||
|
||||
```pug
|
||||
SelectProgram(v-if='shouldShowProgramSelection')
|
||||
```
|
||||
|
||||
Добавить computed для определения необходимости показа:
|
||||
|
||||
```typescript
|
||||
const shouldShowProgramSelection = computed(() => {
|
||||
// Показываем только если есть конфигурация программ для данного кооператива
|
||||
// и тип аккаунта выбран
|
||||
return (
|
||||
registratorStore.state.userData.type &&
|
||||
// TODO: проверка наличия программ из конфигурации
|
||||
);
|
||||
});
|
||||
```
|
||||
|
||||
### 2.4 Обновление вызова generateRegistrationDocuments
|
||||
|
||||
**Файл:** `components/desktop/src/features/User/CreateUser` (или где происходит вызов)
|
||||
|
||||
Передавать `program_key` при генерации документов:
|
||||
|
||||
```typescript
|
||||
await generateAllRegistrationDocuments(
|
||||
coopname,
|
||||
username,
|
||||
accountType,
|
||||
registratorStore.state.selectedProgramKey // новый параметр
|
||||
);
|
||||
```
|
||||
|
||||
## 3. SDK: Обновление мутации
|
||||
|
||||
**Файл:** [`components/sdk/src/mutations/registration/generateRegistrationDocuments.ts`](components/sdk/src/mutations/registration/generateRegistrationDocuments.ts) (если существует, иначе найти где определена)
|
||||
|
||||
Добавить опциональное поле `program_key` в input мутации.
|
||||
|
||||
**Файл:** Где определен query `getRegistrationConfig` (создать новый)
|
||||
|
||||
```typescript
|
||||
export const getRegistrationConfig = {
|
||||
name: 'getRegistrationConfig',
|
||||
query: (coopname: string, account_type: string) => ({
|
||||
getRegistrationConfig: [
|
||||
{ coopname, account_type },
|
||||
{
|
||||
requires_selection: true,
|
||||
programs: {
|
||||
key: true,
|
||||
title: true,
|
||||
description: true,
|
||||
image_url: true,
|
||||
requirements: true,
|
||||
order: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
};
|
||||
```
|
||||
|
||||
## 4. Обновление логики перехода между шагами
|
||||
|
||||
В [`components/desktop/src/pages/Registrator/SignUp/SetUserData.vue`](components/desktop/src/pages/Registrator/SignUp/SetUserData.vue) при клике "Продолжить" нужно:
|
||||
|
||||
1. Проверить, нужен ли шаг SelectProgram
|
||||
2. Если да - перейти на него
|
||||
3. Если нет - перейти на GenerateAccount (как сейчас)
|
||||
|
||||
## Итоговая последовательность шагов регистрации
|
||||
|
||||
1. **EmailInput** - ввод email
|
||||
2. **SetUserData** - выбор типа аккаунта и заполнение данных
|
||||
3. **SelectProgram** *(условный)* - выбор программы (только для voskhod, физики/ИП)
|
||||
4. **GenerateAccount** - генерация аккаунта
|
||||
5. **SelectBranch** *(условный)* - выбор филиала
|
||||
6. **ReadStatement** - чтение и принятие соглашений (уже учитывают program_key)
|
||||
7. **SignStatement** - подпись
|
||||
8. **PayInitial** - оплата
|
||||
9. **WaitingRegistration** - ожидание
|
||||
|
||||
## Миграция и обратная совместимость
|
||||
|
||||
- Для всех кооперативов кроме voskhod ничего не меняется
|
||||
- Для voskhod: если не передан `program_key`, используется старая логика (blagorost_offer для физиков)
|
||||
- После развертывания можно убрать хардкод для voskhod из `AgreementConfigurationService`
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
description: Общие правила
|
||||
alwaysApply: true
|
||||
---
|
||||
Мы работаем в МОНО-репозитории pnpm, любая установка пакетов производится ЧЕРЕЗ ФИЛЬТР компонента в корне: pnpm add glob --filter <package>.
|
||||
|
||||
НИКОГДА НЕ ЗАПУСКАЙ ПРИЛОЖЕНИЕ ИЛИ ЕГО СБОРКУ!!! Просто отчитайся что всё сделал.
|
||||
|
||||
НИКОГДА НЕ ЗАПУСКАЙ ТЕСТЫ ИЛИ ПРОВЕРКУ ТИПОВ ИЛИ ЛИНТЕР!!!
|
||||
|
||||
Никаких ANY типов! Строгая типизация всегда.
|
||||
@@ -0,0 +1,247 @@
|
||||
---
|
||||
description: Правила работы с пагинацией в контроллере
|
||||
alwaysApply: false
|
||||
---
|
||||
# Правила работы с пагинацией в Controller
|
||||
|
||||
## Общие принципы
|
||||
|
||||
**Всегда используйте доменные интерфейсы пагинации** вместо примитивных типов (`skip`, `take`, `limit`, `offset`).
|
||||
Расчет общего количества записей **всегда** производится через `count()` в репозитории.
|
||||
|
||||
## Архитектурные слои и интерфейсы
|
||||
|
||||
### 1. DTO слой (GraphQL)
|
||||
```typescript
|
||||
// Входные параметры от клиента
|
||||
PaginationInputDTO {
|
||||
page: number; // Номер страницы (начиная с 1)
|
||||
limit: number; // Количество элементов на странице
|
||||
sortBy?: string; // Поле сортировки
|
||||
sortOrder: 'ASC' | 'DESC'; // Направление сортировки
|
||||
}
|
||||
|
||||
// Результат для клиента
|
||||
PaginationResult<T> {
|
||||
items: T[]; // Элементы текущей страницы
|
||||
totalCount: number; // Общее количество элементов
|
||||
totalPages: number; // Общее количество страниц
|
||||
currentPage: number; // Текущая страница
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Домен слой
|
||||
```typescript
|
||||
// Входные параметры для домена
|
||||
PaginationInputDomainInterface {
|
||||
page: number;
|
||||
limit: number;
|
||||
sortBy?: string;
|
||||
sortOrder: 'ASC' | 'DESC';
|
||||
}
|
||||
|
||||
// Результат из домена
|
||||
PaginationResultDomainInterface<T> {
|
||||
items: T[];
|
||||
totalCount: number;
|
||||
totalPages: number;
|
||||
currentPage: number;
|
||||
}
|
||||
```
|
||||
|
||||
## Поток данных
|
||||
|
||||
### В Resolver (GraphQL слой)
|
||||
```typescript
|
||||
// Создание GraphQL типа для пагинированного результата
|
||||
const paginatedEntitiesResult = createPaginationResult(EntityDTO, 'PaginatedEntities');
|
||||
|
||||
@Query(() => paginatedEntitiesResult)
|
||||
async getEntities(
|
||||
@Args('filter') filter?: FilterDTO,
|
||||
@Args('options') options?: PaginationInputDTO
|
||||
): Promise<PaginationResult<EntityDTO>> {
|
||||
|
||||
// Преобразование в доменный интерфейс
|
||||
const domainOptions: PaginationInputDomainInterface | undefined = options
|
||||
? {
|
||||
page: options.page,
|
||||
limit: options.limit,
|
||||
sortBy: options.sortBy,
|
||||
sortOrder: options.sortOrder,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
// Вызов сервиса
|
||||
const result = await this.service.getEntities(filter, domainOptions);
|
||||
|
||||
// DTO готовится в сервисе, резолвер проксирует результат
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
### В Service (Прикладной слой)
|
||||
```typescript
|
||||
async getEntities(
|
||||
filter?: FilterDTO,
|
||||
options?: PaginationInputDomainInterface
|
||||
): Promise<PaginationResult<EntityDTO>> {
|
||||
|
||||
// Получение результата из домена
|
||||
const domainResult = await this.interactor.getEntities(filter, options);
|
||||
|
||||
// Преобразование доменных сущностей в DTO
|
||||
const items = domainResult.items.map(item => this.mapToDTO(item));
|
||||
|
||||
// Возврат полного пагинированного результата с DTO
|
||||
return {
|
||||
items,
|
||||
totalCount: domainResult.totalCount,
|
||||
totalPages: domainResult.totalPages,
|
||||
currentPage: domainResult.currentPage,
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### В Interactor/UseCase (Домен слой)
|
||||
```typescript
|
||||
async getEntities(
|
||||
filter?: DomainFilter,
|
||||
options?: PaginationInputDomainInterface
|
||||
): Promise<PaginationResultDomainInterface<DomainEntity>> {
|
||||
|
||||
// Вызов репозитория
|
||||
return await this.repository.findAllPaginated(filter, options);
|
||||
}
|
||||
```
|
||||
|
||||
### В Repository (Инфраструктурный слой)
|
||||
```typescript
|
||||
async findAllPaginated(
|
||||
filter?: Filter,
|
||||
options?: PaginationInputDomainInterface
|
||||
): Promise<PaginationResultDomainInterface<Entity>> {
|
||||
|
||||
// 1. Валидация параметров
|
||||
const validatedOptions = options
|
||||
? PaginationUtils.validatePaginationOptions(options)
|
||||
: { page: 1, limit: 10, sortOrder: 'ASC' };
|
||||
|
||||
// 2. Получение SQL параметров
|
||||
const { limit, offset } = PaginationUtils.getSqlPaginationParams(validatedOptions);
|
||||
|
||||
// 3. Подсчет общего количества (ОБЯЗАТЕЛЬНО!)
|
||||
const totalCount = await this.repository.count({ where });
|
||||
|
||||
// 4. Получение данных с пагинацией
|
||||
const entities = await this.repository.find({
|
||||
where,
|
||||
skip: offset,
|
||||
take: limit,
|
||||
order: orderBy,
|
||||
});
|
||||
|
||||
// 5. Преобразование в доменные сущности
|
||||
const items = entities.map(entity => this.mapper.toDomain(entity));
|
||||
|
||||
// 6. Создание результата через утилиту
|
||||
return PaginationUtils.createPaginationResult(items, totalCount, validatedOptions);
|
||||
}
|
||||
```
|
||||
|
||||
## GraphQL типы пагинации
|
||||
|
||||
Для создания GraphQL типов пагинированных результатов используйте `createPaginationResult`:
|
||||
|
||||
```typescript
|
||||
import { createPaginationResult } from '~/application/common/dto/pagination.dto';
|
||||
|
||||
// Создание GraphQL типа для пагинированного результата
|
||||
const paginatedEntitiesResult = createPaginationResult(EntityDTO, 'PaginatedEntities');
|
||||
|
||||
// Использование в резолвере
|
||||
@Query(() => paginatedEntitiesResult)
|
||||
async getEntities(...) : Promise<PaginationResult<EntityDTO>>
|
||||
```
|
||||
|
||||
Функция `createPaginationResult` создает GraphQL DTO тип для пагинированного ответа, но не готовит сами данные.
|
||||
|
||||
## Утилиты пагинации
|
||||
|
||||
Используйте `PaginationUtils` для стандартных операций:
|
||||
|
||||
```typescript
|
||||
import { PaginationUtils } from '~/shared/utils/pagination.utils';
|
||||
|
||||
// Валидация параметров
|
||||
const validatedOptions = PaginationUtils.validatePaginationOptions(options);
|
||||
|
||||
// Получение SQL параметров (limit, offset)
|
||||
const { limit, offset } = PaginationUtils.getSqlPaginationParams(options);
|
||||
|
||||
// Создание результата пагинации
|
||||
const result = PaginationUtils.createPaginationResult(items, totalCount, options);
|
||||
```
|
||||
|
||||
## Валидация параметров
|
||||
|
||||
- `page >= 1` - номер страницы должен быть положительным
|
||||
- `1 <= limit <= 1000` - ограничение на количество элементов
|
||||
- `sortOrder` должен быть `'ASC'` или `'DESC'`
|
||||
|
||||
## Запрещенные паттерны
|
||||
|
||||
❌ **НЕ ПРАВИЛЬНО:**
|
||||
```typescript
|
||||
// Не используйте примитивные типы
|
||||
async getApprovals(filter?: Filter, skip = 0, take = 50)
|
||||
|
||||
// Не рассчитывайте пагинацию вручную
|
||||
const totalPages = Math.ceil(totalCount / limit); // Делайте это в утилите
|
||||
|
||||
// Не возвращайте сырые данные без пагинации
|
||||
return await this.repository.findAll(); // Всегда используйте пагинацию
|
||||
```
|
||||
|
||||
✅ **ПРАВИЛЬНО:**
|
||||
```typescript
|
||||
// Сервис возвращает полный пагинированный результат с DTO
|
||||
async getApprovals(
|
||||
filter?: ApprovalFilterInput,
|
||||
options?: PaginationInputDomainInterface
|
||||
): Promise<PaginationResult<ApprovalDTO>> {
|
||||
// Получение доменного результата
|
||||
const domainResult = await this.repository.findAllPaginated(filter, options);
|
||||
|
||||
// Преобразование в DTO
|
||||
const items = domainResult.items.map(item => this.toDTO(item));
|
||||
|
||||
// Возврат полного результата
|
||||
return {
|
||||
items,
|
||||
totalCount: domainResult.totalCount,
|
||||
totalPages: domainResult.totalPages,
|
||||
currentPage: domainResult.currentPage,
|
||||
};
|
||||
}
|
||||
|
||||
// Резолвер проксирует результат от сервиса
|
||||
@Query(() => paginatedApprovalsResult)
|
||||
async getApprovals(filter, options): Promise<PaginationResult<ApprovalDTO>> {
|
||||
return await this.service.getApprovals(filter, domainOptions);
|
||||
}
|
||||
```
|
||||
|
||||
## Ключевые моменты
|
||||
|
||||
1. **DTO готовится в Service** - сервис получает доменные сущности и преобразует их в DTO перед возвратом
|
||||
2. **GraphQL типы создаются через createPaginationResult** - это создает схему GraphQL, а не данные
|
||||
3. **Резолвер проксирует результат** - резолвер просто вызывает сервис и возвращает результат
|
||||
4. **Полный пагинированный результат** - всегда возвращается PaginationResult<T> с items, totalCount, totalPages, currentPage
|
||||
|
||||
## Примеры из кода
|
||||
|
||||
- ✅ `ApprovalService` - правильная реализация с полным PaginationResult
|
||||
- ✅ `InvestsManagementService` - прямое преобразование Domain → DTO в сервисе
|
||||
- ✅ `TimeTrackingService` - явное преобразование полей в DTO
|
||||
- ✅ `InvestTypeormRepository` - полная реализация с валидацией через PaginationUtils
|
||||
@@ -0,0 +1,53 @@
|
||||
---
|
||||
description: О правилах на бэкенде
|
||||
globs: **/controller/**/*.ts
|
||||
---
|
||||
# NestJS Controller и Чистая архитектура
|
||||
|
||||
## Основные принципы
|
||||
1. Чистая архитектура - главный архитектурный подход. Пиши код с комментариями.
|
||||
2. Типы IName, IChecksum256, ITimePointSec - это просто строки.
|
||||
3. Домен должен быть полностью изолирован от инфраструктурных деталей.
|
||||
4. Направление зависимостей - внутрь (к ядру, домену).
|
||||
5. Домен, инфраструктура и приложение связаны через App.module, НЕ НУЖНО импортировать их друг в друга, а достаточно просто импортировать домен на уровне приложения чтобы использовать все экспорты домена.
|
||||
|
||||
## Структура проекта
|
||||
- `domain/` - доменный слой (бизнес-логика, независимая от инфраструктуры)
|
||||
- `infrastructure/` - инфраструктурный слой (адаптеры к внешним системам)
|
||||
- `modules/` - слой приложения (DTO, резолверы, сервисы)
|
||||
|
||||
## Слои и их взаимодействие
|
||||
1. **Домен**:
|
||||
- Содержит чистые доменные интерфейсы и реализованные на них сущности
|
||||
- НЕ должен зависеть от `cooptypes` (инфраструктурный контракт)
|
||||
- Использует собственные доменные типы (например, Date вместо строковых timestamp)
|
||||
- Интерфейсы портов описывают взаимодействие с внешними системами
|
||||
|
||||
2. **Инфраструктура**:
|
||||
- Содержит адаптеры к внешним системам (блокчейн, БД и т.д.)
|
||||
- Осуществляет преобразование между доменными и инфраструктурными типами
|
||||
- Общая логика преобразования выносится в утилитарные классы (например, `DomainToBlockchainUtils`)
|
||||
|
||||
3. **Модули (приложение)**:
|
||||
- DTO имплементируют доменные интерфейсы напрямую
|
||||
- Резолвер вызывает сервис, который принимает DTO
|
||||
- Сервис передает объекты DTO в интерактор приложения (юз-кейс)
|
||||
|
||||
## Правила
|
||||
|
||||
Чистая фрактальная трехуровневая архитектура срезов домена (domain) и инфраструктуры (infrastructure), оркестрируемая приложением (application). Фрактал раскрывается в расширениях (extensions), каждое из которых также выстраивается по правилам чистой трехуровневой архитектуры срезов домена и инфраструктуры с окрестрацией на уровне своего приложения.
|
||||
|
||||
Общая архитектура связей:
|
||||
- AppResolver -> AppService[DTO<->Domain] -> AppInteractor -> DomainService -> DomainPort <- InfraAdapter -> [при необходимости] AnyAppOrDomainService
|
||||
|
||||
Все сервисы внедряются через инъекцию (DI) с указанием символа реализации. Импорты разрешены только вглубь - к домену. Из домена переход разрешен только в инфраструктуру через порт и его адаптер.
|
||||
|
||||
Прямые импорты без DI запрещены. Импорты из соседних срезов того же архитектурного уровня запрещены. Направление зависимостей от приложения - к домену, от домена через порт в его инфраструктурный адаптер.
|
||||
|
||||
Взаимодействие расширений с основным приложением осуществляется только через порты домена основного приложения.
|
||||
|
||||
Глобальные модули запрещены. В редких случаях сложных циклических зависимостей на период рефакторинга разрешается обоснованно использовать forwardRef.
|
||||
|
||||
Уровень приложения оркестрирует вызовы доменов через порты в инфраструктуру. Домены не обращаются друг к друга. Срезы уровня приложений не обращаются друг к другу, а только вызывают порты домена.
|
||||
|
||||
```
|
||||
@@ -0,0 +1,174 @@
|
||||
---
|
||||
description: SDK мутации и запросы для подключения на рабочем столе
|
||||
globs:
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
Бэкенд реализован здесь components/controller, sdk здесь components/sdk.
|
||||
|
||||
Пример создания Query к API через SDK:
|
||||
|
||||
``` ts
|
||||
async function loadBranches(data: IGetBranchesInput): Promise<IBranch[]> {
|
||||
const { [Queries.Branches.GetBranches.name]: output } = await client.Query(
|
||||
Queries.Branches.GetBranches.query,
|
||||
{
|
||||
variables: {
|
||||
data
|
||||
}
|
||||
}
|
||||
);
|
||||
return output;
|
||||
}
|
||||
```
|
||||
|
||||
Такие запросы обычно хранятся в папке Entities и вызываются из его store.
|
||||
|
||||
Пример мутации:
|
||||
|
||||
``` ts
|
||||
async function selectBranch(data: ISelectBranchInput): Promise<boolean>{
|
||||
const {[Mutations.Branches.SelectBranch.name]: result} = await client.Mutation(Mutations.Branches.SelectBranch.mutation, {variables: {
|
||||
data
|
||||
}})
|
||||
|
||||
return result
|
||||
}
|
||||
```
|
||||
|
||||
Мутации обычно храним в папке features.
|
||||
|
||||
Как можешь обратить внимание, все мутации и запросы строятся по одному шаблону. Вся необходимая информация уже есть в SDK.
|
||||
|
||||
## Генерация и подпись документов
|
||||
|
||||
### Генерация документов
|
||||
|
||||
Для генерации документов используются мутации SDK типа `Generate[DocumentName]`. Например:
|
||||
|
||||
```ts
|
||||
import { client } from 'src/shared/api/client';
|
||||
import { Mutations } from '@coopenomics/sdk';
|
||||
|
||||
async function generateDocument(data: IGenerateDocumentInput): Promise<IGeneratedDocumentOutput> {
|
||||
const { [Mutations.Capital.GenerateGenerationContract.name]: result } =
|
||||
await client.Mutation(Mutations.Capital.GenerateGenerationContract.mutation, {
|
||||
variables: {
|
||||
data: {
|
||||
coopname: 'coopname',
|
||||
username: 'username',
|
||||
lang: 'ru'
|
||||
},
|
||||
options
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
**Composable для генерации документов:**
|
||||
```ts
|
||||
import { ref } from 'vue';
|
||||
import { api } from '../api';
|
||||
import { useSystemStore } from 'src/entities/System/model';
|
||||
import { useSessionStore } from 'src/entities/Session';
|
||||
|
||||
export function useGenerateDocument() {
|
||||
const system = useSystemStore();
|
||||
const session = useSessionStore();
|
||||
|
||||
const isGenerating = ref(false);
|
||||
const generatedDocument = ref<IGeneratedDocumentOutput | null>(null);
|
||||
|
||||
const generate = async () => {
|
||||
isGenerating.value = true;
|
||||
try {
|
||||
const data = {
|
||||
coopname: system.info.coopname,
|
||||
username: session.username,
|
||||
lang: 'ru',
|
||||
};
|
||||
|
||||
generatedDocument.value = await api.generateDocument(data);
|
||||
return generatedDocument.value;
|
||||
} finally {
|
||||
isGenerating.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
generate,
|
||||
isGenerating,
|
||||
generatedDocument,
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Подпись документов
|
||||
|
||||
Для подписи документов используется класс `DigitalDocument`:
|
||||
|
||||
```ts
|
||||
import { DigitalDocument } from 'src/shared/lib/document';
|
||||
import { useSessionStore } from 'src/entities/Session';
|
||||
|
||||
const session = useSessionStore();
|
||||
const digitalDocument = new DigitalDocument(generatedDocument);
|
||||
|
||||
// Подпись документа
|
||||
const signedDoc = await digitalDocument.sign(session.username);
|
||||
```
|
||||
|
||||
### Стандартный путь: генерация + подпись
|
||||
|
||||
**Стандартный путь (без демонстрации):**
|
||||
```ts
|
||||
// 1. Генерируем документ
|
||||
const document = await generateDocument({
|
||||
coopname: system.info.coopname,
|
||||
username: session.username,
|
||||
lang: 'ru'
|
||||
});
|
||||
|
||||
// 2. Подписываем документ
|
||||
const digitalDocument = new DigitalDocument(document);
|
||||
const signedDoc = await digitalDocument.sign(session.username);
|
||||
|
||||
// 3. Отправляем подписанный документ на бэкенд
|
||||
await submitDocumentMutation(signedDoc);
|
||||
```
|
||||
|
||||
### Демонстрация документов (опционально)
|
||||
|
||||
Демонстрация документов не всегда требуется. Если нужно показать документ пользователю перед подписью:
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<DocumentHtmlReader v-if="generatedDocument" :html="generatedDocument.html" />
|
||||
<q-btn @click="signAndSubmit" label="Подписать документ" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const { generate, isGenerating, generatedDocument } = useGenerateDocument();
|
||||
|
||||
// Генерация при монтировании
|
||||
onMounted(async () => {
|
||||
await generate();
|
||||
});
|
||||
|
||||
// Подпись и отправка
|
||||
const signAndSubmit = async () => {
|
||||
const digitalDocument = new DigitalDocument(generatedDocument.value);
|
||||
const signedDoc = await digitalDocument.sign(session.username);
|
||||
await submitDocumentMutation(signedDoc);
|
||||
};
|
||||
</script>
|
||||
```
|
||||
|
||||
**Важно:**
|
||||
- Демонстрация документов опциональна
|
||||
- Стандартный путь: генерация → подпись → отправка
|
||||
- Используй `DocumentHtmlReader` для отображения HTML документов
|
||||
- Все документы подписываются через `DigitalDocument.sign()`
|
||||
- Данные для генерации обычно включают `coopname`, `username`, `lang`
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
description: О правилах на фронтенде
|
||||
globs: **/desktop/**
|
||||
alwaysApply: false
|
||||
---
|
||||
# DESKTOP
|
||||
|
||||
- Пишем код фронтенда в архитектуре Feature Sliced Design (FSD).
|
||||
- Стиль шаблонов PUG на composite API.
|
||||
- Всегда создаём index.ts файлы для vue компонент.
|
||||
- Создавай индексные файлы для vue через export {default as NAME} from './where', а на уровне выше, если это необходимо, делай export * from './ui'
|
||||
- Всегда удаляем неиспользуемые импорты.
|
||||
- Не используем emit избыточно. Вместо них используем композабл функции моделей фич, если необходимо. Emit только в крайнем случае.
|
||||
- Архитектура приложения состоит из папки src, которая формирует core приложение по FSD, и папки extensions, в каждой папке которого есть подобная FSD структура. Т.е. в src есть features, widgets, entities, shared для core приложения, и в extensions/<name>/ - тоже есть features, widgets, entities, shared для конкретного расширения. По-возможности делаем их изолированными, т.е. импорты между src и extensions ЗАПРЕЩЕНЫ. Исключением являются FailAlert и SuccessAlert, которые допустимо импортировать.
|
||||
- Не создаём не используемые переменные!
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
---
|
||||
description: Правила работы с макросом main.py в компоненте docs
|
||||
alwaysApply: false
|
||||
---
|
||||
Файл [main.py](mdc:monocoop/monocoop/monocoop/components/docs/main.py) автоматически генерит ссылки на документацию SDK и GraphQL, которые формируются и публикуются автоматически. При создании документации к методам всегда применяй ссылки на SDK и GraphQL по форме:
|
||||
{{ get_sdk_doc("Mutations", "Accounts", "RegisterAccount") }} | {{ get_graphql_doc("Mutation.registerAccount") }}
|
||||
|
||||
### 🎯 Основные паттерны использования
|
||||
|
||||
**1. Стандартная структура для методов API:**
|
||||
```markdown
|
||||
## Название действия
|
||||
{{ get_sdk_doc("Namespace", "Module", "Method") }} | {{ get_graphql_doc("Type.methodName") }}
|
||||
|
||||
{{ get_typedoc_input("Namespace.Module.Method") }}
|
||||
|
||||
Результат:
|
||||
{{ get_typedoc_definition("Namespace.Module.Method", "IOutput") }}
|
||||
```
|
||||
|
||||
**2. Описание + пример (для сложных методов):**
|
||||
```markdown
|
||||
{{ get_typedoc_desc("Namespace.Module.Method") }}
|
||||
|
||||
{{ get_typedoc_input("Namespace.Module.Method") }}
|
||||
```
|
||||
|
||||
**3. Ссылки на типы данных:**
|
||||
```markdown
|
||||
У каждого аккаунта есть объект {{ get_graphql_definition("Account") }} в GraphQL-API
|
||||
```
|
||||
|
||||
### 🔧 Макросы и их применение
|
||||
|
||||
| Макрос | Назначение | Пример использования |
|
||||
|--------|------------|---------------------|
|
||||
| `get_sdk_doc` | Ссылка на SDK документацию | `{{ get_sdk_doc("Mutations", "Payments", "CreateDeposit") }}` |
|
||||
| `get_graphql_doc` | Ссылка на GraphQL операцию | `{{ get_graphql_doc("Query.getAccount") }}` |
|
||||
| `get_graphql_definition` | Ссылка на GraphQL тип | `{{ get_graphql_definition("Account") }}` |
|
||||
| `get_class_doc` | Ссылка на класс/метод SDK | `{{ get_class_doc("Document", "sign") }}` |
|
||||
| `get_typedoc_input` | **Полный пример** TypeScript вызова | `{{ get_typedoc_input("Mutations.Auth.Login") }}` |
|
||||
| `get_typedoc_definition` | **Структура интерфейса** | `{{ get_typedoc_definition("Mutations.Auth.Login", "IOutput") }}` |
|
||||
| `get_typedoc_desc` | Описание + примеры | `{{ get_typedoc_desc("Mutations.Auth.Login") }}` |
|
||||
| `get_typedoc_value` | Значение константы | `{{ get_typedoc_value("Constants.API_VERSION") }}` |
|
||||
|
||||
### 📝 Правила оформления
|
||||
|
||||
**ОБЯЗАТЕЛЬНО:**
|
||||
- Всегда используй `get_typedoc_input` для демонстрации **КАК вызывать** метод
|
||||
- Всегда используй `get_typedoc_definition` для показа **ЧТО возвращается**
|
||||
- Комбинируй SDK и GraphQL ссылки через ` | `: `{{ get_sdk_doc(...) }} | {{ get_graphql_doc(...) }}`
|
||||
|
||||
**ЖЕЛАТЕЛЬНО:**
|
||||
- Для сложных методов добавляй `get_typedoc_desc` в начало
|
||||
- Используй `get_graphql_definition` для ссылок на типы данных в тексте
|
||||
- Группируй связанные операции в одном разделе
|
||||
|
||||
**ФОРМАТ ССЫЛОК:**
|
||||
- SDK: `"Namespace", "Module", "Method"` (3 аргумента)
|
||||
- GraphQL: `"Type.methodName"` (точка между типом и методом)
|
||||
- Определения: просто имя типа `"TypeName"`
|
||||
@@ -0,0 +1,30 @@
|
||||
---
|
||||
description: Правила формирования агрегатов документов в controller
|
||||
alwaysApply: false
|
||||
---
|
||||
## 📋 Правила формирования агрегатов документов
|
||||
|
||||
### 📝 **В сервисах**
|
||||
1. **Инжект DocumentAggregationService** в конструктор сервиса
|
||||
2. **Создай async метод toDTO** для преобразования доменных сущностей в DTO
|
||||
3. **Вызывай buildDocumentAggregate()** для поля документа:
|
||||
```typescript
|
||||
const document = await this.documentAggregationService.buildDocumentAggregate(entity.documentField);
|
||||
```
|
||||
4. **Добавь поле document в возвращаемый объект**
|
||||
|
||||
### 🎯 **В DTO**
|
||||
1. **Импорт DocumentAggregateDTO**
|
||||
2. **Добавь поле document** с типом `DocumentAggregateDTO | null`
|
||||
3. **nullable: true** в GraphQL декораторе
|
||||
|
||||
### 🔄 **В резолверах**
|
||||
- **Ничего не меняй** - резолверы автоматически получают DTO с полем document
|
||||
|
||||
### ⚡ **Чек-лист**
|
||||
- ✅ DocumentAggregationService инжектирован
|
||||
- ✅ Метод toDTO стал async
|
||||
- ✅ Все вызовы toDTO стали await
|
||||
- ✅ Поле document добавлено в DTO
|
||||
- ✅ Импорт DocumentAggregateDTO добавлен
|
||||
- ✅ Тип поля корректный (DocumentAggregateDTO | null)
|
||||
@@ -0,0 +1,110 @@
|
||||
---
|
||||
description: Правила работы с двойными подписями документов
|
||||
alwaysApply: false
|
||||
---
|
||||
# Правила работы с двойными подписями документов
|
||||
|
||||
## Общая концепция
|
||||
|
||||
Двойная подпись означает, что документ подписывается двумя разными участниками процесса. Каждая подпись имеет свой уникальный идентификатор (`signatureId`).
|
||||
|
||||
## Типы документов
|
||||
|
||||
### IDocumentAggregate
|
||||
Структура агрегата документа, содержащая:
|
||||
- `document`: `SignedDigitalDocument` - подписанный документ с подписями
|
||||
- `hash`: `string` - хеш документа
|
||||
- `rawDocument`: `GeneratedDocument` - сырой (неподписанный) документ, необходимый для генерации новых подписей
|
||||
|
||||
### SignedDigitalDocument
|
||||
Подписанный документ, содержащий:
|
||||
- `doc_hash`: хеш содержимого
|
||||
- `hash`: общий хеш
|
||||
- `meta`: метаданные документа
|
||||
- `meta_hash`: хеш метаданных
|
||||
- `signatures`: массив подписей
|
||||
- `version`: версия стандарта
|
||||
|
||||
### ZGeneratedDocument (из cooptypes)
|
||||
Базовый документ для подписи:
|
||||
- `full_title`: полное название
|
||||
- `html`: HTML содержимое
|
||||
- `hash`: хеш документа
|
||||
- `meta`: метаданные
|
||||
- `binary`: бинарные данные (Uint8Array или string)
|
||||
|
||||
## Процесс двойной подписи
|
||||
|
||||
### 1. Подготовка данных
|
||||
Для добавления второй подписи к документу необходимо иметь:
|
||||
- `rawDocument` (ZGeneratedDocument) - базовый документ без подписей
|
||||
- `existingSignedDocuments` - массив уже существующих подписанных документов
|
||||
|
||||
### 2. Вызов signDocument
|
||||
```typescript
|
||||
const doubleSignedDocument = await signDocument(
|
||||
rawDocument, // ZGeneratedDocument - базовый документ
|
||||
username, // string - имя пользователя, подписывающего документ
|
||||
2, // signatureId - идентификатор подписи (2 для второй)
|
||||
[existingDocument] // existingSignedDocuments - массив существующих подписей
|
||||
);
|
||||
```
|
||||
|
||||
### 3. Параметры signDocument
|
||||
- **document**: `ZGeneratedDocument` - базовый документ (обязательно `rawDocument` из `IDocumentAggregate`)
|
||||
- **account**: `string` - имя аккаунта подписанта
|
||||
- **signatureId**: `number` - идентификатор подписи:
|
||||
- `1` - первая подпись
|
||||
- `2` - вторая подпись
|
||||
- `3` - третья подпись (если необходимо)
|
||||
- **existingSignedDocuments**: `ISignedDocument2[]` - массив документов с существующими подписями
|
||||
|
||||
## Пример реализации
|
||||
|
||||
```typescript
|
||||
export function useConfirmApproval() {
|
||||
const { signDocument } = useSignDocument();
|
||||
const { username } = useSessionStore();
|
||||
|
||||
const confirmApproval = async (
|
||||
coopname: string,
|
||||
approved_document: IDocumentAggregate
|
||||
): Promise<IConfirmApprovalOutput> => {
|
||||
if (!approved_document.rawDocument) {
|
||||
throw new Error('Документ не найден');
|
||||
}
|
||||
|
||||
// Подписываем документ второй подписью
|
||||
const doubleSignedDocument = await signDocument(
|
||||
approved_document.rawDocument, // ZGeneratedDocument
|
||||
username, // string
|
||||
2, // signatureId для второй подписи
|
||||
[approved_document.document], // existingSignedDocuments
|
||||
);
|
||||
|
||||
// Отправляем документ с двойной подписью
|
||||
return await api.confirmApproval({
|
||||
coopname,
|
||||
approval_hash: approved_document.hash,
|
||||
approved_document: doubleSignedDocument,
|
||||
});
|
||||
};
|
||||
|
||||
return { confirmApproval };
|
||||
}
|
||||
```
|
||||
|
||||
## Важные замечания
|
||||
|
||||
1. **rawDocument обязателен** - без него невозможно добавить новую подпись
|
||||
2. **signatureId должен быть уникальным** - каждая подпись имеет свой идентификатор
|
||||
3. **existingSignedDocuments** - содержит все предыдущие подписи документа
|
||||
4. **Типизация** - строго следить за типами `IDocumentAggregate`, `ZGeneratedDocument`, `SignedDigitalDocument`
|
||||
5. **Session Store** - использовать `useSessionStore().username` для получения имени подписанта
|
||||
|
||||
## Распространенные ошибки
|
||||
|
||||
- Попытка подписать `SignedDigitalDocument` напрямую (нужен `ZGeneratedDocument`)
|
||||
- Отсутствие `rawDocument` в `IDocumentAggregate`
|
||||
- Неправильный `signatureId` (не уникальный)
|
||||
- Отсутствие `existingSignedDocuments` при добавлении второй подписи
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user