Compare commits
903 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0f617cf1a1 | |||
| f8881cfcde | |||
| 0f5c7ad108 | |||
| b1ccb70b5b | |||
| dedfd68980 | |||
| e47ef47283 | |||
| a7e675f5ad | |||
| 67f1922ca2 | |||
| 702eb927bd | |||
| a6b15f0749 | |||
| bf80a839fa | |||
| 585afb16eb | |||
| 7ea382058b | |||
| a531bb1ab4 | |||
| cd08236dd6 | |||
| 1d72bd3960 | |||
| 3c08215671 | |||
| 4c45d1d701 | |||
| 8e10c57af2 | |||
| 0aeeb69581 | |||
| db86a8e001 | |||
| 61e33a2959 | |||
| a68068ded4 | |||
| 99dc260013 | |||
| e1f4fa066d | |||
| 0d60f4004d | |||
| 297055cb7c | |||
| be7c5fd864 | |||
| 818fa8b0a5 | |||
| 4ea43712d2 | |||
| d023beee30 | |||
| 2d1bef7087 | |||
| 3d3ba56282 | |||
| 729816ed26 | |||
| c1e6b1a433 | |||
| 1e8e5c4412 | |||
| 6c72942a30 | |||
| 7dff06d5b9 | |||
| 830b09ea2c | |||
| 8f2454d4fc | |||
| 9953f7e980 | |||
| fcb816b0e6 | |||
| b294c61b4e | |||
| 6435b50c72 | |||
| 7c804da6b2 | |||
| eaae515860 | |||
| 9bcfd3a654 | |||
| 109f4aaf98 | |||
| beb87b0c00 | |||
| 947077d011 | |||
| 3297616ad1 | |||
| 90c830fdb1 | |||
| 130c544191 | |||
| 63dcdde872 | |||
| e1a496759b | |||
| 99283dfdd7 | |||
| adee9ee032 | |||
| aaf3b5b98f | |||
| 3dbad0ff30 | |||
| 615eba3a84 | |||
| e7de054de2 | |||
| 6f911605d9 | |||
| 00a8461b5b | |||
| 7154c7f0e0 | |||
| 0f07666aeb | |||
| 489dc482e8 | |||
| 55c4759780 | |||
| 16b4fe0080 | |||
| 9cd995c5f2 | |||
| e60d6a5d67 | |||
| b274af05c3 | |||
| 40e725f2ed | |||
| 0f091eefc9 | |||
| 2b00c58e9b | |||
| 6d9681fb8a | |||
| acc4d27677 | |||
| ffe581a92b | |||
| 6e643515ce | |||
| 1cc7aca53f | |||
| e5111622a6 | |||
| 99b98cb5c9 | |||
| 20447cfb46 | |||
| 723a1813c9 | |||
| 04d52a0631 | |||
| 097d18cd56 | |||
| 5043995d14 | |||
| 804cfc8b72 | |||
| a0dc6f3333 | |||
| a53957271e | |||
| abae483dd3 | |||
| 4418e09211 | |||
| ca79457c19 | |||
| d2cd947b9c | |||
| ea28285639 | |||
| 6174ddea9a | |||
| ce56adc3c8 | |||
| 59334bb13a | |||
| 21cd1f5217 | |||
| 515df53c96 | |||
| cbf7f48c44 | |||
| ed37aeb615 | |||
| ea89558696 | |||
| c0bad75a37 | |||
| bdc227f110 | |||
| a9c3a1b6de | |||
| f0e7b0b63a | |||
| a709e2f1e3 | |||
| 3a34bd74db | |||
| b755cb715a | |||
| 42753fae0a | |||
| d8b8fa2d6e | |||
| a75277e0b8 | |||
| f95b83674b | |||
| bb13e780af | |||
| 6e80d5187c | |||
| db9b704d1d | |||
| 65a2b5361c | |||
| 7bca73cd7b | |||
| 59383014be | |||
| 60a5296f5f | |||
| eb4fe333a7 | |||
| 8d06d6d817 | |||
| b6afb536b6 | |||
| 790047fa8a | |||
| baf55b6b6d | |||
| 92d85244f1 | |||
| 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 | |||
| 08c31171a0 | |||
| 40addbf595 | |||
| abd6da0a37 | |||
| 794d1c5019 | |||
| b8d048bf3c | |||
| bb7b650ebc | |||
| 5253de9279 | |||
| 094945e350 | |||
| eb6b58e6db | |||
| 7bcb6e30a7 | |||
| 44ab386835 |
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"agentCanUpdateSnapshot": true
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
description:
|
||||
globs:
|
||||
alwaysApply: true
|
||||
---
|
||||
ВАЖНО!!! Мы работаем в МОНО-репозитории pnpm, любая установка пакетов производится ЧЕРЕЗ ФИЛЬТР компонента в корне: pnpm add glob --filter notificator2.
|
||||
|
||||
ВАЖНО!!! НИКОГДА НЕ ЗАПУСКАЙ ПРИЛОЖЕНИЕ ИЛИ ЕГО СБОРКУ!!! Просто отчитайся что всё сделал.
|
||||
@@ -0,0 +1,151 @@
|
||||
---
|
||||
description:
|
||||
globs: components/controller/**
|
||||
alwaysApply: false
|
||||
---
|
||||
# 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 в интерактор домена
|
||||
|
||||
## Поток данных
|
||||
1. Резолвер принимает входные данные и передает их в сервис
|
||||
2. Сервис вызывает интерактор домена, передавая ему объекты DTO (имплементирующие доменный интерфейс)
|
||||
3. Интерактор выполняет бизнес-логику и взаимодействует с портами домена
|
||||
4. Адаптеры (инфраструктурный слой) преобразуют доменные объекты в формат внешних систем (JSON для хранения мета-данных, ISO для дат и т.д.)
|
||||
5. Адаптеры возвращают результаты в домен, домен возвращает данные в сервис, который возвращает их в резолвер
|
||||
|
||||
## Правила преобразования данных
|
||||
1. Преобразование DTO → доменный объект: не требуется, если DTO имплементирует доменный интерфейс
|
||||
2. Преобразование доменный объект → инфраструктурный тип: происходит в адаптерах
|
||||
3. Преобразование документов, дат и других сложных объектов: используются утилитарные классы уровня инфраструктуры
|
||||
|
||||
## Работа с типами данных
|
||||
1. **Даты**:
|
||||
- В доменных интерфейсах используется тип `Date`
|
||||
- В DTO используется тип `Date` с декораторами `@IsDate()` и `@Type(() => Date)`
|
||||
- В инфраструктурном слое происходит преобразование `Date` → `string` (ISO формат)
|
||||
```typescript
|
||||
// Доменный интерфейс
|
||||
interface MeetDomainInterface {
|
||||
open_at: Date;
|
||||
}
|
||||
|
||||
// DTO
|
||||
@Field(() => Date)
|
||||
@IsDate()
|
||||
@Type(() => Date)
|
||||
open_at!: Date;
|
||||
|
||||
// Адаптер
|
||||
const blockchainData = {
|
||||
open_at: domainToBlockchainUtils.convertDateToBlockchainFormat(data.open_at)
|
||||
};
|
||||
```
|
||||
|
||||
2. **Документы**:
|
||||
- В доменных интерфейсах используется собственный тип `SignedDocumentDomainInterface<T>`
|
||||
- В DTO используется соответствующий DTO-класс (например, `SignedDigitalDocumentInputDTO`)
|
||||
- В инфраструктурном слое происходит преобразование между форматами (meta-поля в JSON и т.д.)
|
||||
|
||||
## Типовые ошибки
|
||||
1. **Нарушение изоляции домена**: домен должен быть изолирован от инфраструктуры. Не используйте импорты из `cooptypes` в доменных интерфейсах.
|
||||
```typescript
|
||||
// Неправильно
|
||||
import { MeetContract } from 'cooptypes';
|
||||
export type VoteDomainInterface = MeetContract.Actions.Vote.IInput;
|
||||
|
||||
// Правильно
|
||||
export interface VoteDomainInterface {
|
||||
coopname: string;
|
||||
hash: string;
|
||||
// ... доменные поля
|
||||
}
|
||||
```
|
||||
|
||||
2. **Преобразование в неправильном слое**: преобразование доменных объектов в инфраструктурные типы должно происходить в адаптерах, а не в доменном слое или сервисе.
|
||||
```typescript
|
||||
// Неправильно (в доменном интеракторе)
|
||||
async vote(data: VoteDomainInterface) {
|
||||
const blockchainData = { ...data, meta: JSON.stringify(data.meta) };
|
||||
// ...
|
||||
}
|
||||
|
||||
// Правильно (в адаптере)
|
||||
async vote(data: VoteDomainInterface) {
|
||||
const blockchainData = this.convertToBlockchainFormat(data);
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
3. **Дублирование логики преобразования**: общая логика преобразования должна быть вынесена в утилитарные классы.
|
||||
```typescript
|
||||
// Неправильно (дублирование в разных адаптерах)
|
||||
// В MeetAdapter
|
||||
private convertDoc(doc) { return { ...doc, meta: JSON.stringify(doc.meta) }; }
|
||||
// В BranchAdapter
|
||||
private convertDoc(doc) { return { ...doc, meta: JSON.stringify(doc.meta) }; }
|
||||
|
||||
// Правильно (общий утилитарный класс)
|
||||
// DomainToBlockchainUtils
|
||||
convertSignedDocumentToBlockchainFormat(doc) {
|
||||
return { ...doc, meta: JSON.stringify(doc.meta) };
|
||||
}
|
||||
```
|
||||
|
||||
## Примеры
|
||||
```typescript
|
||||
// Доменный интерфейс - чистый, без зависимостей от инфраструктуры
|
||||
export interface VoteOnAnnualGeneralMeetInputDomainInterface {
|
||||
coopname: string;
|
||||
hash: string;
|
||||
member: string;
|
||||
ballot: VoteItemInputDomainInterface[];
|
||||
}
|
||||
|
||||
// DTO имплементирует доменный интерфейс напрямую
|
||||
@InputType('VoteOnAnnualGeneralMeetInput')
|
||||
export class VoteOnAnnualGeneralMeetInputDTO implements VoteOnAnnualGeneralMeetInputDomainInterface {
|
||||
@Field(() => String)
|
||||
@IsString()
|
||||
coopname!: string;
|
||||
// ... остальные поля
|
||||
}
|
||||
|
||||
// Адаптер преобразует доменный объект в инфраструктурный тип
|
||||
async vote(data: VoteOnAnnualGeneralMeetInputDomainInterface): Promise<TransactionResult> {
|
||||
// Преобразуем доменный объект в инфраструктурный тип
|
||||
const blockchainData: MeetContract.Actions.Vote.IInput = {
|
||||
coopname: data.coopname,
|
||||
hash: data.hash,
|
||||
// ... преобразование других полей
|
||||
};
|
||||
// ... отправка данных во внешнюю систему
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,41 @@
|
||||
---
|
||||
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.
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
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,225 @@
|
||||
# Архитектура расширений в MonoCoop
|
||||
|
||||
## Основные принципы
|
||||
1. Расширения построены на модулях NestJS с использованием шаблона "Порты и адаптеры"
|
||||
2. Каждое расширение наследуется от `BaseExtModule` и реализует интерфейс `OnModuleInit`
|
||||
3. Расширения могут взаимодействовать с блокчейном через соответствующие порты
|
||||
4. Конфигурации расширений хранятся в БД и описываются с помощью Zod-схем
|
||||
5. Расширения регистрируются в глобальном реестре `AppRegistry`
|
||||
|
||||
## Структура расширения
|
||||
Минимальная структура расширения включает:
|
||||
- `XXX-extension.module.ts` - основной модуль расширения
|
||||
- `package.json` - информация о пакете
|
||||
- `README.md` - документация
|
||||
- `INSTALL.md` - инструкции по установке
|
||||
- `CHANGELOG.md` - история изменений
|
||||
|
||||
## Создание нового расширения
|
||||
1. Создайте директорию для расширения в `components/controller/src/extensions/`
|
||||
2. Создайте основной класс расширения, наследующийся от `BaseExtModule`
|
||||
3. Определите Zod-схему для конфигурации
|
||||
4. Реализуйте метод `initialize()`
|
||||
5. Зарегистрируйте расширение в `extensions.registry.ts`
|
||||
6. Добавьте расширение в список дефолтных приложений в `extension-domain.service.ts`
|
||||
|
||||
## Пример структуры модуля расширения
|
||||
```typescript
|
||||
// XXX-extension.module.ts
|
||||
export class XXXPlugin extends BaseExtModule {
|
||||
constructor(...) {
|
||||
super();
|
||||
}
|
||||
|
||||
name = 'xxx';
|
||||
plugin!: ExtensionDomainEntity<IConfig>;
|
||||
public configSchemas = Schema;
|
||||
|
||||
async initialize() {
|
||||
// Инициализация расширения
|
||||
// Настройка cron-задач
|
||||
}
|
||||
}
|
||||
|
||||
@Module({
|
||||
providers: [XXXPlugin],
|
||||
})
|
||||
export class XXXPluginModule {
|
||||
constructor(private readonly xxxPlugin: XXXPlugin) {}
|
||||
|
||||
async initialize() {
|
||||
await this.xxxPlugin.initialize();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Управление отображением конфигурации
|
||||
Zod-схемы используются для автоматического отображения формы настроек в интерфейсе пользователя.
|
||||
Через интерфейс `DeserializedDescriptionOfExtension` из `components/controller/src/types/shared/extension.types.ts`
|
||||
можно управлять отображением полей формы:
|
||||
|
||||
```typescript
|
||||
export const Schema = z.object({
|
||||
// Базовое поле с меткой
|
||||
simpleField: z.string().describe(
|
||||
describeField({
|
||||
label: 'Название поля',
|
||||
note: 'Подсказка под полем'
|
||||
})
|
||||
),
|
||||
|
||||
// Скрытое поле для служебного использования
|
||||
hiddenField: z.string().describe(
|
||||
describeField({
|
||||
label: 'Скрытое поле',
|
||||
visible: false
|
||||
})
|
||||
),
|
||||
|
||||
// Поле с проверкой значения
|
||||
validatedField: z.number().describe(
|
||||
describeField({
|
||||
label: 'Поле с валидацией',
|
||||
rules: ['val >= 5', 'val <= 100'],
|
||||
})
|
||||
),
|
||||
|
||||
// Форматированное поле с префиксом и суффиксом
|
||||
formattedField: z.number().describe(
|
||||
describeField({
|
||||
label: 'Форматированное поле',
|
||||
prepend: '$',
|
||||
append: 'USD',
|
||||
})
|
||||
),
|
||||
|
||||
// Многострочное текстовое поле
|
||||
multilineField: z.string().describe(
|
||||
describeField({
|
||||
label: 'Многострочное поле',
|
||||
maxRows: 5,
|
||||
minLength: 10,
|
||||
maxLength: 1000
|
||||
})
|
||||
),
|
||||
});
|
||||
```
|
||||
|
||||
Доступные поля для управления отображением:
|
||||
- `label` - название поля (обязательное)
|
||||
- `note` - пояснение или подсказка
|
||||
- `visible` - видимость поля (по умолчанию true)
|
||||
- `rules` - правила валидации в виде строковых выражений
|
||||
- `mask` - маска для ввода
|
||||
- `fillMask` - автозаполнение маски
|
||||
- `minLength` / `maxLength` - ограничения длины для текстовых полей
|
||||
- `maxRows` - количество строк для многострочного ввода
|
||||
- `append` / `prepend` - текст до/после значения поля
|
||||
|
||||
## Взаимодействие с блокчейном
|
||||
Для взаимодействия с блокчейном:
|
||||
1. Определите порт в доменном слое (например, `SovietBlockchainPort`)
|
||||
2. Инжектируйте порт в конструкторе расширения через DI
|
||||
3. Используйте методы порта для взаимодействия с блокчейном
|
||||
|
||||
```typescript
|
||||
@Inject(SOVIET_BLOCKCHAIN_PORT) private readonly sovietBlockchainPort: SovietBlockchainPort
|
||||
// ...
|
||||
const decisions = await this.sovietBlockchainPort.getDecisions(coopname);
|
||||
```
|
||||
|
||||
## Настройка планировщика задач
|
||||
Расширения могут использовать cron-задачи для периодического выполнения операций:
|
||||
|
||||
```typescript
|
||||
import cron from 'node-cron';
|
||||
|
||||
// Регистрация cron-задачи (каждые N минут)
|
||||
const cronExpression = `*/${this.plugin.config.checkInterval} * * * *`;
|
||||
cron.schedule(cronExpression, () => {
|
||||
this.logger.info('Запуск запланированной задачи');
|
||||
this.runTask();
|
||||
});
|
||||
```
|
||||
|
||||
## Работа с конфигурацией
|
||||
1. Определите Zod-схему для конфигурации
|
||||
2. Используйте `describeField` для добавления UI-метаданных к полям
|
||||
3. Получайте и обновляйте конфигурацию через репозиторий `extensionRepository`
|
||||
|
||||
```typescript
|
||||
export const Schema = z.object({
|
||||
checkInterval: z.number().describe(
|
||||
describeField({
|
||||
label: 'Интервал проверки (в минутах)',
|
||||
note: 'Минимум: 5 минут',
|
||||
rules: ['val >= 5'],
|
||||
})
|
||||
),
|
||||
});
|
||||
|
||||
// Обновление конфигурации
|
||||
this.plugin.config.lastCheckDate = new Date().toISOString();
|
||||
await this.extensionRepository.update(this.plugin);
|
||||
```
|
||||
|
||||
## Логирование действий
|
||||
Расширения должны логировать свои действия:
|
||||
1. Используйте `WinstonLoggerService` для системного логирования
|
||||
2. Используйте `LogExtensionDomainRepository` для хранения логов в БД
|
||||
|
||||
```typescript
|
||||
// Системное логирование
|
||||
this.logger.info(`Выполнение операции для ${id}`);
|
||||
|
||||
// Сохранение лога в БД
|
||||
await this.logExtensionRepository.push(this.name, {
|
||||
type: 'operation',
|
||||
timestamp: new Date().toISOString(),
|
||||
data: { ... },
|
||||
});
|
||||
```
|
||||
|
||||
## Регистрация расширения
|
||||
После создания расширения, добавьте его в `extensions.registry.ts`:
|
||||
|
||||
```typescript
|
||||
export const AppRegistry: INamedExtension = {
|
||||
myExtension: {
|
||||
is_builtin: false,
|
||||
is_internal: true,
|
||||
is_available: true,
|
||||
is_desktop: false,
|
||||
title: 'Моё расширение',
|
||||
description: 'Описание функциональности.',
|
||||
image: 'https://example.com/image.png',
|
||||
class: MyExtensionPluginModule,
|
||||
schema: MyExtensionSchema,
|
||||
tags: ['тег1', 'тег2'],
|
||||
readme: getReadmeContent('./myExtension'),
|
||||
instructions: getInstructionsContent('./myExtension'),
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## Дефолтные настройки
|
||||
Добавьте расширение в список дефолтных приложений в `extension-domain.service.ts`:
|
||||
|
||||
```typescript
|
||||
getDefaultApps(): Partial<ExtensionDomainEntity>[] {
|
||||
return [
|
||||
// ...
|
||||
{
|
||||
name: 'myExtension',
|
||||
enabled: true,
|
||||
config: {
|
||||
// Дефолтные значения конфигурации
|
||||
parameter1: 'value1',
|
||||
parameter2: 42,
|
||||
},
|
||||
},
|
||||
// ...
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
---
|
||||
description:
|
||||
globs: **/controller/src/extensions/**
|
||||
alwaysApply: false
|
||||
---
|
||||
# Архитектура расширений в MonoCoop
|
||||
|
||||
## Основные принципы
|
||||
1. Расширения построены на модулях NestJS с использованием шаблона "Порты и адаптеры"
|
||||
2. Каждое расширение наследуется от `BaseExtModule` и реализует интерфейс `OnModuleInit`
|
||||
3. Расширения могут взаимодействовать с блокчейном через соответствующие порты
|
||||
4. Конфигурации расширений хранятся в БД и описываются с помощью Zod-схем
|
||||
5. Расширения регистрируются в глобальном реестре `AppRegistry`
|
||||
|
||||
## Структура расширения
|
||||
Минимальная структура расширения включает:
|
||||
- `XXX-extension.module.ts` - основной модуль расширения
|
||||
- `package.json` - информация о пакете
|
||||
- `README.md` - документация
|
||||
- `INSTALL.md` - инструкции по установке
|
||||
- `CHANGELOG.md` - история изменений
|
||||
|
||||
## Создание нового расширения
|
||||
1. Создайте директорию для расширения в `components/controller/src/extensions/`
|
||||
2. Создайте основной класс расширения, наследующийся от `BaseExtModule`
|
||||
3. Определите Zod-схему для конфигурации
|
||||
4. Реализуйте метод `initialize()`
|
||||
5. Зарегистрируйте расширение в `extensions.registry.ts`
|
||||
6. Добавьте расширение в список дефолтных приложений в `extension-domain.service.ts`
|
||||
|
||||
## Пример структуры модуля расширения
|
||||
```typescript
|
||||
// XXX-extension.module.ts
|
||||
export class XXXPlugin extends BaseExtModule {
|
||||
constructor(...) {
|
||||
super();
|
||||
}
|
||||
|
||||
name = 'xxx';
|
||||
plugin!: ExtensionDomainEntity<IConfig>;
|
||||
public configSchemas = Schema;
|
||||
|
||||
async initialize() {
|
||||
// Инициализация расширения
|
||||
// Настройка cron-задач
|
||||
}
|
||||
|
||||
// Дополнительные методы расширения
|
||||
}
|
||||
|
||||
@Module({
|
||||
providers: [XXXPlugin],
|
||||
})
|
||||
export class XXXPluginModule {
|
||||
constructor(private readonly xxxPlugin: XXXPlugin) {}
|
||||
|
||||
async initialize() {
|
||||
await this.xxxPlugin.initialize();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Взаимодействие с блокчейном
|
||||
Для взаимодействия с блокчейном:
|
||||
1. Определите порт в доменном слое (например, `SovietBlockchainPort`)
|
||||
2. Инжектируйте порт в конструкторе расширения через DI
|
||||
3. Используйте методы порта для взаимодействия с блокчейном
|
||||
|
||||
```typescript
|
||||
@Inject(SOVIET_BLOCKCHAIN_PORT) private readonly sovietBlockchainPort: SovietBlockchainPort
|
||||
// ...
|
||||
const decisions = await this.sovietBlockchainPort.getDecisions(coopname);
|
||||
```
|
||||
|
||||
## Настройка планировщика задач
|
||||
Расширения могут использовать cron-задачи для периодического выполнения операций:
|
||||
|
||||
```typescript
|
||||
import cron from 'node-cron';
|
||||
|
||||
// Регистрация cron-задачи (каждые N минут)
|
||||
const cronExpression = `*/${this.plugin.config.checkInterval} * * * *`;
|
||||
cron.schedule(cronExpression, () => {
|
||||
this.logger.info('Запуск запланированной задачи');
|
||||
this.runTask();
|
||||
});
|
||||
```
|
||||
|
||||
## Работа с конфигурацией
|
||||
1. Определите Zod-схему для конфигурации
|
||||
2. Используйте `describeField` для добавления UI-метаданных к полям
|
||||
3. Получайте и обновляйте конфигурацию через репозиторий `extensionRepository`
|
||||
|
||||
```typescript
|
||||
export const Schema = z.object({
|
||||
checkInterval: z.number().describe(
|
||||
describeField({
|
||||
label: 'Интервал проверки (в минутах)',
|
||||
note: 'Минимум: 5 минут',
|
||||
rules: ['val >= 5'],
|
||||
})
|
||||
),
|
||||
});
|
||||
|
||||
// Обновление конфигурации
|
||||
this.plugin.config.lastCheckDate = new Date().toISOString();
|
||||
await this.extensionRepository.update(this.plugin);
|
||||
```
|
||||
|
||||
## Логирование действий
|
||||
Расширения должны логировать свои действия:
|
||||
1. Используйте `WinstonLoggerService` для системного логирования
|
||||
2. Используйте `LogExtensionDomainRepository` для хранения логов в БД
|
||||
|
||||
```typescript
|
||||
// Системное логирование
|
||||
this.logger.info(`Выполнение операции для ${id}`);
|
||||
|
||||
// Сохранение лога в БД
|
||||
await this.logExtensionRepository.push(this.name, {
|
||||
type: 'operation',
|
||||
timestamp: new Date().toISOString(),
|
||||
data: { ... },
|
||||
});
|
||||
```
|
||||
|
||||
## Регистрация расширения
|
||||
После создания расширения, добавьте его в `extensions.registry.ts`:
|
||||
|
||||
```typescript
|
||||
export const AppRegistry: INamedExtension = {
|
||||
myExtension: {
|
||||
is_builtin: false,
|
||||
is_internal: true,
|
||||
is_available: true,
|
||||
is_desktop: false,
|
||||
title: 'Моё расширение',
|
||||
description: 'Описание функциональности.',
|
||||
image: 'https://example.com/image.png',
|
||||
class: MyExtensionPluginModule,
|
||||
schema: MyExtensionSchema,
|
||||
tags: ['тег1', 'тег2'],
|
||||
readme: getReadmeContent('./myExtension'),
|
||||
instructions: getInstructionsContent('./myExtension'),
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## Дефолтные настройки
|
||||
Добавьте расширение в список дефолтных приложений в `extension-domain.service.ts`:
|
||||
|
||||
```typescript
|
||||
getDefaultApps(): Partial<ExtensionDomainEntity>[] {
|
||||
return [
|
||||
// ...
|
||||
{
|
||||
name: 'myExtension',
|
||||
enabled: true,
|
||||
config: {
|
||||
// Дефолтные значения конфигурации
|
||||
parameter1: 'value1',
|
||||
parameter2: 42,
|
||||
},
|
||||
},
|
||||
// ...
|
||||
];
|
||||
}
|
||||
```
|
||||
@@ -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,59 @@
|
||||
---
|
||||
description:
|
||||
globs: components/sdk/**
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Руководство по работе с GraphQL Zeus в SDK
|
||||
|
||||
## Основной процесс
|
||||
|
||||
1. **Анализ DTO бэкенда**:
|
||||
- Изучите структуру DTO классов (`@ObjectType`) в бэкенде
|
||||
- Обратите внимание на имена типов в декораторах `@ObjectType('ИмяТипа')`
|
||||
- Отметьте поля, связи и вложенные объекты
|
||||
|
||||
2. **Создание селекторов**:
|
||||
- Для каждого DTO создайте соответствующий селектор с именем `raw<ИмяТипа>Selector`
|
||||
- Селектор - это объект, где ключи соответствуют полям DTO, а значения - `true`
|
||||
- Для вложенных объектов используйте вложенные селекторы: `field: nestedSelector`
|
||||
- Для списков используйте один селектор без массива: `items: itemSelector`
|
||||
|
||||
3. **Валидация селекторов**:
|
||||
- Для каждого селектора создайте проверку типа:
|
||||
```typescript
|
||||
const _validate: MakeAllFieldsRequired<ValueTypes['ТочноеИмяГрафКьЭлТипа']> = rawSelector
|
||||
```
|
||||
- Имя типа должно точно совпадать с именем в декораторе `@ObjectType`
|
||||
|
||||
4. **Экспорт селекторов**:
|
||||
- Создайте финальный селектор с помощью функции `Selector`:
|
||||
```typescript
|
||||
export const typeSelector = Selector('ТочноеИмяГрафКьЭлТипа')(rawTypeSelector)
|
||||
```
|
||||
- Экспортируйте сырой селектор для переиспользования
|
||||
- Экспортируйте тип модели: `export type modelType = ModelTypes['ТочноеИмяГрафКьЭлТипа']`
|
||||
|
||||
5. **Создание запросов/мутаций**:
|
||||
- Используйте селекторы в запросах и мутациях:
|
||||
```typescript
|
||||
export const query = Selector('Query')({
|
||||
queryName: [{ data: $('data', 'ТочноеИмяВходногоТипа!') }, exportedSelector]
|
||||
})
|
||||
```
|
||||
- Для параметров используйте оператор `$` с точным именем входного типа
|
||||
- Создайте интерфейс входных данных:
|
||||
```typescript
|
||||
export interface IInput {
|
||||
data: ModelTypes['ТочноеИмяВходногоТипа']
|
||||
}
|
||||
```
|
||||
|
||||
## Особенности работы
|
||||
|
||||
- **Документы**: всегда сохраняйте структуру `{ hash, signatures, rawDocument }`
|
||||
- **Сложные DTO**: разбивайте на атомарные селекторы и комбинируйте их
|
||||
- **Типы в Zeus**: часто отличаются от имен классов в бэкенде, всегда проверяйте в `schema.gql`
|
||||
- **Массивы**: Zeus автоматически обрабатывает массивы, не используйте `[selector]`
|
||||
|
||||
Это руководство поможет правильно структурировать работу с SDK и избежать типичных ошибок при работе с Zeus.
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
globs: notifications/src/workflows/**/*.ts
|
||||
---
|
||||
# Правила валидации воркфлоу
|
||||
|
||||
## 🚫 ID воркфлоу
|
||||
- **Длина ID не должна превышать 32 символа**
|
||||
- Используйте описательные, но короткие идентификаторы малыми латинскими буквами и тире.
|
||||
|
||||
## 🚫 Условия в шаблонах
|
||||
- **Запрещено использовать JavaScript выражения в шаблонах Novu**
|
||||
- Нельзя использовать:
|
||||
- Тернарные операторы: `{{condition ? "text1" : "text2"}}`
|
||||
- Логические операторы: `{{field && "text"}}`
|
||||
- Любые другие JS конструкции
|
||||
|
||||
## ✅ Рекомендации
|
||||
- Добавляйте текстовые поля в payload для условной логики
|
||||
- Вычисляйте значения на стороне сервера перед отправкой уведомления
|
||||
- Используйте только простые переменные: `{{payload.fieldName}}`
|
||||
@@ -0,0 +1,163 @@
|
||||
name: Build Docker Images
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Debug info
|
||||
run: |
|
||||
echo "Текущая ветка:"
|
||||
git branch --show-current
|
||||
echo "Последние коммиты:"
|
||||
git log -n 3 --oneline
|
||||
echo "Проверяем файлы в директории components/desktop/src-ssr:"
|
||||
ls -la components/desktop/src-ssr/ || echo "Директория не найдена!"
|
||||
echo "Проверяем файлы в middlewares:"
|
||||
ls -la components/desktop/src-ssr/middlewares/ || echo "Директория middlewares не найдена!"
|
||||
|
||||
- name: Set docker tags
|
||||
run: |
|
||||
if [[ $GITHUB_REF == refs/tags/* ]]; then
|
||||
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
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
# Сначала собираем базовый образ с runtime
|
||||
- name: Build base image
|
||||
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
|
||||
run: |
|
||||
echo "FROM dicoop/mono-base:${{ env.DOCKER_TAG }}" > Dockerfile.desktop
|
||||
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: |
|
||||
echo "FROM dicoop/mono-base:${{ env.DOCKER_TAG }}" > Dockerfile.coopback
|
||||
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: |
|
||||
echo "FROM dicoop/mono-base:${{ env.DOCKER_TAG }}" > Dockerfile.cooparser
|
||||
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: |
|
||||
echo "FROM dicoop/mono-base:${{ env.DOCKER_TAG }}" > Dockerfile.notificator
|
||||
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/*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
|
||||
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 }}"}'
|
||||
@@ -0,0 +1,107 @@
|
||||
name: Publish Docs
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- testnet
|
||||
- dev
|
||||
|
||||
jobs:
|
||||
build-and-publish-docs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.10'
|
||||
|
||||
- name: Install pnpm
|
||||
run: npm install -g pnpm
|
||||
|
||||
- name: Install Python requirements
|
||||
run: |
|
||||
python -m venv venv
|
||||
source venv/bin/activate
|
||||
pip install mkdocs-material mkdocs-macros-plugin mkdocs-section-index pymdown-extensions
|
||||
working-directory: ./components/docs
|
||||
|
||||
- name: Install Node.js dependencies
|
||||
run: pnpm install
|
||||
working-directory: ./components/docs
|
||||
|
||||
- name: Patch spectaql-config.yml for CI
|
||||
run: |
|
||||
sed -i.bak "0,/url:.*/s|url:.*|url: 'https://testnet.coopenomics.world/backend/v1/graphql'|" spectaql-config.yml
|
||||
working-directory: ./components/controller
|
||||
|
||||
- name: Show patched spectaql-config.yml
|
||||
run: cat spectaql-config.yml
|
||||
working-directory: ./components/controller
|
||||
|
||||
- name: Build cooptypes
|
||||
run: pnpm run build
|
||||
working-directory: ./components/cooptypes
|
||||
|
||||
- name: Generate controller docs
|
||||
run: pnpm run docs
|
||||
working-directory: ./components/controller
|
||||
|
||||
- name: Copy controller docs
|
||||
run: |
|
||||
mkdir -p ./components/docs/docs/graphql
|
||||
cp -r ./components/controller/docs/* ./components/docs/docs/graphql/
|
||||
|
||||
- name: Generate sdk docs
|
||||
run: pnpm run docs
|
||||
working-directory: ./components/sdk
|
||||
|
||||
- name: Copy sdk docs
|
||||
run: |
|
||||
mkdir -p ./components/docs/docs/sdk
|
||||
cp -r ./components/sdk/docs/* ./components/docs/docs/sdk/
|
||||
|
||||
- name: Generate cooptypes docs
|
||||
run: pnpm run docs
|
||||
working-directory: ./components/cooptypes
|
||||
|
||||
- name: Copy cooptypes docs
|
||||
run: |
|
||||
mkdir -p ./components/docs/docs/cooptypes
|
||||
cp -r ./components/cooptypes/docs/* ./components/docs/docs/cooptypes/
|
||||
|
||||
- name: Build docs (mkdocs)
|
||||
run: |
|
||||
source venv/bin/activate
|
||||
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
|
||||
env:
|
||||
GIT_AUTHOR_NAME: github-actions
|
||||
GIT_AUTHOR_EMAIL: github-actions@github.com
|
||||
GIT_COMMITTER_NAME: github-actions
|
||||
GIT_COMMITTER_EMAIL: github-actions@github.com
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -0,0 +1,31 @@
|
||||
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:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24
|
||||
registry-url: https://registry.npmjs.org
|
||||
|
||||
- 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 }}
|
||||
+8
-2
@@ -1,5 +1,11 @@
|
||||
node_modules/
|
||||
lerna-debug.log
|
||||
components/controller/graph.png
|
||||
wallet-data/
|
||||
blockchain-data/
|
||||
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
-3
@@ -1,14 +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"],
|
||||
"eslint.run": "onType",
|
||||
"eslint.validate": ["javascript", "typescript", "vue"],
|
||||
// "typescript.tsdk": "node_modules/typescript/lib",
|
||||
"i18n-ally.localesPaths": ["src/i18n"],
|
||||
"typescript.format.enable": true,
|
||||
"typescript.format.indentSwitchCase": false,
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
# AGENTS.md
|
||||
|
||||
## Cursor Cloud specific instructions
|
||||
|
||||
### Обзор
|
||||
|
||||
Монорепозиторий «Цифровой Кооператив» (monocoop) — платформа управления кооперативами на блокчейне EOSIO. pnpm v9 + Lerna. Node.js v20.
|
||||
|
||||
### Сервисы
|
||||
|
||||
| Компонент | Контейнер | Порт | Описание |
|
||||
|-----------|-----------|------|----------|
|
||||
| controller | coopback | 2998 | NestJS GraphQL API |
|
||||
| desktop | desktop | 2999 | Vue 3 + Quasar SPA |
|
||||
| parser | cooparser | 4000 | Индексация блокчейна через SHiP |
|
||||
| blockchain | node | 8888, 8070 | EOSIO node + State History Plugin |
|
||||
| MongoDB | mongo | 27017 | Основная БД (replica set) |
|
||||
| Redis | monoredis | 6379 | Кэш и стримы |
|
||||
| PG | (см. compose) | 5532→5432 | Реляционная БД |
|
||||
|
||||
### Полный перезапуск (одна команда)
|
||||
|
||||
```
|
||||
pnpm run reboot
|
||||
```
|
||||
|
||||
Делает: останавливает контейнеры → чистит blockchain data и volumes → поднимает инфру → ждёт готовности → `pnpm run boot` → запускает parser и controller.
|
||||
|
||||
### Первоначальная настройка Cloud-окружения
|
||||
|
||||
1. **`/etc/hosts`** — обязательно для boot (запускается на хосте, обращается к MongoDB по docker hostname):
|
||||
```
|
||||
echo "127.0.0.1 mongo" | sudo tee -a /etc/hosts
|
||||
echo "127.0.0.1 monoredis" | sudo tee -a /etc/hosts
|
||||
```
|
||||
|
||||
2. **WeasyPrint** — системная зависимость для генерации PDF:
|
||||
```
|
||||
sudo apt-get install -y python3 python3-venv libpango-1.0-0 libcairo2 libffi-dev libjpeg-dev libopenjp2-7-dev libharfbuzz-dev
|
||||
sudo python3 -m venv /opt/weasyprint-venv && sudo /opt/weasyprint-venv/bin/pip install WeasyPrint==67
|
||||
sudo ln -sf /opt/weasyprint-venv/bin/weasyprint /usr/local/bin/weasyprint
|
||||
```
|
||||
|
||||
3. **Контракты** (test-режим — позволяет boot с 1 членом совета):
|
||||
```
|
||||
cd components/contracts && sudo rm -rf build && bash build-all.sh test
|
||||
```
|
||||
|
||||
4. **Shared-библиотеки** (порядок важен):
|
||||
```
|
||||
pnpm --filter cooptypes run build
|
||||
pnpm --filter @coopenomics/factory run build
|
||||
pnpm --filter @coopenomics/sdk run build
|
||||
pnpm --filter @coopenomics/notifications run build
|
||||
```
|
||||
|
||||
5. **`.env` файлы** — скопировать из `.env-example`, адаптировать hostnames:
|
||||
- Controller/Parser (в Docker): хосты по именам контейнеров из docker-compose (порт БД 5432)
|
||||
- Boot (на хосте): `127.0.0.1`, PG порт `5532`, mongo через `/etc/hosts`
|
||||
- Desktop: `127.0.0.1`
|
||||
- **CHAIN_ID**: берётся из `curl http://localhost:8888/v1/chain/get_info` после старта ноды
|
||||
- Controller требует `VAPID_PUBLIC_KEY` и `VAPID_PRIVATE_KEY`
|
||||
|
||||
6. **Запуск**: `pnpm run reboot`, затем `docker compose up -d --force-recreate coopback cooparser` (если .env менялись)
|
||||
|
||||
### Запуск тестов
|
||||
|
||||
- **Factory** (`components/factory`): нужен только MongoDB. Запуск:
|
||||
```
|
||||
NODE_ENV=test SOURCE=local MONGO_URI=$MONGO_URI SKIP_BLOCK_FETCH=TRUE pnpm --filter @coopenomics/factory test
|
||||
```
|
||||
`MONGO_URI` по умолчанию: `mongodb://<host>:27017/cooperative-x`.
|
||||
- **Boot** (`components/boot`): требует полный EOSIO blockchain + MongoDB + PG. Запуск после `pnpm run reboot`:
|
||||
```
|
||||
pnpm --filter @coopenomics/boot test
|
||||
```
|
||||
- **Duplicate transaction** — в boot-тестах EOSIO отклоняет транзакции с одинаковым хешем (TAPOS block + action data). При повторном вызове `refreshSegment` для того же участника — добавить `await sleep(500)` перед ним. Паттерн уже используется (см. комментарий на строке ~720 capital.test.ts).
|
||||
|
||||
### Маркетплейс (cooplace extension)
|
||||
|
||||
**Архитектура:**
|
||||
- `cooplace` = бэкенд маркетплейса (карточки, категории, настройки, match, cycles)
|
||||
- `marketplace` = фронтенд расширение (витрина, заказы — использует API cooplace)
|
||||
- `market-admin` = фронтенд админки (настройки, модерация)
|
||||
|
||||
**Ключевой принцип:** Каждая встречная заявка сразу в блокчейн → блокировка средств. Карточки в БД, заявки в blockchain.
|
||||
|
||||
**Циклы:** min_units/deadline — порог для supply, не для match. При истечении → cancel через blockchain.
|
||||
|
||||
**Настройки** (MarketplaceSettings): lead_request_policy (offers_only/orders_only/both), publish_access_policy (all_members/whitelist/council_only), moderation, cycles, delivery types.
|
||||
|
||||
### Критические gotchas
|
||||
|
||||
- **SHiP порт 8070** — `state-history-endpoint = 0.0.0.0:8070` в config.ini. Парсер: `SHIP=ws://node:8070`.
|
||||
- **Парсер START_BLOCK**: при `START_BLOCK=1` на чистой БД стартует с HEAD и делает частичную инициализацию. Для полного replay: временно `START_BLOCK=2`, после первого запуска вернуть `1`.
|
||||
- **SSR desktop в dev** — расширения не рендерятся из-за Pinia SSR-сериализации компонентов. Dev — SPA (`quasar dev`), production build SSR работает.
|
||||
- **Тестовые учётные данные**: email `ivanov@example.com`, ключ — дефолтный EOSIO dev key (см. `components/boot/.env-example`), пользователь `ant` (председатель).
|
||||
- **Docker hostnames**: без `network_mode: host` — контейнеры обращаются друг к другу по именам контейнеров из docker-compose.
|
||||
- **Установка пакетов**: только через фильтр — `pnpm add <pkg> --filter <component>`.
|
||||
+282
@@ -1,8 +1,290 @@
|
||||
# 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
|
||||
|
||||
В этом релизе рабочие столы переведены на серверный рендеринг, что улучшает стабильность развертывания и упрощает автоматизацию поставки ПО. Данный релиз является подготовительным к переходу на новый стандарт цифровых документов на платформе.
|
||||
|
||||
✨ Новые функции
|
||||
- [#245](https://github.com/coopenomics/mono/issues/245): Перевод десктопа на серверный рендеринг с поддержкой динамических переменных окружения
|
||||
|
||||
🐛 Исправления ошибок
|
||||
- [#247](https://github.com/coopenomics/mono/issues/247): Исправлен баг с повторной поставкой ПО при обрывах соединения между серверами
|
||||
|
||||
🔧 Улучшения
|
||||
- [#246](https://github.com/coopenomics/mono/issues/246): Перевод CI/CD на сборку и поставку предсобранных докер-контейнеров
|
||||
|
||||
#releases
|
||||
|
||||
---
|
||||
|
||||
# MONO v2025.4.29
|
||||
|
||||
В этом релизе реализована новая архитектура рабочих столов с установкой через маркетплейс. Добавлены стол пайщика и стол совета, переработаны разделы документов и подписей, улучшено разделение кошелька и профиля для повышения удобства пользователей.
|
||||
|
||||
✨ Новые функции
|
||||
- [#234](https://github.com/coopenomics/mono/issues/234): Маркетплейс рабочих столов с поддержкой разных ролей и микросервисной архитектурой
|
||||
- [#238](https://github.com/coopenomics/mono/issues/238): Контроллер общего собрания пайщиков с поддержкой документооборота и подписей
|
||||
- [#233](https://github.com/coopenomics/mono/issues/233): Минимальный смарт-контракт общих собраний пайщиков
|
||||
- [#232](https://github.com/coopenomics/mono/issues/232): Бэкенд полного обозревателя блоков
|
||||
|
||||
🐛 Исправления ошибок
|
||||
- [#231](https://github.com/coopenomics/mono/issues/231): Исправления ошибок регистрации, выхода из системы, отображения платежей и редактирования организации
|
||||
|
||||
🔧 Улучшения
|
||||
- [#243](https://github.com/coopenomics/mono/issues/243): Контроль прав доступа на получении документов пайщика
|
||||
- [#242](https://github.com/coopenomics/mono/issues/242): Бесконечный скролл на документах пайщика и кооператива
|
||||
- [#241](https://github.com/coopenomics/mono/issues/241): Раздел "Документы" для пайщика
|
||||
- [#240](https://github.com/coopenomics/mono/issues/240): Множественные подписи на одном документе
|
||||
- [#239](https://github.com/coopenomics/mono/issues/239): Последовательные методы подписи протокола общего собрания
|
||||
- [#237](https://github.com/coopenomics/mono/issues/237): Мобильная вёрстка на страницах стола совета
|
||||
- [#236](https://github.com/coopenomics/mono/issues/236): Пересобран лендинг для MONO
|
||||
- [#235](https://github.com/coopenomics/mono/issues/235): Настроен флоу гитхаб-релизов с описаниями
|
||||
- [#226](https://github.com/coopenomics/mono/issues/226): Встроено редактирование пайщиков
|
||||
- [#224](https://github.com/coopenomics/mono/issues/224): Добавлено ТЗ по контракту капитализации
|
||||
|
||||
---
|
||||
|
||||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.2.10](https://github.com/coopenomics/mono/compare/v2.2.9...v2.2.10) (2025-03-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add Zeus type ([7bcb6e3](https://github.com/coopenomics/mono/commit/7bcb6e30a77b0ab89c5293188b58f08f19c8761e))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.2.9](https://github.com/coopenomics/mono/compare/v2.2.8...v2.2.9) (2025-03-12)
|
||||
|
||||
**Note:** Version bump only for package monocoop
|
||||
|
||||
+40
-1
@@ -1,10 +1,49 @@
|
||||
FROM node:18-alpine
|
||||
FROM node:20-slim AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Сразу копируем все файлы
|
||||
COPY . .
|
||||
|
||||
# Устанавливаем инструменты
|
||||
RUN npm install -g pnpm lerna
|
||||
|
||||
# Установка зависимостей
|
||||
# Используем версию pnpm, совместимую с существующим lock-файлом
|
||||
RUN pnpm install
|
||||
|
||||
# Установка системных зависимостей для WeasyPrint и диагностических утилит (Debian/Ubuntu версии)
|
||||
RUN apt-get update && apt-get install -y \
|
||||
python3 \
|
||||
python3-pip \
|
||||
python3-venv \
|
||||
gcc \
|
||||
g++ \
|
||||
python3-dev \
|
||||
libpango-1.0-0 \
|
||||
libpangoft2-1.0-0 \
|
||||
libpangocairo-1.0-0 \
|
||||
libcairo2 \
|
||||
libcairo2-dev \
|
||||
libffi-dev \
|
||||
shared-mime-info \
|
||||
zlib1g-dev \
|
||||
libjpeg-dev \
|
||||
libopenjp2-7-dev \
|
||||
procps \
|
||||
wget \
|
||||
&& python3 -m venv /venv \
|
||||
&& /venv/bin/pip install WeasyPrint==67 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Сборка всех компонентов
|
||||
RUN lerna run build
|
||||
|
||||
# Финальный образ
|
||||
FROM builder AS runtime
|
||||
|
||||
# Настройка переменных окружения
|
||||
ENV PATH="/venv/bin:$PATH"
|
||||
|
||||
# Проверка WeasyPrint
|
||||
RUN weasyprint --version
|
||||
+367
@@ -0,0 +1,367 @@
|
||||
# MARKET-LOGIC.md — Бизнес-процессы маркетплейса
|
||||
|
||||
## 2А. БИЗНЕС-ПРОЦЕССЫ СМАРТ-КОНТРАКТОВ
|
||||
|
||||
### Основные бизнес-сценарии
|
||||
|
||||
**Сценарий 1: Поставка-приобретение имущества**
|
||||
- **Описание:** Обмен имущества между пайщиками через кооператив с блокировкой средств и документооборотом
|
||||
- **Участники:** Заказчик, Поставщик, Совет кооператива, Председатель КУ
|
||||
- **Бизнес-ценность:** Основной процесс кооперативного маркетплейса
|
||||
|
||||
**Сценарий 1А.** Прямая поставка (OFFER→ORDER) — поставщик публикует, заказчик откликается
|
||||
**Сценарий 1Б.** Обратная поставка (ORDER→OFFER) — заказчик публикует, поставщики откликаются
|
||||
**Сценарий 1В.** Из запасов кооператива (COOPSTOCK) — имущество уже на балансе
|
||||
|
||||
**Сценарий 2: Гарантийный возврат**
|
||||
- **Описание:** Возврат бракованного имущества в течение гарантийного срока
|
||||
- **Участники:** Заказчик, Поставщик, Председатель КУ, Совет
|
||||
- **Бизнес-ценность:** Защита интересов пайщика и качества имущества
|
||||
|
||||
**Сценарий 3: Уничтожение/перепредложение**
|
||||
- **Описание:** Утилизация просроченного или перепродажа по новой цене
|
||||
- **Участники:** Председатель КУ, Совет
|
||||
- **Бизнес-ценность:** Управление складскими запасами и минимизация потерь
|
||||
|
||||
**Сценарий 4: Транспортировка**
|
||||
- **Описание:** Перевозка имущества между КУ группами
|
||||
- **Участники:** Председатель КУ отправителя, Водитель, Председатель КУ получателя
|
||||
- **Бизнес-ценность:** Логистика распределённой кооперативной сети
|
||||
|
||||
---
|
||||
|
||||
### Процесс 1А: Прямая поставка (OFFER→ORDER)
|
||||
|
||||
**Предусловия:**
|
||||
- Поставщик создал карточку товара в БД (статус: `published`)
|
||||
- Заказчик нашёл карточку на витрине и решил заказать
|
||||
- У заказчика достаточно средств на цифровом кошельке
|
||||
|
||||
**Последовательность шагов:**
|
||||
|
||||
#### Шаг 1: orderoffer — Заказчик создаёт заявку
|
||||
|
||||
- **Предусловие:** Карточка товара в БД (published). При создании заявки происходит **match** — заявка публикуется в блокчейн
|
||||
- **Исполнитель:** Заказчик (через контроллер, авторизация от кооператива)
|
||||
- **Подписываемые документы:** Заявление на конвертацию из кошелька (convert_in)
|
||||
- **Проводки по кошелькам:**
|
||||
- Списать у заказчика `total_cost` из ЦПП «Цифровой Кошелёк»
|
||||
- Начислить заказчику `total_cost` в ЦПП «Маркетплейс»
|
||||
- Заблокировать у заказчика `total_cost` в ЦПП «Маркетплейс»
|
||||
- **Статус заявки:** `active`
|
||||
- **Параметры:** `delivery_type` (internal/external), `contribution_type` (share/member)
|
||||
|
||||
#### Шаг 2: accept — Поставщик принимает заявку
|
||||
|
||||
- **Исполнитель:** Поставщик
|
||||
- **Подписываемые документы:**
|
||||
- Заявление на конвертацию в кошелёк (convert_out)
|
||||
- Заявление на имущественный паевой взнос (contribution_statement)
|
||||
- **Проводки:** Нет
|
||||
- **Эффект:** Создаётся **одно** заявление в совет — `authcontrib` (авторизация взноса)
|
||||
- **Статус заявки:** `accepted`
|
||||
|
||||
#### Шаг 3: authcontrib — Совет авторизует взнос
|
||||
|
||||
- **Исполнитель:** Совет кооператива (автоматически через soviet контракт)
|
||||
- **Подписываемые документы:** Решение совета об авторизации взноса
|
||||
- **Проводки:** Нет
|
||||
- **Статус заявки:** `authorized` — поставщик может начинать поставку
|
||||
|
||||
#### Шаг 4: supply — Поставщик поставляет имущество на КУ
|
||||
|
||||
- **Исполнитель:** Поставщик
|
||||
- **Подписываемые документы:** Акт поставки (supply_act)
|
||||
- **Проводки:** Нет
|
||||
- **Статус заявки:** `supplied1`
|
||||
|
||||
#### Шаг 5: supplcnf — Председатель КУ подтверждает поставку
|
||||
|
||||
- **Исполнитель:** Председатель КУ поставщика
|
||||
- **Подписываемые документы:** Акт подтверждения поставки (supply_act_conf)
|
||||
- **Проводки по кошелькам:**
|
||||
- Начислить поставщику `base_cost` в ЦПП «Маркетплейс»
|
||||
- Заблокировать у поставщика `base_cost` в ЦПП «Маркетплейс»
|
||||
- **Проводки по ledger:**
|
||||
- Увеличить паевой фонд (счёт 80) на `total_cost`
|
||||
- **Статус заявки:** `supplied2`, имущество на складе КУ
|
||||
|
||||
#### Шаг 6: [Транспортировка] — При необходимости (см. Сценарий 4)
|
||||
|
||||
#### Шаг 7: delivered — Готово к выдаче
|
||||
|
||||
- **Исполнитель:** Председатель КУ получателя
|
||||
- **Подписываемые документы:** Нет
|
||||
- **Проводки:** Нет
|
||||
- **Статус заявки:** `delivered`
|
||||
|
||||
#### Шаг 8: reqreturn — Заказчик запрашивает возврат
|
||||
|
||||
- **Исполнитель:** Заказчик
|
||||
- **Подписываемые документы:** Заявление на возврат паевого взноса имуществом (return_statement) — с актуальными данными о весе/составе
|
||||
- **Проводки:** Нет
|
||||
- **Эффект:** Создаётся заявление в совет — `authreturn`
|
||||
- **Статус заявки:** `reqreturn`
|
||||
|
||||
#### Шаг 9: authreturn — Совет авторизует возврат
|
||||
|
||||
- **Исполнитель:** Совет кооператива
|
||||
- **Подписываемые документы:** Решение совета об авторизации возврата
|
||||
- **Проводки:** Нет
|
||||
- **Статус заявки:** `retauthorized`
|
||||
|
||||
#### Шаг 10: receive — Председатель КУ передаёт имущество
|
||||
|
||||
- **Исполнитель:** Председатель КУ
|
||||
- **Подписываемые документы:** Акт приёма-передачи (receive_act)
|
||||
- **Проводки:** Нет
|
||||
- **Статус заявки:** `received1`
|
||||
|
||||
#### Шаг 11: receivecnf — Заказчик подтверждает получение
|
||||
|
||||
- **Исполнитель:** Заказчик
|
||||
- **Подписываемые документы:** Акт подтверждения получения (receive_act_conf)
|
||||
- **Проводки по кошелькам:**
|
||||
- Списать заблокированный баланс заказчика `total_cost` из ЦПП «Маркетплейс»
|
||||
- **Проводки по ledger:**
|
||||
- Уменьшить паевой фонд (счёт 80) на `base_cost`
|
||||
- **Статус заявки:** `received2`
|
||||
- **Эффект:** Устанавливается `warranty_delay_until` (текущее время + гарантийный срок)
|
||||
|
||||
#### Шаг 12: complete — Завершение после гарантии
|
||||
|
||||
- **Исполнитель:** Система (после истечения `warranty_delay_until`)
|
||||
- **Проводки по кошелькам:**
|
||||
- Списать заблокированный баланс поставщика `base_cost` из ЦПП «Маркетплейс»
|
||||
- Начислить поставщику `base_cost` в ЦПП «Цифровой Кошелёк»
|
||||
- **Статус:** Заявка удаляется из блокчейна
|
||||
|
||||
**Постусловия:**
|
||||
- Заказчик получил имущество
|
||||
- Поставщик получил средства на кошелёк
|
||||
- Членские взносы распределены по фондам кооператива
|
||||
|
||||
---
|
||||
|
||||
### Процесс 1Б: Обратная поставка (ORDER→OFFER)
|
||||
|
||||
**Предусловия:**
|
||||
- Заказчик создал карточку заказа в БД (статус: `published`, тип: `order`)
|
||||
- Поставщик нашёл заказ и готов поставить
|
||||
|
||||
**Последовательность шагов:**
|
||||
|
||||
#### Шаг 1: createorder — Заказчик публикует заказ
|
||||
|
||||
- **Предусловие:** Карточка заказа в БД. При публикации — **match** в блокчейн
|
||||
- **Проводки:** Списание + блокировка `total_cost` заказчика (аналогично orderoffer)
|
||||
- **Статус:** `active`
|
||||
|
||||
#### Шаг 2: respondoffer — Поставщик откликается
|
||||
|
||||
- **Исполнитель:** Поставщик
|
||||
- **Подписываемые документы:** Заявление на взнос + конвертация
|
||||
- **Эффект:** Создаётся заявление в совет `authcontrib`
|
||||
- **Статус предложения:** `accepted`
|
||||
|
||||
#### Шаги 3-12: Аналогичны Процессу 1А (authcontrib → complete)
|
||||
|
||||
---
|
||||
|
||||
### Процесс 1В: Из запасов кооператива (COOPSTOCK)
|
||||
|
||||
**Предусловия:**
|
||||
- Имущество уже на балансе кооператива (на складе КУ)
|
||||
- Председатель КУ создаёт предложение coopstock
|
||||
|
||||
**Последовательность шагов:**
|
||||
|
||||
#### Шаг 1: coopstock — Создание предложения
|
||||
|
||||
- **Исполнитель:** Председатель КУ
|
||||
- **Проводки:** Нет (имущество уже на балансе)
|
||||
- **Статус:** `delivered` (сразу готово к выдаче)
|
||||
|
||||
#### Шаг 2: acceptstock — Заказчик принимает
|
||||
|
||||
- **Исполнитель:** Заказчик
|
||||
- **Подписываемые документы:** Конвертация + заявление на возврат
|
||||
- **Проводки:** Блокировка `total_cost` заказчика
|
||||
- **Эффект:** Сразу создаётся заявление в совет `authreturn`
|
||||
- **Статус:** `reqreturn`
|
||||
|
||||
#### Шаги 3-6: authreturn → receive → receivecnf → complete (аналогично 1А шаги 9-12)
|
||||
|
||||
**Особенности:**
|
||||
- Пропущены шаги accept, authcontrib, supply, supplcnf — не нужны
|
||||
- Нет проводок по паевому фонду при поставке (имущество уже на балансе)
|
||||
|
||||
---
|
||||
|
||||
### Процесс 2: Гарантийный возврат
|
||||
|
||||
**Предусловия:**
|
||||
- Заявка в статусе `received2` (имущество получено)
|
||||
- Не истёк `warranty_delay_until`
|
||||
- Заказчик обнаружил дефект
|
||||
|
||||
**Последовательность шагов:**
|
||||
|
||||
#### Шаг 1: dispute — Заказчик подаёт претензию
|
||||
|
||||
- **Подписываемые документы:** Претензия (wdispute) + фото/видео
|
||||
- **Эффект:** Деньги поставщика дополнительно блокируются
|
||||
|
||||
#### Шаг 2: Рассмотрение на КУ (вне контракта)
|
||||
|
||||
- **Исполнитель:** Председатель КУ
|
||||
- **Действия:** Осмотр, подтверждение/отклонение претензии
|
||||
|
||||
#### Шаг 3: wauthorize — Совет авторизует возврат
|
||||
|
||||
- **Подписываемые документы:** Решение о возврате (wreturn_auth) + решение о выдаче поставщику (wsupply_auth)
|
||||
|
||||
#### Шаг 4: wreturn — Возврат имущества в кооператив
|
||||
|
||||
- **Проводки:** Разблокировка средств заказчика, начисление на кошелёк
|
||||
|
||||
#### Шаг 5: woffer — Предложение имущества поставщику
|
||||
|
||||
#### Шаг 6: waccept — Поставщик принимает/отказывается
|
||||
|
||||
**Альтернативные потоки:**
|
||||
- **Отклонение претензии:** Заказчик забирает имущество обратно
|
||||
- **Поставщик не забрал:** Имущество перепредлагается (→ reoffer) или уничтожается (→ destroy)
|
||||
|
||||
---
|
||||
|
||||
### Процесс 3: Уничтожение имущества
|
||||
|
||||
**Предусловия:**
|
||||
- Заявка в статусе `delivered` или `supplied2`
|
||||
- Истёк `deadline_for_receipt` (заказчик не пришёл)
|
||||
|
||||
**Шаг 1: destroy — Уничтожение**
|
||||
|
||||
- **Исполнитель:** Председатель КУ (chairman)
|
||||
- **Подписываемые документы:** Акт уничтожения (destruction_act)
|
||||
- **Проводки:**
|
||||
- Возврат заказчику: `total_cost - cancellation_fee` → разблокировать и вернуть в кошелёк
|
||||
- Штраф `cancellation_fee` → в фонд членских взносов (spreadamount)
|
||||
- Поставщику: `base_cost` → разблокировать и вернуть в кошелёк
|
||||
- **Эффект:** Заявка удаляется из блокчейна
|
||||
|
||||
---
|
||||
|
||||
### Процесс 3Б: Перепредложение (reoffer)
|
||||
|
||||
**Предусловия:**
|
||||
- Аналогичны destroy, но срок годности НЕ истёк
|
||||
|
||||
**Шаг 1: reoffer — Перепредложение по новой цене**
|
||||
|
||||
- **Исполнитель:** Председатель КУ
|
||||
- **Проводки:** Аналогичны destroy (возврат средств)
|
||||
- **Эффект:** Старая заявка удаляется, создаётся новая типа `coopstock` со статусом `delivered`
|
||||
|
||||
---
|
||||
|
||||
### Процесс 4: Транспортировка (Shipment)
|
||||
|
||||
**Предусловия:**
|
||||
- Имущество на складе КУ (статус `supplied2` или `shiprecvd`)
|
||||
- Нужно доставить на другой КУ
|
||||
|
||||
**Последовательность шагов:**
|
||||
|
||||
#### Шаг 1: createship — Создание перевозки
|
||||
- **Исполнитель:** Представитель КУ отправителя
|
||||
- **Документ:** Акт передачи (shsendact)
|
||||
- **Статус перевозки:** `loading`
|
||||
|
||||
#### Шаг 2: signbydriver — Подпись водителя
|
||||
- **Исполнитель:** Водитель-пайщик
|
||||
- **Документ:** Акт приёма (shloadact)
|
||||
- **Эффект:** Товары снимаются со склада
|
||||
- **Статус:** `transit`
|
||||
|
||||
#### Шаг 3: arrived — Прибытие
|
||||
- **Исполнитель:** Водитель
|
||||
- **Документ:** Акт доставки (sharriveact)
|
||||
- **Статус:** `arrived`
|
||||
|
||||
#### Шаг 4: receiveshipm — Приём на складе
|
||||
- **Исполнитель:** Представитель КУ получателя
|
||||
- **Документ:** Акт приёма на складе (shrecvact)
|
||||
- **Эффект:** Товары ставятся на склад КУ назначения, перевозка удаляется
|
||||
- **Статус заявок:** `shiprecvd`
|
||||
|
||||
**Альтернативный поток:** retransport — промежуточная перегрузка на другой маршрут
|
||||
|
||||
---
|
||||
|
||||
### Связи между процессами
|
||||
|
||||
- **Процесс 1 → Процесс 4:** После `supplcnf` (шаг 5) товар может пойти на транспортировку перед `delivered`
|
||||
- **Процесс 1 → Процесс 2:** После `receivecnf` (шаг 11) возможен гарантийный возврат до истечения `warranty_delay_until`
|
||||
- **Процесс 2 → Процесс 3:** Если поставщик не забирает возвращённое имущество → destroy/reoffer
|
||||
- **Процесс 1 → Процесс 3:** Если заказчик не приходит за товаром → destroy/reoffer
|
||||
- **Процесс 3Б → Процесс 1В:** reoffer создаёт coopstock → новый цикл 1В
|
||||
|
||||
---
|
||||
|
||||
## Ключевой принцип: Match = Блокчейн
|
||||
|
||||
**Каждая встречная заявка сразу публикуется в блокчейн.**
|
||||
|
||||
Карточки хранятся в БД (draft → moderation → published).
|
||||
Но как только появляется встречная заявка — она немедленно уходит в блокчейн.
|
||||
Блокировка средств — дело смарт-контракта, не контроллера.
|
||||
|
||||
### Циклы поставки (min_units + cycle_deadline)
|
||||
|
||||
Циклы НЕ влияют на match. Они определяют:
|
||||
- **Когда поставщик начинает поставку** (supply) — при наборе min_units
|
||||
- **Когда цикл истекает** — cancel всех заявок через блокчейн → возврат средств
|
||||
|
||||
Поток:
|
||||
1. Карточка published, `min_units: 100`, `cycle_deadline: 7 февраля`
|
||||
2. Заказчик 1 → order на 30 ед. → **сразу в блокчейн** → средства заблокированы
|
||||
3. Заказчик 2 → order на 50 ед. → **сразу в блокчейн** → средства заблокированы
|
||||
4. Заказчик 3 → order на 25 ед. → **сразу в блокчейн** → средства заблокированы
|
||||
5. Итого 105 ≥ 100 → поставщику разрешено начать supply
|
||||
6. Если бы до 7 февраля набрали только 70 → cancel всех заявок → возврат через блокчейн → новый цикл
|
||||
|
||||
---
|
||||
|
||||
### Бизнес-правила и ограничения
|
||||
|
||||
**Правило 1: Блокировка средств при заказе**
|
||||
- **Описание:** Средства заказчика блокируются в момент создания заявки в блокчейне (match)
|
||||
- **Применение:** orderoffer, createorder, acceptstock
|
||||
- **Последствия нарушения:** Заявка не может быть создана без достаточных средств
|
||||
|
||||
**Правило 2: Одно заявление в совет при принятии**
|
||||
- **Описание:** При accept создаётся только authcontrib (на взнос). authreturn — перед получением
|
||||
- **Применение:** accept, reqreturn
|
||||
- **Обоснование:** Заявление на возврат содержит точные данные (вес), доступные после доставки
|
||||
|
||||
**Правило 3: Тип доставки определяет маршрут**
|
||||
- **Описание:** `delivery_type: internal` — между КУ через Shipment, `external` — через внешний сервис (СДЭК)
|
||||
- **Применение:** Определяется в карточке при создании
|
||||
|
||||
**Правило 4: Тип взноса определяет проводки**
|
||||
- **Описание:** `contribution_type: share` — паевой взнос (возврат имуществом), `member` — членский взнос (кооператив покупает)
|
||||
- **Применение:** Влияет на проводки при complete
|
||||
|
||||
**Правило 5: Гарантийный период**
|
||||
- **Описание:** `warranty_delay_until = received_at + warranty_period_secs`. До истечения — complete невозможен, dispute возможен
|
||||
- **Применение:** complete, dispute
|
||||
|
||||
**Правило 6: Карточки в БД, заявки в блокчейне**
|
||||
- **Описание:** Карточки хранятся в PostgreSQL (draft → moderation → published). Каждая встречная заявка сразу публикуется в блокчейн — средства блокируются смарт-контрактом
|
||||
- **Применение:** MatchService при каждой встречной заявке
|
||||
|
||||
**Правило 7: Циклы — порог для supply, не для match**
|
||||
- **Описание:** min_units определяет когда поставщику разрешено начать supply. cycle_deadline — когда cancel при ненаборе. Каждый заказ сразу в блокчейн независимо от цикла
|
||||
- **Применение:** CycleService (cron каждые 5 мин)
|
||||
@@ -1,55 +1,120 @@
|
||||
# MONO
|
||||
Система управления взаимоотношениями в кооперативе. Включает в себя полный комплект программного обеспечения для подключения к платформе "Кооперативная Экономика" и управления взаимоотношениями с пайщиками в кооперативе и самим кооперативом на основе смарт-контрактов и простой электронной подписи.
|
||||
# Цифровой Кооператив
|
||||
|
||||
## Ветки
|
||||
- Для разработки используется ветка `dev`.
|
||||
- Для стейджа используется ветка `testnet`.
|
||||
- Для продакшена используется ветка `main`.
|
||||
<!-- badges -->
|
||||

|
||||

|
||||

|
||||
|
||||
## Установка
|
||||
```
|
||||
Платформа «Цифровой Кооператив» — комплексное программное обеспечение для управления кооперативными организациями на основе блокчейна EOSIO. Система обеспечивает полный цикл управления кооперативом: от регистрации пайщиков и электронного документооборота до проведения собраний и финансового учёта. Построена на принципах прозрачности, децентрализации и простой электронной подписи.
|
||||
|
||||
Проект является частью экосистемы [Кооперативная Экономика](https://coopenomics.world).
|
||||
|
||||
## Архитектура
|
||||
|
||||
| Компонент | Пакет | Описание |
|
||||
|-----------|-------|----------|
|
||||
| [boot](components/boot) | `@coopenomics/boot` | CLI для инициализации и управления блокчейн-инфраструктурой |
|
||||
| [cleos](components/cleos) | `@coopenomics/cleos` | Утилита командной строки для работы с блокчейн-кошельком |
|
||||
| [contracts](components/contracts) | `@coopenomics/contracts` | Смарт-контракты EOSIO на C++ |
|
||||
| [controller](components/controller) | `@coopenomics/controller` | GraphQL API сервер (NestJS) |
|
||||
| [cooptypes](components/cooptypes) | `cooptypes` | Общие типы и интерфейсы блокчейн-контрактов |
|
||||
| [desktop](components/desktop) | `@coopenomics/desktop` | Рабочий стол кооператива (Vue 3 + Quasar) |
|
||||
| [factory](components/factory) | `@coopenomics/factory` | Генератор юридических документов |
|
||||
| [migrator](components/migrator) | `migrator` | Утилита миграции данных |
|
||||
| [notifications](components/notifications) | `@coopenomics/notifications` | Библиотека уведомлений на основе Novu |
|
||||
| [parser](components/parser) | `@coopenomics/parser` | Индексатор блокчейна через State History Plugin |
|
||||
| [sdk](components/sdk) | `@coopenomics/sdk` | TypeScript SDK для GraphQL API |
|
||||
| [setup](components/setup) | `@coopenomics/setup` | Мастер первоначальной настройки |
|
||||
|
||||
## Быстрый старт
|
||||
|
||||
### Предварительные требования
|
||||
|
||||
- Node.js >= 20
|
||||
- pnpm 9
|
||||
- Docker и Docker Compose
|
||||
- [WeasyPrint](https://doc.courtbouillon.org/weasyprint/stable/first_steps.html#installation) (для генерации PDF)
|
||||
|
||||
### Установка
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
```
|
||||
|
||||
## Конфигурация
|
||||
Для конфигурации потребуетcя установленный `docker` и `docker-compose`. Выполните команду и следуйте инструкциям:
|
||||
### Конфигурация
|
||||
|
||||
```
|
||||
```bash
|
||||
pnpm run setup
|
||||
```
|
||||
|
||||
## Запуск
|
||||
Интерактивный мастер создаст необходимые `.env` файлы для всех компонентов.
|
||||
|
||||
### Инфраструктура
|
||||
Для работы системы необходимы базы `mongo`, `postgres` и `redis`.
|
||||
### Запуск инфраструктуры
|
||||
|
||||
Команда запуска баз данных в контейнерах для работы в фоновом режиме:
|
||||
```
|
||||
```bash
|
||||
docker compose up -d
|
||||
pnpm run reboot
|
||||
```
|
||||
|
||||
### Блокчейн и контракты
|
||||
```
|
||||
pnpm boot
|
||||
```
|
||||
Команда запустит блокчейн и установит все смарт-контракты, подготовив систему к локальной разработке или применению.
|
||||
## Разработка
|
||||
|
||||
### Бэкенд
|
||||
```
|
||||
### Бэкенд (controller + parser)
|
||||
|
||||
```bash
|
||||
pnpm run dev:backend
|
||||
```
|
||||
Команда запустит набор сервисов бэкенда и точку доступа GraphQL-API на 2998 порту.
|
||||
|
||||
### Фронтенд
|
||||
Команда запустит рабочий стол кооператива на 3005 порту:
|
||||
### Фронтенд (desktop)
|
||||
|
||||
```
|
||||
```bash
|
||||
pnpm run dev:desktop
|
||||
```
|
||||
|
||||
### Библиотеки (factory + cooptypes)
|
||||
|
||||
```bash
|
||||
pnpm run dev:lib
|
||||
```
|
||||
|
||||
### Все сервисы одновременно
|
||||
|
||||
```bash
|
||||
pnpm run dev:all
|
||||
```
|
||||
|
||||
> **Примечание:** установка пакетов производится только через фильтр: `pnpm add <пакет> --filter <компонент>`
|
||||
|
||||
## Тестирование
|
||||
|
||||
```bash
|
||||
# Все тесты
|
||||
pnpm run test
|
||||
|
||||
# Юнит-тесты (cooptypes, parser, notifications)
|
||||
pnpm run test:unit
|
||||
|
||||
# Компонентные тесты (factory)
|
||||
pnpm run test:component
|
||||
|
||||
# Интеграционные тесты (boot + blockchain)
|
||||
pnpm run test:integration
|
||||
```
|
||||
|
||||
## Сборка
|
||||
|
||||
```bash
|
||||
# Библиотеки (cooptypes, factory)
|
||||
pnpm run build:lib
|
||||
|
||||
# Смарт-контракты
|
||||
pnpm run build:contracts:all
|
||||
|
||||
# Desktop (SSR)
|
||||
pnpm --filter @coopenomics/desktop run build
|
||||
```
|
||||
|
||||
## Лицензия
|
||||
Продукт Потребительского Кооператива "ВОСХОД" распространяется по лицензии BY-NC-SA 4.0.
|
||||
|
||||
Разрешено делиться, копировать и распространять материал на любом носителе и форме, адаптировать, делать ремиксы, видоизменять и создавать новое, опираясь на этот материал. При использовании, Вы должны обеспечить указание авторства, предоставить ссылку, и обозначить изменения, если таковые были сделаны. Если вы перерабатываете, преобразовываете материал или берёте его за основу для производного произведения, вы должны распространять переделанные вами части материала на условиях той же лицензии , в соответствии с которой распространяется оригинал. Запрещено коммерческое использование материала. Использование в коммерческих целях – это использование, в первую очередь направленное на получение коммерческого преимущества или денежного вознаграждения.
|
||||
Продукт Потребительского Кооператива «ВОСХОД» распространяется по лицензии [BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode.ru).
|
||||
|
||||
Юридический текст лицензии: https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode.ru
|
||||
Разрешено делиться, копировать и распространять материал, адаптировать и создавать производные произведения при условии указания авторства и сохранения той же лицензии. Коммерческое использование запрещено.
|
||||
|
||||
+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 типы
|
||||
@@ -0,0 +1,181 @@
|
||||
# TASKS.md — Прогресс выполнения задач
|
||||
|
||||
## Завершённые задачи
|
||||
- ✅ 1. Dev-окружение
|
||||
- ✅ 2. Security updates
|
||||
- ✅ 3. Unified test pipeline (162/163)
|
||||
- ✅ 4. README + описания компонентов
|
||||
- ✅ 5. AGENTS.md для всех компонентов
|
||||
- ✅ 6. Setup — профессиональный установщик
|
||||
- ✅ 7. Поисковая система документов (OpenSearch)
|
||||
- ✅ 8. Процессы (Capital extension) — бэкенд + фронтенд
|
||||
- ✅ 9. Отчёты ФНС — 8 генераторов, фабрика, GraphQL API
|
||||
|
||||
---
|
||||
|
||||
## Активные задачи
|
||||
|
||||
### 10. Генерация отчётов ФНС (доработка) ✅
|
||||
- [x] Фабрика генераторов (ReportRegistryService)
|
||||
- [x] 8 генераторов (Бухбаланс, 6-НДФЛ, РСВ, ПСВ, ДУСН, 4-ФСС, Увед. взносы, УУСН)
|
||||
- [x] GraphQL API (getAvailableReports, generateReport)
|
||||
- [x] Генераторы переписаны по XSD — структура соответствует схемам ФНС
|
||||
- [x] 48 unit-тестов для всех генераторов
|
||||
- [x] Desktop UI (страница отчётов) — расширение reports
|
||||
- [x] Интеграция с реальными данными ledger через LedgerInteractor
|
||||
- [x] OrganizationDataInput DTO для передачи данных организации
|
||||
|
||||
### 11. CASL + гранулированные права доступа + шаринг страниц
|
||||
|
||||
#### 11.1 CASL — система прав доступа ✅
|
||||
- [x] @casl/ability установлен
|
||||
- [x] Action enum: manage, read, create, update, delete, execute, share
|
||||
- [x] Subject enum: 30+ subjects для всех ресурсов
|
||||
- [x] CaslAbilityFactory: role-based + granular permissions
|
||||
- [x] CaslGuard + @CheckAbility decorator (обратная совместимость с RolesGuard)
|
||||
- [x] role в user сохранено (chairman, member, user)
|
||||
- [x] Тесты: 22/22 unit-тестов (chairman, member, user, granular permissions)
|
||||
|
||||
#### 11.2 Шаринг страниц ✅
|
||||
- [x] ShareTokenEntity с JWT, guest/member targets
|
||||
- [x] ShareService: create/verify/revoke
|
||||
- [x] GraphQL: createShareLink, revokeShareLink, getMyShareLinks, getSharedWithMe
|
||||
- [x] Два уровня: гости (linkName) и пайщики (targetUsername)
|
||||
- [x] Granular allowedActions per share link
|
||||
- [x] Кнопка "Поделиться" в Desktop Header (ShareHeaderAction)
|
||||
- [x] Диалог управления правами (ShareDialog)
|
||||
- [x] Страница "Доступные мне" на рабочем столе Пайщика (SharedWithMePage)
|
||||
|
||||
#### 11.3 Интеграция в desktop ✅
|
||||
- [x] useShareButtonProcess: авто-регистрация на всех страницах
|
||||
- [x] ShareButton + ShareDialog компоненты
|
||||
- [x] Интеграция в init-app process
|
||||
- [x] Валидация share tokens в route guard (frontend) — bypass ролей при наличии share_token
|
||||
|
||||
### 12. API ключи кооператива ✅
|
||||
- [x] ApiKeyEntity: хеш ключа (sha256), префикс, операции, срок
|
||||
- [x] ApiKeyService: create, validate, list, revoke
|
||||
- [x] ApiKeyGuard: аутентификация через x-api-key header
|
||||
- [x] GraphQL: createApiKey, getApiKeys, revokeApiKey (chairman only)
|
||||
- [x] Безопасность: ключ показывается ТОЛЬКО при создании, хранится хеш
|
||||
- [x] Desktop: ApiKeysPage в chairman extension
|
||||
- [x] UI: таблица ключей, создание, отзыв, копирование
|
||||
|
||||
### 15. Стол заказов (Marketplace) — полная реализация
|
||||
|
||||
#### 15.1 Смарт-контракт marketplace — реорганизация
|
||||
- [x] Исправить процесс: одно заявление в совет при accept (authcontrib), не два
|
||||
- [x] Новый action requestreturn — заявление на возврат подаётся перед получением
|
||||
- [x] authcontrib сразу ставит authorized
|
||||
- [x] authreturn работает для статуса reqreturn → retauthorized
|
||||
- [x] receive требует retauthorized (после авторизации возврата)
|
||||
- [x] orderoffer убран product_return_statement (подаётся позже)
|
||||
- [x] Путь coopstock — имущество уже в кооперативе
|
||||
- [x] acceptstock — заказчик принимает coopstock (сразу в requestreturn)
|
||||
- [x] Document::remove_document утилита
|
||||
- [x] Уничтожение просроченного (destroy) — возврат средств, штраф, выплата поставщику
|
||||
- [x] Перепредложение (reoffer) — закрытие старой заявки, создание coopstock с новой ценой
|
||||
- [x] Система перевозок — уже реализована (shipment/)
|
||||
- [x] Гарантийный возврат — уже реализован (dispute_on_offer/)
|
||||
- [x] Сборка marketplace.wasm + marketplace.abi (test mode) — OK
|
||||
- [x] ORDER→OFFER: createorder + respondoffer (заказчик публикует → поставщики откликаются)
|
||||
- [x] delivery_type: internal (между КУ) / external (СДЭК и т.д.) в request struct
|
||||
- [x] contribution_type: share (паевой) / member (членский) в request struct
|
||||
- [x] Убраны ВСЕ any из controller marketplace (service, interactor)
|
||||
- [x] Импортировано 87 файлов marketplace extension из ветки marketplace
|
||||
- [x] Domain: 11 entities, 7 repositories, 5 services (БД-first архитектура)
|
||||
- [x] Application: 30+ DTOs, 4 resolvers (категории, атрибуты, заявки)
|
||||
- [x] Infrastructure: TypeORM entities, mappers, adapters
|
||||
- [x] Архитектурный принцип: заявки в БД (draft→published), блокчейн при match
|
||||
- [x] Накопительный характер заявок — CycleService (canStartSupply, checkExpiredCycles, cron)
|
||||
|
||||
#### 15.2 Генерация типов
|
||||
- [x] Интерфейсы cooptypes: RequestReturn, Coopstock, AcceptStock, Destroy, Reoffer
|
||||
- [x] ABI marketplace сохранён (31 action) для генерации типов
|
||||
|
||||
#### 15.3 Controller (бэкенд)
|
||||
- [x] CooplaceBlockchainPort: 5 новых портов
|
||||
- [x] CooplaceBlockchainAdapter: 5 реализаций (reqReturn, coopstock, acceptStock, destroy, reoffer)
|
||||
- [x] DTOs: ReqReturnInput, CoopstockInput, AcceptStockInput, DestroyRequestInput, ReofferRequestInput
|
||||
- [x] GraphQL resolvers: 5 новых мутаций с авторизацией
|
||||
- [x] Service + Interactor: проброс до blockchain
|
||||
- [x] ShipmentResolver: createShipment, signByDriver, arrived, receiveShipment
|
||||
- [x] Интеграция с документами — через SignedDigitalDocumentInputDTO
|
||||
- [x] MarketplaceEventService: 13 event handlers для синхронизации с блокчейном
|
||||
|
||||
#### 15.4 Desktop (фронтенд)
|
||||
- [x] Маршруты: витрина, предложение, создание, мои заказы, модерация
|
||||
- [x] install.ts расширения market обновлён с agreementsBase и roles
|
||||
- [x] ReqReturnStep — шаг подачи заявления на возврат (step 7)
|
||||
- [x] RetAuthorizedStep — шаг авторизации возврата советом (step 8)
|
||||
- [x] Base.vue: новые статусы (reqreturn, retauthorized), badge coopstock
|
||||
- [x] Панель председателя КУ: WarehousePage (склад, выдача, destroy/reoffer)
|
||||
- [x] ShipmentsPage: таблица перевозок, timeline 4 этапов, создание
|
||||
- [x] DisputePage: подача претензии, список, timeline спора, решение совета
|
||||
|
||||
#### 15.X Разделение cooplace на бэкенд и фронтенд расширения
|
||||
- [x] CooplaceExtensionModule — бэкенд (карточки, категории, blockchain actions)
|
||||
- [x] ProductCardResolver: CRUD карточек (draft→published→archived)
|
||||
- [x] CategoryResolver: CRUD дерева категорий (chairman)
|
||||
- [x] Domain entities: ProductCard (тип, статус, delivery_type, contribution_type), Category, SupplyOrder
|
||||
- [x] Repositories: interfaces для карточек, категорий, заявок
|
||||
- [x] GraphQL: 3 queries + 6 mutations для карточек/категорий
|
||||
- [x] marketplace (desktop) — чистый фронтенд, использует API cooplace
|
||||
- [x] market-admin (desktop) — уже существует (extensions/market-admin)
|
||||
- [x] Controller стартует, 22+9 = 31 GraphQL endpoints
|
||||
|
||||
#### 15.Y Бэкенд карточек с циклами
|
||||
- [x] TypeORM entities: ProductCard (циклы), Category, SupplyOrder
|
||||
- [x] Repository adapters: CRUD + фильтры для всех entities
|
||||
- [x] ProductCardService: модерация (draft→moderation→published), циклы (min_units, deadline)
|
||||
- [x] addOrderToCard: инкремент cycle_collected_units, проверка min_units
|
||||
- [x] checkCycleDeadline: истечение → возврат → новый цикл (cycle_number++)
|
||||
- [x] ProductCardResolver: 9 queries/mutations с реальной БД логикой
|
||||
- [x] CooplaceExtensionModule: полная DI с TypeORM
|
||||
- [x] Исправлен белый экран desktop (vite-plugin-checker + share-button crash)
|
||||
|
||||
#### 15.Z Админка маркетплейса (настройки)
|
||||
- [x] MarketplaceSettingsEntity: lead_request_policy, publish_access_policy, whitelist, moderation, cycles, delivery, prices
|
||||
- [x] MarketplaceSettingsTypeormEntity + TypeORM repository (upsert)
|
||||
- [x] MarketplaceSettingsResolver: get/update settings, add/remove whitelist
|
||||
- [x] Перенесены ВСЕ DTOs/entities/repositories/services из extensions/marketplace → extensions/cooplace
|
||||
- [x] Desktop SettingsPage: radio groups, toggles, whitelist chips, price inputs
|
||||
- [x] market-admin: маршрут настроек как дефолтный + модерация + все заказы
|
||||
|
||||
#### 15.W Правильная логика match + cycles
|
||||
- [x] MatchService: каждая встречная заявка → сразу в блокчейн (блокировка средств смарт-контрактом)
|
||||
- [x] CycleService: min_units/deadline → порог для supply, НЕ для match
|
||||
- [x] CycleService: cron каждые 5 мин — проверка истёкших циклов → cancel через блокчейн
|
||||
- [x] MARKET-LOGIC.md обновлён с правильным принципом
|
||||
- [x] AGENTS.md обновлён с описанием маркетплейса
|
||||
|
||||
#### 15.5 Тесты
|
||||
- [x] Unit-тесты controller: 8 тестов marketplace actions + statuses (90/90 total)
|
||||
- [x] cooptypes собран с новыми actions
|
||||
- [x] Boot интеграционный тест: marketplace.test.ts (orderoffer + coopstock flows)
|
||||
- [x] Boot тест marketplace.test.ts: orderoffer + coopstock flows
|
||||
|
||||
### 16. Документация (docs)
|
||||
- [x] Раздел Cooplace: обзор архитектуры, жизненный цикл карточки, match, циклы
|
||||
- [x] Раздел Cooplace/dev: GraphQL API reference (настройки, карточки, поставки, перевозки, диспуты)
|
||||
- [x] Раздел Marketplace: пользовательская документация (витрина, заказ, администрирование)
|
||||
- [x] TypeDoc JSON сгенерирован из SDK (137MB)
|
||||
- [x] mkdocs.yml nav обновлён с «Стол заказов»
|
||||
- [x] .gitignore для typedoc.json (генерируется при сборке)
|
||||
|
||||
### 13. GraphQL Subscriptions ✅
|
||||
- [x] PubSubModule: глобальный PubSub provider
|
||||
- [x] WebSocket support в GraphQL module (graphql-ws)
|
||||
- [x] 5 Capital subscriptions: issueUpdated/Created, commitCreated/Updated, dataChanged
|
||||
- [x] systemStatusChanged — подписка на статус системы (install/init/update)
|
||||
- [x] sovietDataChanged — подписка на события собраний/решений
|
||||
- [x] Events publishing: GenerationService, ProjectManagementService, VotingService, SystemService, MeetEventService
|
||||
- [x] SDK: Subscriptions namespace (Capital)
|
||||
- [x] Auth через connectionParams.token
|
||||
- [x] `useGraphqlSubscription` composable на фронтенде
|
||||
- [x] **7 страниц Capital** переведены с polling на подписки
|
||||
- [x] **System store** — WebSocket мониторинг вместо setTimeout
|
||||
- [x] **init-wallet** — удалён рекурсивный setTimeout(run, 10_000)
|
||||
- [x] **MeetDetails, ListOfAgenda, WaitingRegistration** — подписки вместо setInterval
|
||||
- [x] **Provider, ConnectionAgreement** — удалён setInterval polling
|
||||
- [x] 0 использований setInterval в прикладном коде (только UI-анимация энергии)
|
||||
@@ -0,0 +1,64 @@
|
||||
# Test Plan — pnpm run test
|
||||
|
||||
## Архитектура
|
||||
```
|
||||
pnpm run test
|
||||
├── cooptypes — vitest run (smoke tests exports) ✅ 4/4
|
||||
├── parser — vitest run (config smoke tests) ✅ 3/3
|
||||
├── factory — vitest run (document generation tests) 🔧 17/85 → need mocks
|
||||
├── sdk — vitest run (API integration tests) 🔧 TODO
|
||||
├── notifications — vitest run (workflow tests) 🔧 TODO
|
||||
├── controller — jest / vitest (NestJS unit tests) 🔧 TODO
|
||||
└── boot — vitest run (blockchain integration) 🔧 53/60
|
||||
```
|
||||
|
||||
## Статус по компонентам
|
||||
|
||||
### cooptypes ✅ DONE
|
||||
- 4 smoke-теста экспортов
|
||||
- Не требует инфраструктуры
|
||||
|
||||
### parser ✅ DONE
|
||||
- 3 smoke-теста конфигурации
|
||||
- Не требует инфраструктуры
|
||||
|
||||
### factory 🔧 IN PROGRESS
|
||||
- **Проблема**: тесты обращаются к parser API (`SIMPLE_EXPLORER_API`) для get-tables/get-actions
|
||||
- **Решение**: Уже есть мок-система в `src/Utils/testMocks.ts` + `matchMock.ts`
|
||||
- **Нужно**: Добавить моки для ВСЕХ документов:
|
||||
- [ ] cooperative data mock (registrator.coops table)
|
||||
- [ ] soviet boards mock — уже есть через test setup в MongoDB
|
||||
- [ ] draft templates mock (draft.drafts + draft.translations tables)
|
||||
- [ ] decision data mocks (soviet.decisions table)
|
||||
- [ ] Мок для ReturnByMoney документов
|
||||
- [ ] Все документы 1000+ серии
|
||||
- **Текущие рабочие моки**: meet tables, votefor actions, returnByMoneyDecision actions
|
||||
- **После мокирования**: все 85 тестов должны проходить
|
||||
|
||||
### boot 🔧 NEEDS REBOOT
|
||||
- capital.test — 53/60 тестов проходят (после чистого reboot)
|
||||
- wallet.test — нужен полный boot с agreements
|
||||
- registrator.test — нужен полный boot
|
||||
- capital-import.test — отдельный тест импорта
|
||||
- **Требует**: pnpm run reboot перед запуском
|
||||
|
||||
### sdk 🔧 TODO
|
||||
- 1 тест файл с login + fetch extensions
|
||||
- Требует запущенный controller
|
||||
- Нужно: обновить chain_id, api_url, credentials
|
||||
|
||||
### controller 🔧 TODO
|
||||
- Все старые тесты удалены (устаревшие)
|
||||
- NestJS-приложение — нужны тесты через @nestjs/testing
|
||||
- Минимум: unit-тесты domain-логики, smoke-тест GraphQL API
|
||||
|
||||
### notifications 🔧 TODO
|
||||
- Нет тестов
|
||||
- Нужно: smoke-тесты workflow builder
|
||||
|
||||
### desktop — SKIP (нет тестов, UI-тестирование)
|
||||
|
||||
## Root script требования
|
||||
- `pnpm run test` — запускает ВСЕ тесты
|
||||
- fail-fast: если один пакет падает — весь pipeline падает
|
||||
- Последовательный запуск (не параллельный)
|
||||
File diff suppressed because one or more lines are too long
Vendored
BIN
Binary file not shown.
@@ -2,9 +2,14 @@ EOSIO_PUB_KEY=EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV
|
||||
EOSIO_PRV_KEY=5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3
|
||||
DATADIR=blockchain-data
|
||||
BPACCOUNT=eosio
|
||||
PASSWORD=PASSWORD
|
||||
PASSWORD=PW5JGe4WsTPGrjnMzkGd4wfVdzoHCEySgbyq2WBWGAxSfevbXqAG4
|
||||
SERVER_SECRET=SECRET
|
||||
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"
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
# @coopenomics/boot
|
||||
|
||||
## Назначение
|
||||
|
||||
CLI-утилита для инициализации блокчейна EOSIO и развёртывания кооператива. Выполняет полный цикл: запуск ноды → создание аккаунтов → активация фич → деплой контрактов → выпуск токенов → загрузка шаблонов → создание кооператива и совета.
|
||||
|
||||
## Структура
|
||||
|
||||
```
|
||||
src/
|
||||
├── index.ts — CLI точка входа (commander): boot, boot:clean, boot:extra,
|
||||
│ start, stop, clear, cleos, deploy, create-coop, unlock
|
||||
├── init/
|
||||
│ ├── booter.ts — Главные сценарии: boot(), bootClean(), bootExtra()
|
||||
│ ├── infra.ts — startInfra() — базовая инфраструктура блокчейна
|
||||
│ │ installInitialData() — начальные данные (пользователи, совет)
|
||||
│ │ installExtraData() — расширенные данные
|
||||
│ └── cooperative.ts — CooperativeClass — создание программ ЦПП, регистрация
|
||||
│ └── participant.ts — Логика регистрации пайщика
|
||||
├── blockchain/
|
||||
│ └── index.ts — Класс Blockchain — обёртка над eosjs для работы с EOSIO
|
||||
│ (создание аккаунтов, деплой контрактов, транзакции, токены)
|
||||
├── configs/
|
||||
│ ├── index.ts — Главная конфигурация (аккаунты, ключи, токен, эмиссия)
|
||||
│ ├── networks.ts — Настройки сети (протокол, хост, порт)
|
||||
│ ├── contracts.ts — Список контрактов и путей к их WASM/ABI файлам
|
||||
│ └── protocol_features/ — JSON-файлы фич для активации на блокчейне
|
||||
├── docker/ — Управление Docker-контейнером ноды блокчейна
|
||||
│ ├── container.ts, run.ts, stop.ts, find.ts, exec.ts, deploy.ts
|
||||
│ ├── health.ts — Проверка готовности ноды
|
||||
│ └── purge.ts — Очистка данных блокчейна и БД
|
||||
├── *-init.ts — Инициализация реляционной БД (пользователи, vault, статус, extensions)
|
||||
├── tests/ — Интеграционные тесты
|
||||
│ ├── capital.test.ts — Основной тест (полный цикл ЦПП)
|
||||
│ ├── wallet.test.ts — Тесты кошелька
|
||||
│ ├── registrator.test.ts — Тесты регистрации
|
||||
│ └── capital/ — Вспомогательные функции для тестов капитала
|
||||
└── utils/ — Утилиты (sleep, randomHash, randomUsername, и т.д.)
|
||||
```
|
||||
|
||||
## Порядок загрузки (boot flow)
|
||||
|
||||
1. **startInfra()** — основа:
|
||||
- Инициализация `Blockchain` с ключами
|
||||
- Создание системных аккаунтов (eosio.token, registrator, soviet, и т.д.)
|
||||
- Активация фич протокола (PREACTIVATE_FEATURE → остальные)
|
||||
- Деплой смарт-контрактов (eosio.boot → eosio.system → все остальные)
|
||||
- Создание и выпуск токенов
|
||||
- Загрузка шаблонов документов из `@coopenomics/factory` Registry
|
||||
- Настройка ресурсов (powerup)
|
||||
|
||||
2. **installInitialData()** — данные кооператива:
|
||||
- Сохранение организации, платёжных методов, физлиц в MongoDB
|
||||
- Сохранение переменных кооператива (vars)
|
||||
- Регистрация пользователей в блокчейне
|
||||
- Инициализация реляционной БД (пользователи, vault)
|
||||
- Создание совета (board)
|
||||
- Создание программ ЦПП (Благорост, маркетплейс)
|
||||
|
||||
3. **bootClean()** — минимальная загрузка (только инфра + программы, без данных)
|
||||
|
||||
4. **bootExtra()** — расширенная загрузка (5 членов совета, дополнительные пайщики)
|
||||
|
||||
## Режимы контрактов
|
||||
|
||||
- **test** — контракты собранные с флагом `test` (позволяют boot с 1 членом совета)
|
||||
- **production** — стандартные контракты (требуют кворум)
|
||||
|
||||
Пути к WASM/ABI файлам указаны в `configs/contracts.ts`, относительно `../contracts/build/contracts/`.
|
||||
|
||||
## Тесты
|
||||
|
||||
- Фреймворк: **Vitest**
|
||||
- Основной тест: `src/tests/capital.test.ts` — полный цикл ЦПП (создание программ, проектов, инвестирование, голосование, конвертация)
|
||||
- Требует: полностью загруженный блокчейн (`pnpm run reboot`), MongoDB, реляционную БД
|
||||
|
||||
### Запуск
|
||||
|
||||
```
|
||||
pnpm --filter @coopenomics/boot test
|
||||
```
|
||||
|
||||
### Важно: Duplicate transaction
|
||||
|
||||
EOSIO отклоняет транзакции с одинаковым хешем (TAPOS block + action data). При повторных вызовах одинаковых действий нужен `await sleep(500)` между ними.
|
||||
|
||||
## Скрипты package.json
|
||||
|
||||
| Скрипт | Описание |
|
||||
|--------|----------|
|
||||
| `boot` | Полная загрузка: инфра + данные + кооператив |
|
||||
| `boot:clean` | Чистая загрузка: только инфра + программы |
|
||||
| `boot:extra` | Расширенная загрузка: инфра + 5 членов совета |
|
||||
| `create-coop` | Создание кооператива после boot:clean |
|
||||
| `test` | Vitest (capital.test.ts, timeout 240s) |
|
||||
| `test:all` | Все тесты |
|
||||
| `clear` | Очистка блокчейна и перезапуск ноды |
|
||||
|
||||
## Зависимости от других компонентов
|
||||
|
||||
- `cooptypes` (workspace) — типы контрактов
|
||||
- `@coopenomics/factory` (workspace) — реестр шаблонов, сохранение данных в MongoDB
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.2.10](https://github.com/coopenomics/mono/compare/v2.2.9...v2.2.10) (2025-03-27)
|
||||
|
||||
**Note:** Version bump only for package @coopenomics/boot
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.2.9](https://github.com/coopenomics/mono/compare/v2.2.8...v2.2.9) (2025-03-12)
|
||||
|
||||
**Note:** Version bump only for package @coopenomics/boot
|
||||
|
||||
+100
-1
@@ -1 +1,100 @@
|
||||
# Загрузчик.
|
||||
# 🚀 @coopenomics/boot
|
||||
|
||||
CLI-утилита для инициализации блокчейн-инфраструктуры кооператива. Создаёт системные аккаунты, устанавливает смарт-контракты, настраивает токены и разворачивает кооперативную среду. Включает интеграционные тесты для основных подсистем платформы.
|
||||
|
||||
## Основные возможности
|
||||
|
||||
- Полный цикл развёртывания блокчейна EOSIO — от запуска ноды до создания кооператива
|
||||
- Установка системных и прикладных смарт-контрактов
|
||||
- Инициализация токенов и создание тестовых данных
|
||||
- Управление Docker-контейнерами блокчейн-ноды
|
||||
- Интеграционные тесты: паевые взносы (capital), кошелёк (wallet), регистрация участников (registrator)
|
||||
- Поддержка нескольких режимов загрузки: стандартный, чистый и расширенный
|
||||
|
||||
## Установка
|
||||
|
||||
Компонент является частью монорепозитория. Установка зависимостей из корня проекта:
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
```
|
||||
|
||||
Или только для этого компонента:
|
||||
|
||||
```bash
|
||||
pnpm install --filter @coopenomics/boot
|
||||
```
|
||||
|
||||
## Скрипты
|
||||
|
||||
| Скрипт | Команда | Описание |
|
||||
|--------|---------|----------|
|
||||
| `boot` | `pnpm run boot` | Запуск блокчейна и установка контрактов |
|
||||
| `boot:clean` | `pnpm run boot:clean` | Чистый запуск с полной переустановкой |
|
||||
| `boot:extra` | `pnpm run boot:extra` | Расширенная инициализация с дополнительными данными |
|
||||
| `deploy` | `pnpm run deploy` | Развёртывание контрактов на запущенной ноде |
|
||||
| `cli` | `pnpm run cli` | Интерактивный командный интерфейс (Commander.js) |
|
||||
| `clear` | `pnpm run clear` | Очистка данных блокчейна |
|
||||
| `start` | `pnpm run start` | Перезапуск ноды |
|
||||
| `stop` | `pnpm run stop` | Остановка ноды |
|
||||
| `test` | `pnpm run test` | Интеграционные тесты (capital) |
|
||||
| `test:all` | `pnpm run test:all` | Все интеграционные тесты |
|
||||
|
||||
## Конфигурация
|
||||
|
||||
Скопируйте `.env-example` в `.env` и настройте переменные окружения:
|
||||
|
||||
- Endpoint блокчейн-ноды (API)
|
||||
- Строка подключения к MongoDB
|
||||
- Строка подключения к реляционной БД
|
||||
- Приватные ключи для развёртывания контрактов
|
||||
|
||||
Подробное описание переменных — в файле `.env-example`.
|
||||
|
||||
## Тестирование
|
||||
|
||||
Перед запуском тестов необходима работающая инфраструктура (см. `docker-compose.yaml` в корне проекта):
|
||||
|
||||
```bash
|
||||
pnpm --filter @coopenomics/boot run test
|
||||
pnpm --filter @coopenomics/boot run test:all
|
||||
```
|
||||
|
||||
Тесты используют **Vitest** с таймаутом 240 секунд — это связано с ожиданием подтверждения блокчейн-транзакций.
|
||||
|
||||
## Архитектура
|
||||
|
||||
```
|
||||
src/
|
||||
├── index.ts # Точка входа CLI (Commander.js)
|
||||
├── init/ # Логика инициализации
|
||||
│ ├── booter.ts # Загрузчик блокчейна
|
||||
│ ├── cooperative.ts # Настройка кооператива
|
||||
│ ├── infra.ts # Инфраструктурная инициализация
|
||||
│ └── participant.ts # Создание участников
|
||||
├── blockchain/ # Взаимодействие с EOSIO (eosjs)
|
||||
├── docker/ # Управление Docker-контейнерами
|
||||
├── configs/ # Конфигурация ноды и протокол-фичи
|
||||
├── tests/ # Интеграционные тесты
|
||||
│ ├── capital.test.ts # Тесты паевых взносов
|
||||
│ ├── wallet.test.ts # Тесты кошелька
|
||||
│ └── registrator.test.ts # Тесты регистрации
|
||||
└── utils/ # Вспомогательные утилиты
|
||||
scripts/
|
||||
├── reboot.sh # Полный перезапуск
|
||||
├── clean_reboot.sh # Чистый перезапуск
|
||||
└── extra_reboot.sh # Расширенный перезапуск
|
||||
```
|
||||
|
||||
## Ключевые зависимости
|
||||
|
||||
- **eosjs** — взаимодействие с блокчейном EOSIO
|
||||
- **commander** — CLI-фреймворк
|
||||
- **dockerode** — управление Docker-контейнерами из Node.js
|
||||
- **mongoose** — работа с MongoDB
|
||||
- **pg** — работа с реляционной БД
|
||||
- **vitest** — тестирование
|
||||
|
||||
## Лицензия
|
||||
|
||||
[BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode.ru)
|
||||
|
||||
@@ -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,24 +1,35 @@
|
||||
{
|
||||
"name": "@coopenomics/boot",
|
||||
"type": "module",
|
||||
"version": "2.2.9",
|
||||
"version": "2026.2.22-2",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@9.0.6",
|
||||
"description": "",
|
||||
"description": "CLI-утилита инициализации блокчейна и кооператива",
|
||||
"author": "Alex Ant <dacom.dark.sun@gmail.com>",
|
||||
"license": "",
|
||||
"keywords": [],
|
||||
"scripts": {
|
||||
"enter": "./scripts/enter.sh",
|
||||
"cli": "esno src/index.ts",
|
||||
"deploy": "esno src/index.ts deploy",
|
||||
"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"
|
||||
"stop": "./scripts/stop.sh",
|
||||
"test": "vitest run src/tests/capital.test.ts --testTimeout=240000",
|
||||
"test:all": "vitest run --testTimeout=240000"
|
||||
},
|
||||
"dependencies": {
|
||||
"@coopenomics/factory": "workspace:*",
|
||||
"axios": "^1.6.8",
|
||||
"@types/pg": "^8.16.0",
|
||||
"axios": "^1.13.5",
|
||||
"chai": "^5.1.2",
|
||||
"commander": "^12.1.0",
|
||||
"cooptypes": "workspace:*",
|
||||
@@ -29,7 +40,8 @@
|
||||
"eosjs-ecc": "^4.0.7",
|
||||
"execa": "^9.5.2",
|
||||
"mocha": "^10.7.3",
|
||||
"mongoose": "^8.10.0"
|
||||
"mongoose": "^9.2.2",
|
||||
"pg": "^8.16.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@antfu/eslint-config": "^2.18.0",
|
||||
@@ -48,4 +60,4 @@
|
||||
"vite": "^5.2.11",
|
||||
"vitest": "^1.6.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 opensearch 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 opensearch
|
||||
|
||||
# Ждем готовности 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 "Перезапуск завершен!"
|
||||
@@ -4,6 +4,7 @@ import { JsSignatureProvider } from 'eosjs/dist/eosjs-jssig'
|
||||
import { Api, JsonRpc, Serialize } from 'eosjs'
|
||||
import {
|
||||
DraftContract,
|
||||
GatewayContract,
|
||||
RegistratorContract,
|
||||
SovietContract,
|
||||
SystemContract,
|
||||
@@ -14,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'
|
||||
|
||||
@@ -42,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 })
|
||||
}
|
||||
@@ -513,8 +515,8 @@ export default class Blockchain {
|
||||
console.log('Новый кооператив пре-иниализирован: ', params)
|
||||
}
|
||||
|
||||
// TODO change registerOrganization to registerCooperative
|
||||
async registerOrganization(
|
||||
// TODO change registerCooperative to registerCooperative
|
||||
async registerCooperative(
|
||||
params: RegistratorContract.Actions.RegisterCooperative.IRegisterCooperative,
|
||||
) {
|
||||
await this.update_pass_instance()
|
||||
@@ -573,7 +575,7 @@ export default class Blockchain {
|
||||
)
|
||||
}
|
||||
|
||||
async registerAccount2(
|
||||
async createAccount(
|
||||
params: RegistratorContract.Actions.CreateAccount.ICreateAccount,
|
||||
) {
|
||||
await this.update_pass_instance()
|
||||
@@ -586,7 +588,7 @@ export default class Blockchain {
|
||||
name: RegistratorContract.Actions.CreateAccount.actionName,
|
||||
authorization: [
|
||||
{
|
||||
actor: params.registrator,
|
||||
actor: params.coopname,
|
||||
permission: 'active',
|
||||
},
|
||||
],
|
||||
@@ -606,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: [
|
||||
@@ -618,13 +620,11 @@ export default class Blockchain {
|
||||
name: RegistratorContract.Actions.RegisterUser.actionName,
|
||||
authorization: [
|
||||
{
|
||||
actor: params.registrator,
|
||||
actor: params.coopname,
|
||||
permission: 'active',
|
||||
},
|
||||
],
|
||||
data: {
|
||||
...params,
|
||||
},
|
||||
data: params,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -637,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: [
|
||||
@@ -747,7 +807,7 @@ export default class Blockchain {
|
||||
name: SovietContract.Actions.Decisions.VoteFor.actionName,
|
||||
authorization: [
|
||||
{
|
||||
actor: params.member,
|
||||
actor: params.username,
|
||||
permission: 'active',
|
||||
},
|
||||
],
|
||||
@@ -800,36 +860,39 @@ export default class Blockchain {
|
||||
|
||||
async exec(params: SovietContract.Actions.Decisions.Exec.IExec) {
|
||||
await this.update_pass_instance()
|
||||
|
||||
await this.api.transact(
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
account: SovietContract.contractName.production,
|
||||
name: SovietContract.Actions.Decisions.Exec.actionName,
|
||||
authorization: [
|
||||
{
|
||||
actor: params.executer,
|
||||
permission: 'active',
|
||||
try {
|
||||
await this.api.transact(
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
account: SovietContract.contractName.production,
|
||||
name: SovietContract.Actions.Decisions.Exec.actionName,
|
||||
authorization: [
|
||||
{
|
||||
actor: params.executer,
|
||||
permission: 'active',
|
||||
},
|
||||
],
|
||||
data: {
|
||||
...params,
|
||||
},
|
||||
],
|
||||
data: {
|
||||
...params,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
blocksBehind: 3,
|
||||
expireSeconds: 30,
|
||||
},
|
||||
)
|
||||
|
||||
],
|
||||
},
|
||||
{
|
||||
blocksBehind: 3,
|
||||
expireSeconds: 30,
|
||||
},
|
||||
)
|
||||
}
|
||||
catch (e) {
|
||||
console.dir(e, { depth: null })
|
||||
}
|
||||
console.log('Решение исполнено: ', params)
|
||||
}
|
||||
|
||||
async joinCoop(
|
||||
params: RegistratorContract.Actions.JoinCooperative.IJoinCooperative,
|
||||
async ConfirmPayment(
|
||||
params: GatewayContract.Actions.CompleteIncome.ICompleteIncome,
|
||||
) {
|
||||
await this.update_pass_instance()
|
||||
|
||||
@@ -837,11 +900,11 @@ export default class Blockchain {
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
account: RegistratorContract.contractName.production,
|
||||
name: RegistratorContract.Actions.JoinCooperative.actionName,
|
||||
account: GatewayContract.contractName.production,
|
||||
name: GatewayContract.Actions.CompleteIncome.actionName,
|
||||
authorization: [
|
||||
{
|
||||
actor: params.registrator,
|
||||
actor: params.coopname,
|
||||
permission: 'active',
|
||||
},
|
||||
],
|
||||
@@ -860,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()
|
||||
|
||||
@@ -919,7 +950,7 @@ export default class Blockchain {
|
||||
},
|
||||
)
|
||||
|
||||
console.log('Шаблон создан: ', params)
|
||||
console.log('Шаблон создан: ', params.registry_id)
|
||||
}
|
||||
|
||||
async createTranslation(
|
||||
@@ -951,7 +982,7 @@ export default class Blockchain {
|
||||
},
|
||||
)
|
||||
|
||||
console.log('Перевод создан: ', params)
|
||||
console.log('Перевод создан: ', params.registry_id)
|
||||
}
|
||||
|
||||
async createProgram(
|
||||
@@ -986,20 +1017,18 @@ export default class Blockchain {
|
||||
console.log('Программа установлена: ', params)
|
||||
}
|
||||
|
||||
async makeCoagreement(
|
||||
params: SovietContract.Actions.Agreements.MakeCoagreement.IMakeCoagreement,
|
||||
) {
|
||||
async createBoard(params: SovietContract.Actions.Boards.CreateBoard.ICreateboard) {
|
||||
await this.update_pass_instance()
|
||||
|
||||
await this.api.transact(
|
||||
console.dir(params, { depth: null })
|
||||
const result = await this.api.transact(
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
account: SovietContract.contractName.production,
|
||||
name: SovietContract.Actions.Agreements.MakeCoagreement.actionName,
|
||||
name: SovietContract.Actions.Boards.CreateBoard.actionName,
|
||||
authorization: [
|
||||
{
|
||||
actor: params.administrator,
|
||||
actor: params.coopname,
|
||||
permission: 'active',
|
||||
},
|
||||
],
|
||||
@@ -1015,6 +1044,38 @@ export default class Blockchain {
|
||||
},
|
||||
)
|
||||
|
||||
console.log('Программа установлена: ', params)
|
||||
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,13 +26,13 @@ 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
|
||||
trace-history = true
|
||||
state-history-endpoint = 0.0.0.0:8080
|
||||
state-history-endpoint = 0.0.0.0:8070
|
||||
resource-monitor-space-threshold = 99
|
||||
resource-monitor-not-shutdown-on-threshold-exceeded = true
|
||||
wasm-runtime = eos-vm
|
||||
|
||||
@@ -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'),
|
||||
@@ -75,4 +85,19 @@ export default [
|
||||
path: path.resolve(process.cwd(), '../contracts/build/contracts/capital'),
|
||||
target: 'capital',
|
||||
},
|
||||
{
|
||||
name: 'wallet',
|
||||
path: path.resolve(process.cwd(), '../contracts/build/contracts/wallet'),
|
||||
target: 'wallet',
|
||||
},
|
||||
{
|
||||
name: 'loan',
|
||||
path: path.resolve(process.cwd(), '../contracts/build/contracts/loan'),
|
||||
target: 'loan',
|
||||
},
|
||||
{
|
||||
name: 'meet',
|
||||
path: path.resolve(process.cwd(), '../contracts/build/contracts/meet'),
|
||||
target: 'meet',
|
||||
},
|
||||
]
|
||||
|
||||
@@ -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: [
|
||||
@@ -87,6 +87,26 @@ export default {
|
||||
name: 'capital',
|
||||
code_permissions_to: ['capital'],
|
||||
},
|
||||
{
|
||||
name: 'loan',
|
||||
code_permissions_to: ['loan'],
|
||||
},
|
||||
{
|
||||
name: 'ledger',
|
||||
code_permissions_to: ['ledger'],
|
||||
},
|
||||
{
|
||||
name: 'test',
|
||||
code_permissions_to: ['test'],
|
||||
},
|
||||
{
|
||||
name: 'meet',
|
||||
code_permissions_to: ['meet'],
|
||||
},
|
||||
{
|
||||
name: 'wallet',
|
||||
code_permissions_to: ['wallet'],
|
||||
},
|
||||
{
|
||||
name: 'soviet',
|
||||
code_permissions_to: ['soviet'],
|
||||
@@ -115,9 +135,9 @@ export default {
|
||||
name: 'contributor',
|
||||
code_permissions_to: ['contributor'],
|
||||
},
|
||||
{
|
||||
name: provider_chairman,
|
||||
},
|
||||
// {
|
||||
// name: provider_chairman,
|
||||
// },
|
||||
{
|
||||
name: provider,
|
||||
code_permissions_to: ['registrator'],
|
||||
|
||||
@@ -1,38 +1,25 @@
|
||||
import type { Container } from "dockerode"
|
||||
import { findContainerByName } from "./find"
|
||||
import { runContainer } from "./run"
|
||||
import { exec } from 'node:child_process'
|
||||
import { promisify } from 'node:util'
|
||||
|
||||
export async function execCommand(command: string[]): Promise<string> {
|
||||
const container = await runContainer(true)
|
||||
const execAsync = promisify(exec)
|
||||
|
||||
const exec = await container.exec({
|
||||
Cmd: command,
|
||||
AttachStdout: true,
|
||||
AttachStderr: true,
|
||||
})
|
||||
export async function execCommand(command: string[]): Promise<void> {
|
||||
const fullCmd = ['docker', 'compose', 'exec', 'node', ...command].join(' ')
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
exec.start({}, (err: any, stream: any) => {
|
||||
if (err) {
|
||||
return reject(err)
|
||||
}
|
||||
|
||||
let output = ""
|
||||
|
||||
// eslint-disable-next-line node/prefer-global/buffer
|
||||
stream.on("data", (data: Buffer) => {
|
||||
output += data.toString()
|
||||
})
|
||||
|
||||
stream.on("end", () => {
|
||||
output = output.replace(/^\d+\s*/, "")
|
||||
console.log(output)
|
||||
resolve(output)
|
||||
})
|
||||
|
||||
stream.on("error", (err: any) => {
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
})
|
||||
try {
|
||||
// eslint-disable-next-line node/prefer-global/process
|
||||
const { stdout, stderr } = await execAsync(fullCmd, { env: process.env })
|
||||
if (stdout)
|
||||
console.log(stdout.trim())
|
||||
if (stderr)
|
||||
console.error(stderr.trim())
|
||||
}
|
||||
catch (err: any) {
|
||||
if (err.stdout)
|
||||
console.log(err.stdout.trim())
|
||||
if (err.stderr)
|
||||
console.error(err.stderr.trim())
|
||||
// опционально:
|
||||
// console.error(`Command failed with code ${err.code}`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,18 @@ 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)
|
||||
|
||||
try {
|
||||
await mongoose.connection.collection('users').deleteMany({})
|
||||
console.log('Все документы удалены из коллекции users')
|
||||
}
|
||||
catch (e) {
|
||||
console.error('Ошибка при удалении:', e)
|
||||
}
|
||||
|
||||
try {
|
||||
await mongoose.connection.collection('sync').deleteMany({})
|
||||
console.log('Все документы удалены из коллекции sync')
|
||||
@@ -29,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> {
|
||||
|
||||
+119
-21
@@ -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'
|
||||
@@ -31,17 +32,11 @@ program.version('0.1.0')
|
||||
|
||||
// Команда для запуска команды в контейнере
|
||||
program
|
||||
.command('cleos <cmd...>')
|
||||
.command('cleos [cmd...]')
|
||||
.description('Execute a cleos command in a Node container')
|
||||
.allowUnknownOption()
|
||||
.action(async (cmd: string[]) => {
|
||||
try {
|
||||
console.log(cmd)
|
||||
await execCommand(['cleos', ...cmd])
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Command execution failed:', error)
|
||||
}
|
||||
.action(async (cmd: string[] = []) => {
|
||||
await execCommand(['cleos', ...cmd])
|
||||
})
|
||||
|
||||
// Команда для запуска команды в контейнере
|
||||
@@ -54,14 +49,8 @@ program
|
||||
.action(async (cmd: string, options: any) => {
|
||||
try {
|
||||
const { target, network } = options
|
||||
await deleteFile(keosdPath)
|
||||
|
||||
await execCommand([
|
||||
'cleos',
|
||||
'wallet',
|
||||
'unlock',
|
||||
'--password',
|
||||
process.env.PASSWORD!,
|
||||
])
|
||||
await deployCommand(cmd, target, network)
|
||||
}
|
||||
catch (error) {
|
||||
@@ -126,15 +115,19 @@ program
|
||||
.action(async () => {
|
||||
try {
|
||||
await stopContainerByName('node')
|
||||
await clearDirectory(basePath)
|
||||
await clearDB()
|
||||
}
|
||||
catch (e) {
|
||||
console.log('Нет контейнера для остановки. Стартуем новый..')
|
||||
}
|
||||
|
||||
try {
|
||||
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
|
||||
@@ -144,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)
|
||||
}
|
||||
@@ -154,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,11 +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'
|
||||
@@ -14,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 {
|
||||
@@ -32,7 +42,7 @@ export class CooperativeClass {
|
||||
await this.blockchain.createProgram({
|
||||
coopname,
|
||||
username: coopname,
|
||||
type: 'cooplace',
|
||||
type: 'marketplace',
|
||||
title: 'КООПЛЕЙС',
|
||||
announce: '',
|
||||
description: '',
|
||||
@@ -44,47 +54,37 @@ export class CooperativeClass {
|
||||
meta: '',
|
||||
is_can_coop_spend_share_contributions: false,
|
||||
})
|
||||
}
|
||||
|
||||
async createAgreements(coopname: string) {
|
||||
// console.log('создаём кооперативное соглашение/положение по кошельку')
|
||||
await this.blockchain.makeCoagreement({
|
||||
await this.blockchain.createProgram({
|
||||
coopname,
|
||||
administrator: coopname,
|
||||
type: 'wallet',
|
||||
draft_id: TCooperative.Registry.WalletAgreement.registry_id,
|
||||
program_id: 1,
|
||||
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.makeCoagreement({
|
||||
await this.blockchain.createProgram({
|
||||
coopname,
|
||||
administrator: coopname,
|
||||
type: 'signature',
|
||||
draft_id: TCooperative.Registry.RegulationElectronicSignature.registry_id,
|
||||
program_id: 0,
|
||||
})
|
||||
await this.blockchain.makeCoagreement({
|
||||
coopname,
|
||||
administrator: coopname,
|
||||
type: 'user',
|
||||
draft_id: TCooperative.Registry.UserAgreement.registry_id,
|
||||
program_id: 0,
|
||||
})
|
||||
|
||||
await this.blockchain.makeCoagreement({
|
||||
coopname,
|
||||
administrator: coopname,
|
||||
type: 'privacy',
|
||||
draft_id: TCooperative.Registry.PrivacyPolicy.registry_id,
|
||||
program_id: 0,
|
||||
})
|
||||
|
||||
await this.blockchain.makeCoagreement({
|
||||
coopname,
|
||||
administrator: coopname,
|
||||
type: 'coopenomics',
|
||||
draft_id: TCooperative.Registry.CoopenomicsAgreement.registry_id,
|
||||
program_id: 0,
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -96,8 +96,7 @@ export class CooperativeClass {
|
||||
)
|
||||
console.log('Регистрируем аккаунт')
|
||||
|
||||
await this.blockchain.registerAccount2({
|
||||
registrator: config.provider,
|
||||
await this.blockchain.createAccount({
|
||||
coopname: config.provider,
|
||||
referer: '',
|
||||
username: account.username,
|
||||
@@ -106,17 +105,68 @@ export class CooperativeClass {
|
||||
})
|
||||
|
||||
console.log('Регистрируем аккаунт как пользователя')
|
||||
const registration_hash = generateRandomSHA256()
|
||||
|
||||
await this.blockchain.registerUser({
|
||||
registrator: config.provider,
|
||||
coopname: config.provider,
|
||||
braname: '',
|
||||
username: account.username,
|
||||
type: 'organization',
|
||||
statement: document,
|
||||
registration_hash,
|
||||
})
|
||||
|
||||
console.log('Переводим аккаунт в организации')
|
||||
console.log('Отправляем заявление на вступление')
|
||||
|
||||
await this.blockchain.registerOrganization({
|
||||
await this.blockchain.ConfirmPayment({
|
||||
coopname: config.provider,
|
||||
income_hash: registration_hash,
|
||||
})
|
||||
|
||||
await processLastDecision(this.blockchain, config.provider)
|
||||
// console.log('Голосуем по решению в провайдере')
|
||||
|
||||
// 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('Утверждаем решение в провайдере')
|
||||
|
||||
// await this.blockchain.authorize({
|
||||
// coopname: config.provider,
|
||||
// chairman: config.provider_chairman,
|
||||
// decision_id: 1,
|
||||
// document,
|
||||
// })
|
||||
|
||||
// console.log('Исполняем решение в провайдере')
|
||||
|
||||
// await this.blockchain.exec({
|
||||
// executer: config.provider_chairman,
|
||||
// coopname: config.provider,
|
||||
// decision_id: 1,
|
||||
// })
|
||||
|
||||
console.log('Отправляем подписанное положение о ЦПП Кошелька оператору')
|
||||
|
||||
await this.blockchain.sendAgreement({
|
||||
coopname: config.provider,
|
||||
administrator: config.provider,
|
||||
username: username!,
|
||||
agreement_type: 'wallet',
|
||||
document,
|
||||
})
|
||||
|
||||
console.log('Переводим аккаунт в кооператив')
|
||||
|
||||
await this.blockchain.registerCooperative({
|
||||
username: account.username,
|
||||
coopname: account.username,
|
||||
params: {
|
||||
@@ -132,62 +182,8 @@ export class CooperativeClass {
|
||||
document,
|
||||
})
|
||||
|
||||
console.log('Отправляем заявление на вступление')
|
||||
|
||||
await this.blockchain.joinCoop({
|
||||
registrator: config.provider_chairman,
|
||||
coopname: config.provider,
|
||||
username: account.username,
|
||||
document,
|
||||
braname: '',
|
||||
})
|
||||
|
||||
console.log('Голосуем по решению в провайдере')
|
||||
|
||||
await this.blockchain.votefor({
|
||||
coopname: config.provider,
|
||||
member: config.provider_chairman,
|
||||
decision_id: 1,
|
||||
})
|
||||
|
||||
console.log('Утверждаем решение в провайдере')
|
||||
|
||||
await this.blockchain.authorize({
|
||||
coopname: config.provider,
|
||||
chairman: config.provider_chairman,
|
||||
decision_id: 1,
|
||||
document,
|
||||
})
|
||||
|
||||
console.log('Исполняем решение в провайдере')
|
||||
|
||||
await this.blockchain.exec({
|
||||
executer: config.provider_chairman,
|
||||
coopname: config.provider,
|
||||
decision_id: 1,
|
||||
})
|
||||
|
||||
console.log('Отправляем подписанное положение о ЦПП Кошелька оператору')
|
||||
|
||||
await this.blockchain.sendAgreement({
|
||||
coopname: config.provider,
|
||||
administrator: config.provider,
|
||||
username: username!,
|
||||
agreement_type: 'wallet',
|
||||
document: { // отправляем произвольный документ с валидной подписью
|
||||
hash: '157192B276DA23CC84AB078FC8755C051C5F0430BF4802E55718221E6B76C777',
|
||||
public_key: 'PUB_K1_5JhMfxbsNebajHcTEK8yC9uNN9Dit9hEmzE8ri8yMhhzzEtUA4',
|
||||
signature: 'SIG_K1_KmKWPBC8dZGGDGhbKEoZEzPr3h5crRrR2uLdGRF5DJbeibY1MY1bZ9sPwHsgmPfiGFv9psfoCVsXFh9TekcLuvaeuxRKA8',
|
||||
meta: '{}',
|
||||
},
|
||||
})
|
||||
|
||||
console.log('создаём программы и соглашения новому кооперативу')
|
||||
// await this.createPrograms(username)
|
||||
await this.createAgreements(username)
|
||||
|
||||
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(`Арендуем ресурсы кооперативу`)
|
||||
|
||||
@@ -206,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: 'Иван',
|
||||
@@ -157,7 +180,7 @@ export async function startInfra() {
|
||||
position: 'Председатель',
|
||||
based_on: 'Решение общего собрания №1',
|
||||
},
|
||||
country: 'Russia',
|
||||
country: 'Российская Федерация',
|
||||
city: 'Москва',
|
||||
fact_address: '117593 г. Москва, муниципальный округ Ясенево, проезд Соловьиный, дом 1, помещение 1/1',
|
||||
full_address:
|
||||
@@ -202,7 +225,7 @@ export async function startInfra() {
|
||||
first_name: 'Иван',
|
||||
last_name: 'Иванов',
|
||||
middle_name: 'Иванович',
|
||||
birthdate: '2023-04-01',
|
||||
birthdate: '1990/04/01',
|
||||
phone: '+71234567890',
|
||||
email: 'ivanov@example.com',
|
||||
full_address: 'Переулок Правды д. 1',
|
||||
@@ -210,7 +233,7 @@ export async function startInfra() {
|
||||
series: 7122,
|
||||
number: 112233,
|
||||
issued_by: 'отделом УФМС по г. Москва',
|
||||
issued_at: '22.04.2010',
|
||||
issued_at: '2010/05/10',
|
||||
code: '111-232',
|
||||
},
|
||||
}
|
||||
@@ -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,28 +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.createAgreements(config.provider)
|
||||
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 createParticipant(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,
|
||||
@@ -37,9 +104,8 @@ export class CooperativeClass {
|
||||
console.log('Регистрируем аккаунт')
|
||||
|
||||
const data: RegistratorContract.Actions.AddUser.IAddUser = {
|
||||
registrator: config.provider,
|
||||
coopname: config.provider,
|
||||
referer: '',
|
||||
referer: referer || '',
|
||||
username: account.username,
|
||||
type: 'individual',
|
||||
created_at: '2025-02-05T09:34:27',
|
||||
@@ -93,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('создаём кошелёк')
|
||||
@@ -115,15 +176,28 @@ export class CooperativeClass {
|
||||
}
|
||||
}
|
||||
|
||||
export async function createParticipant(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.createParticipant(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,
|
||||
}
|
||||
}
|
||||
@@ -1,34 +1,21 @@
|
||||
import { expect } from 'vitest'
|
||||
import { CapitalContract } from 'cooptypes'
|
||||
import { CapitalContract, SovietContract } from 'cooptypes'
|
||||
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
|
||||
import { generateRandomSHA256 } from '../../utils/randomHash'
|
||||
import { fakeDocument } from '../soviet/fakeDocument'
|
||||
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] || { spend: '0.0000 RUB', commits_count: 0 }
|
||||
console.log(`\n🔹 Начало коммита: ${commitHash}, проект: ${projectHash}, часы: ${spendHours}`)
|
||||
|
||||
// Получение текущих данных проекта перед коммитом
|
||||
const prevProject = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
@@ -38,7 +25,7 @@ export async function commitToResult(
|
||||
projectHash,
|
||||
3,
|
||||
'sha256',
|
||||
))[0] || { spend: '0.0000 RUB' }
|
||||
))[0] || { spended: '0.0000 RUB' }
|
||||
|
||||
const prevContributor = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
@@ -52,22 +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,
|
||||
creator,
|
||||
username: creator,
|
||||
project_hash: projectHash,
|
||||
commit_hash: commitHash,
|
||||
specification: fakeDocument,
|
||||
contributed_hours: spendHours,
|
||||
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,41 +102,30 @@ export async function commitToResult(
|
||||
console.log('🔍 Коммит после создания:', blockchainCommit)
|
||||
expect(blockchainCommit).toBeDefined()
|
||||
expect(blockchainCommit.commit_hash).toBe(commitHash)
|
||||
expect(blockchainCommit.spend).toBe(totalSpended)
|
||||
expect(blockchainCommit.project_hash).toBe(projectHash)
|
||||
expect(blockchainCommit.status).toBe('created')
|
||||
|
||||
// Утверждение коммита
|
||||
const approveCommitData: CapitalContract.Actions.ApproveCommit.IApproveCommit = {
|
||||
// Получение данных проекта для определения мастера
|
||||
const projectData = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
application: coopname,
|
||||
commit_hash: commitHash,
|
||||
approver: 'ant',
|
||||
approved_specification: fakeDocument,
|
||||
}
|
||||
'projects',
|
||||
1,
|
||||
projectHash,
|
||||
projectHash,
|
||||
3,
|
||||
'sha256',
|
||||
))[0]
|
||||
|
||||
console.log(`\n✅ Подтверждение коммита ${commitHash}`)
|
||||
const approveCommitResult = await blockchain.api.transact(
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
account: CapitalContract.contractName.production,
|
||||
name: CapitalContract.Actions.ApproveCommit.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',
|
||||
@@ -148,23 +136,10 @@ export async function commitToResult(
|
||||
'sha256',
|
||||
))[0]
|
||||
|
||||
console.log('🔍 Коммит после утверждения:', blockchainCommit)
|
||||
expect(blockchainCommit).toBeDefined()
|
||||
expect(blockchainCommit.commit_hash).toBe(commitHash)
|
||||
expect(blockchainCommit.status).toBe('approved')
|
||||
|
||||
// Проверка результата после утверждения коммита
|
||||
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,
|
||||
@@ -188,23 +163,19 @@ export async function commitToResult(
|
||||
))[0]
|
||||
|
||||
console.log('\n📊 Балансы после утверждения коммита:')
|
||||
console.log('▶ Результат:', finalResult)
|
||||
console.log('▶ Проект:', finalProject)
|
||||
console.log('▶ Контрибьютор:', finalContributor)
|
||||
|
||||
expect(parseFloat(finalResult.spend)).toBe(parseFloat(prevResult.spend) + parseFloat(totalSpended))
|
||||
expect(parseFloat(finalProject.spend)).toBe(parseFloat(prevProject.spend))
|
||||
|
||||
// Проверка, что у контрибьютора увеличились 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,
|
||||
}
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
export const capitalProgramId = 3
|
||||
export const sourceProgramId = 3
|
||||
export const capitalProgramId = 4
|
||||
export const walletProgramId = 1
|
||||
export const ratePerHour = '1000.0000 RUB'
|
||||
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 } 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()
|
||||
}
|
||||
|
||||
@@ -174,26 +174,24 @@ export async function registerExpense(
|
||||
const blockchainWithdraw = (await blockchain.getTableRows(
|
||||
GatewayContract.contractName.production,
|
||||
coopname,
|
||||
GatewayContract.Tables.Withdraws.tableName,
|
||||
GatewayContract.Tables.Outcomes.tableName,
|
||||
1,
|
||||
expenseHash,
|
||||
expenseHash,
|
||||
3,
|
||||
2,
|
||||
'sha256',
|
||||
))[0]
|
||||
|
||||
console.log('🔍 Вывод средств:', blockchainWithdraw)
|
||||
expect(blockchainWithdraw).toBeDefined()
|
||||
expect(blockchainWithdraw.withdraw_hash).toBe(expenseHash)
|
||||
expect(blockchainWithdraw.outcome_hash).toBe(expenseHash)
|
||||
expect(blockchainWithdraw.quantity).toBe(expenseAmount)
|
||||
expect(blockchainWithdraw.status).toBe('authorized')
|
||||
expect(blockchainWithdraw.status).toBe('pending')
|
||||
|
||||
// Завершение вывода
|
||||
const completeWithdrawData: GatewayContract.Actions.CompleteWithdraw.ICompleteWithdraw = {
|
||||
const completeWithdrawData: GatewayContract.Actions.CompleteOutcome.ICompleteOutcome = {
|
||||
coopname,
|
||||
application: '',
|
||||
memo: '',
|
||||
withdraw_hash: expenseHash,
|
||||
outcome_hash: expenseHash,
|
||||
}
|
||||
|
||||
console.log(`\n✅ Завершение вывода ${expenseHash}`)
|
||||
@@ -202,7 +200,7 @@ export async function registerExpense(
|
||||
actions: [
|
||||
{
|
||||
account: GatewayContract.contractName.production,
|
||||
name: GatewayContract.Actions.CompleteWithdraw.actionName,
|
||||
name: GatewayContract.Actions.CompleteOutcome.actionName,
|
||||
authorization: [{ actor: coopname, permission: 'active' }],
|
||||
data: completeWithdrawData,
|
||||
},
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { expect } from 'vitest'
|
||||
import { signAgreement } from '../soviet/signAgreement'
|
||||
import { getCoopProgramWallet, getUserProgramWallet } from '../wallet/walletUtils'
|
||||
import { capitalProgramId } from './consts'
|
||||
import { capitalProgramName, sourceProgramId } from './consts'
|
||||
|
||||
export async function signCapitalAgreement(
|
||||
blockchain: any,
|
||||
@@ -9,20 +9,20 @@ export async function signCapitalAgreement(
|
||||
username: string,
|
||||
fakeDocument: any,
|
||||
) {
|
||||
const txId = await signAgreement(blockchain, coopname, username, 'cofund', fakeDocument)
|
||||
const txId = await signAgreement(blockchain, coopname, username, capitalProgramName, fakeDocument)
|
||||
|
||||
const wallet = await getUserProgramWallet(blockchain, coopname, username, capitalProgramId)
|
||||
const wallet = await getUserProgramWallet(blockchain, coopname, username, sourceProgramId)
|
||||
|
||||
expect(wallet).toEqual(expect.objectContaining({
|
||||
coopname,
|
||||
username,
|
||||
available: expect.any(String),
|
||||
blocked: expect.any(String),
|
||||
program_id: capitalProgramId,
|
||||
program_id: sourceProgramId,
|
||||
membership_contribution: expect.any(String),
|
||||
}))
|
||||
|
||||
const program = await getCoopProgramWallet(blockchain, coopname, capitalProgramId)
|
||||
const program = await getCoopProgramWallet(blockchain, coopname, sourceProgramId)
|
||||
|
||||
return { wallet, program, txId }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { expect } from 'vitest'
|
||||
import { signAgreement } from '../soviet/signAgreement'
|
||||
import { getCoopProgramWallet, getUserProgramWallet } from '../wallet/walletUtils'
|
||||
import { sourceProgramId, sourceProgramName } from './consts'
|
||||
|
||||
export async function signGenerationContract(
|
||||
blockchain: any,
|
||||
coopname: string,
|
||||
username: string,
|
||||
fakeDocument: any,
|
||||
) {
|
||||
const txId = await signAgreement(blockchain, coopname, username, sourceProgramName, fakeDocument)
|
||||
|
||||
const wallet = await getUserProgramWallet(blockchain, coopname, username, sourceProgramId)
|
||||
|
||||
expect(wallet).toEqual(expect.objectContaining({
|
||||
coopname,
|
||||
username,
|
||||
available: expect.any(String),
|
||||
blocked: expect.any(String),
|
||||
program_id: sourceProgramId,
|
||||
membership_contribution: expect.any(String),
|
||||
}))
|
||||
|
||||
const program = await getCoopProgramWallet(blockchain, coopname, sourceProgramId)
|
||||
|
||||
return { wallet, program, txId }
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -44,15 +44,14 @@ export async function withdrawContribution(
|
||||
console.log('▶ Проект:', prevProject)
|
||||
|
||||
// Создание заявления на возврат
|
||||
const createWithdrawData: CapitalContract.Actions.CreateWithdraw1.ICreateWithdraw = {
|
||||
const createWithdrawData: CapitalContract.Actions.WithdrawResult.IWithdrawFromResult = {
|
||||
coopname,
|
||||
application: coopname,
|
||||
username,
|
||||
result_hash: resultHash,
|
||||
withdraw_hash: withdrawHash,
|
||||
commit_hashes: commitHashes,
|
||||
contribution_statement: fakeDocument,
|
||||
return_statement: fakeDocument,
|
||||
amount: withdrawAmount,
|
||||
}
|
||||
|
||||
console.log(`\n🚀 Отправка транзакции CreateWithdraw для ${username} на сумму ${withdrawAmount}`)
|
||||
@@ -61,7 +60,7 @@ export async function withdrawContribution(
|
||||
actions: [
|
||||
{
|
||||
account: CapitalContract.contractName.production,
|
||||
name: CapitalContract.Actions.CreateWithdraw1.actionName,
|
||||
name: CapitalContract.Actions.WithdrawResult.actionName,
|
||||
authorization: [{ actor: coopname, permission: 'active' }],
|
||||
data: createWithdrawData,
|
||||
},
|
||||
@@ -79,7 +78,7 @@ export async function withdrawContribution(
|
||||
let blockchainWithdraw = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'withdraws',
|
||||
CapitalContract.Tables.ResultWithdraws.tableName,
|
||||
1,
|
||||
withdrawHash,
|
||||
withdrawHash,
|
||||
@@ -93,12 +92,11 @@ export async function withdrawContribution(
|
||||
expect(blockchainWithdraw.amount).toBe(withdrawAmount)
|
||||
|
||||
// Подтверждение возврата
|
||||
const approveWithdrawData: CapitalContract.Actions.ApproveWithdraw.IApproveWithdraw = {
|
||||
const approveWithdrawData: CapitalContract.Actions.ApproveWithdrawResult.IApproveWithdrawResult = {
|
||||
coopname,
|
||||
application: coopname,
|
||||
approver: 'ant',
|
||||
withdraw_hash: withdrawHash,
|
||||
approved_contribution_statement: fakeDocument,
|
||||
approved_return_statement: fakeDocument,
|
||||
}
|
||||
|
||||
@@ -108,7 +106,7 @@ export async function withdrawContribution(
|
||||
actions: [
|
||||
{
|
||||
account: CapitalContract.contractName.production,
|
||||
name: CapitalContract.Actions.ApproveWithdraw.actionName,
|
||||
name: CapitalContract.Actions.ApproveWithdrawResult.actionName,
|
||||
authorization: [{ actor: coopname, permission: 'active' }],
|
||||
data: approveWithdrawData,
|
||||
},
|
||||
@@ -126,7 +124,7 @@ export async function withdrawContribution(
|
||||
blockchainWithdraw = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'withdraws',
|
||||
CapitalContract.Tables.ResultWithdraws.tableName,
|
||||
1,
|
||||
withdrawHash,
|
||||
withdrawHash,
|
||||
@@ -146,17 +144,15 @@ export async function withdrawContribution(
|
||||
1000,
|
||||
)
|
||||
const lastDecision = decisions[decisions.length - 1]
|
||||
const prevLastDecision = decisions[decisions.length - 2]
|
||||
|
||||
console.log(`\n📜 Выполнение решений: ${prevLastDecision.id}, ${lastDecision.id}`)
|
||||
console.log(`\n📜 Выполнение решения: ${lastDecision.id}`)
|
||||
|
||||
await processDecision(blockchain, prevLastDecision.id)
|
||||
await processDecision(blockchain, lastDecision.id)
|
||||
|
||||
blockchainWithdraw = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'withdraws',
|
||||
CapitalContract.Tables.ResultWithdraws.tableName,
|
||||
1,
|
||||
withdrawHash,
|
||||
withdrawHash,
|
||||
@@ -203,7 +199,9 @@ export async function withdrawContribution(
|
||||
expect(parseFloat(programWallet.blocked)).toBe(parseFloat(prevProgramWallet.blocked))
|
||||
|
||||
// Проверка изменения spend в проекте
|
||||
expect(parseFloat(finalProject.spend)).toBe(parseFloat(prevProject.spend) + parseFloat(withdrawAmount))
|
||||
expect(parseFloat(finalProject.withdrawed)).toBe(parseFloat(prevProject.withdrawed) + parseFloat(withdrawAmount))
|
||||
|
||||
expect(parseFloat(finalProject.spend)).toBe(parseFloat(prevProject.spend))
|
||||
|
||||
console.log(`\n✅ Возврат на ${withdrawAmount} завершен успешно!`)
|
||||
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
|
||||
import Blockchain from '../blockchain'
|
||||
import config from '../configs'
|
||||
import { getTotalRamUsage, globalRamStats } from '../utils/getTotalRamUsage'
|
||||
import { addUser } from '../init/participant'
|
||||
import { generateRandomUsername } from '../utils/randomUsername'
|
||||
|
||||
const blockchain = new Blockchain(config.network, config.private_keys)
|
||||
|
||||
let supplier: string
|
||||
let customer: string
|
||||
|
||||
const fakeDocument = {
|
||||
hash: '157192B276DA23CC84AB078FC8755C051C5F0430BF4802E55718221E6B76C777',
|
||||
public_key: 'PUB_K1_5JhMfxbsNebajHcTEK8yC9uNN9Dit9hEmzE8ri8yMhhzzEtUA4',
|
||||
signature: 'SIG_K1_KmKWPBC8dZGGDGhbKEoZEzPr3h5crRrR2uLdGRF5DJbeibY1MY1bZ9sPwHsgmPfiGFv9psfoCVsXFh9TekcLuvaeuxRKA8',
|
||||
meta: '{}',
|
||||
}
|
||||
|
||||
const testHash = '0000000000000000000000000000000000000000000000000000000000000001'
|
||||
|
||||
beforeAll(async () => {
|
||||
await blockchain.update_pass_instance()
|
||||
|
||||
supplier = generateRandomUsername()
|
||||
customer = generateRandomUsername()
|
||||
|
||||
console.log('supplier:', supplier)
|
||||
console.log('customer:', customer)
|
||||
|
||||
await addUser(supplier)
|
||||
await addUser(customer)
|
||||
}, 500_000)
|
||||
|
||||
afterAll(() => {
|
||||
console.log('\n📊 **MARKETPLACE RAM USAGE** 📊')
|
||||
let total = 0
|
||||
for (const [key, ram] of Object.entries(globalRamStats)) {
|
||||
console.log(` ${key} = ${(ram / 1024).toFixed(2)} kb`)
|
||||
total += ram
|
||||
}
|
||||
console.log(`\n💾 **TOTAL**: ${(total / 1024).toFixed(2)} kb\n`)
|
||||
})
|
||||
|
||||
describe('Marketplace — orderoffer flow', () => {
|
||||
it('контракт marketplace задеплоен', async () => {
|
||||
const info = await blockchain.api.v1.chain.get_info()
|
||||
expect(info).toBeDefined()
|
||||
expect(info.chain_id).toBeDefined()
|
||||
})
|
||||
|
||||
it('создание заявки orderoffer (заказчик)', async () => {
|
||||
const coopname = config.coopname
|
||||
|
||||
const result = await blockchain.transact({
|
||||
account: 'marketplace',
|
||||
name: 'orderoffer',
|
||||
authorization: [{ actor: coopname, permission: 'active' }],
|
||||
data: {
|
||||
coopname,
|
||||
receiver_braname: coopname,
|
||||
username: customer,
|
||||
hash: testHash,
|
||||
units: 10,
|
||||
unit_cost: '100.0000 RUB',
|
||||
product_lifecycle_secs: 2592000,
|
||||
warranty_period_secs: 604800,
|
||||
membership_fee_amount: '50.0000 RUB',
|
||||
cancellation_fee_amount: '10.0000 RUB',
|
||||
convert_in: fakeDocument,
|
||||
meta: JSON.stringify({ title: 'Тестовый товар', description: 'Описание' }),
|
||||
},
|
||||
})
|
||||
|
||||
expect(result).toBeDefined()
|
||||
console.log('orderoffer tx:', result?.response?.transaction_id?.substring(0, 16))
|
||||
|
||||
const ramUsed = await getTotalRamUsage(coopname)
|
||||
globalRamStats['orderoffer'] = ramUsed
|
||||
})
|
||||
|
||||
it('принятие заявки поставщиком (accept)', async () => {
|
||||
const coopname = config.coopname
|
||||
|
||||
const result = await blockchain.transact({
|
||||
account: 'marketplace',
|
||||
name: 'accept',
|
||||
authorization: [{ actor: coopname, permission: 'active' }],
|
||||
data: {
|
||||
coopname,
|
||||
supplier_braname: coopname,
|
||||
username: supplier,
|
||||
request_hash: testHash,
|
||||
convert_out: fakeDocument,
|
||||
product_contribution_statement: fakeDocument,
|
||||
},
|
||||
})
|
||||
|
||||
expect(result).toBeDefined()
|
||||
console.log('accept tx:', result?.response?.transaction_id?.substring(0, 16))
|
||||
|
||||
const ramUsed = await getTotalRamUsage(coopname)
|
||||
globalRamStats['accept'] = ramUsed
|
||||
})
|
||||
})
|
||||
|
||||
describe('Marketplace — coopstock flow', () => {
|
||||
const stockHash = '0000000000000000000000000000000000000000000000000000000000000002'
|
||||
|
||||
it('создание предложения из запасов кооператива', async () => {
|
||||
const coopname = config.coopname
|
||||
|
||||
const result = await blockchain.transact({
|
||||
account: 'marketplace',
|
||||
name: 'coopstock',
|
||||
authorization: [{ actor: coopname, permission: 'active' }],
|
||||
data: {
|
||||
coopname,
|
||||
braname: coopname,
|
||||
hash: stockHash,
|
||||
units: 5,
|
||||
unit_cost: '50.0000 RUB',
|
||||
product_lifecycle_secs: 1296000,
|
||||
warranty_period_secs: 302400,
|
||||
membership_fee_amount: '25.0000 RUB',
|
||||
meta: JSON.stringify({ title: 'Уценённый товар', description: 'Из запасов' }),
|
||||
},
|
||||
})
|
||||
|
||||
expect(result).toBeDefined()
|
||||
console.log('coopstock tx:', result?.response?.transaction_id?.substring(0, 16))
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,57 @@
|
||||
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
|
||||
import { GatewayContract, RegistratorContract, SovietContract } from 'cooptypes'
|
||||
import { sha256 } from 'eosjs/dist/eosjs-key-conversions'
|
||||
import Blockchain from '../blockchain'
|
||||
import config from '../configs'
|
||||
import { getTotalRamUsage, globalRamStats } from '../utils/getTotalRamUsage'
|
||||
import { addUser } from '../init/participant'
|
||||
import { generateRandomUsername } from '../utils/randomUsername'
|
||||
import { generateRandomSHA256 } from '../utils/randomHash'
|
||||
import { signWalletAgreement } from './wallet/signWalletAgreement'
|
||||
import { depositToWallet } from './wallet/depositToWallet'
|
||||
import { processDecision } from './soviet/processDecision'
|
||||
import { getParticipant } from './registrator/getParticipant'
|
||||
import { registerUser } from './registrator/registerUser'
|
||||
|
||||
const blockchain = new Blockchain(config.network, config.private_keys)
|
||||
let tester1: string
|
||||
const walletProgramStates1: any[] = []
|
||||
|
||||
const fakeDocument = {
|
||||
hash: '157192B276DA23CC84AB078FC8755C051C5F0430BF4802E55718221E6B76C777',
|
||||
public_key: 'PUB_K1_5JhMfxbsNebajHcTEK8yC9uNN9Dit9hEmzE8ri8yMhhzzEtUA4',
|
||||
signature: 'SIG_K1_KmKWPBC8dZGGDGhbKEoZEzPr3h5crRrR2uLdGRF5DJbeibY1MY1bZ9sPwHsgmPfiGFv9psfoCVsXFh9TekcLuvaeuxRKA8',
|
||||
meta: '{}',
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
await blockchain.update_pass_instance()
|
||||
}, 500_000)
|
||||
|
||||
afterAll(() => {
|
||||
console.log('\n📊 **RAM USAGE SUMMARY** 📊')
|
||||
let totalGlobalRam = 0
|
||||
|
||||
for (const [key, ramUsed] of Object.entries(globalRamStats)) {
|
||||
console.log(` ${key} = ${(ramUsed / 1024).toFixed(2)} kb`)
|
||||
totalGlobalRam += ramUsed
|
||||
}
|
||||
|
||||
console.log(`\n💾 **TOTAL RAM USED IN TESTS**: ${(totalGlobalRam / 1024).toFixed(2)} kb\n`)
|
||||
})
|
||||
|
||||
describe('тест Registrator', () => {
|
||||
it('регистируем пайщика 1', async () => {
|
||||
const coopname = 'voskhod'
|
||||
const tester1 = generateRandomUsername()
|
||||
|
||||
await registerUser(blockchain, coopname, tester1)
|
||||
})
|
||||
|
||||
it('регистируем пайщика 2', async () => {
|
||||
const coopname = 'voskhod'
|
||||
const tester2 = generateRandomUsername()
|
||||
|
||||
await registerUser(blockchain, coopname, tester2)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,19 @@
|
||||
import { SovietContract } from 'cooptypes'
|
||||
import type Blockchain from '../../blockchain'
|
||||
|
||||
export async function getParticipant(
|
||||
blockchain: Blockchain,
|
||||
coopname: string,
|
||||
username: string,
|
||||
) {
|
||||
const participant = (await blockchain.getTableRows(
|
||||
SovietContract.contractName.production,
|
||||
'voskhod',
|
||||
'participants',
|
||||
1,
|
||||
username,
|
||||
username,
|
||||
))[0]
|
||||
|
||||
return participant
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
import { GatewayContract, RegistratorContract, SovietContract } from 'cooptypes'
|
||||
import { expect } from 'vitest'
|
||||
import { generateRandomSHA256 } from '../../utils/randomHash'
|
||||
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
|
||||
import { processDecision } from '../soviet/processDecision'
|
||||
import { getParticipant } from '../registrator/getParticipant'
|
||||
import { fakeDocument } from '../soviet/fakeDocument'
|
||||
import { compareTokenAmounts, getCoopWallet } from '../wallet/walletUtils'
|
||||
|
||||
export async function registerUser(blockchain: any, coopname: string, username: string) {
|
||||
const registration_hash = generateRandomSHA256()
|
||||
|
||||
const account = await blockchain.generateKeypair(username, undefined, 'Аккаунт кооператива')
|
||||
|
||||
// 🔹 Получаем балансы до регистрации
|
||||
const prevCoopWallet = await getCoopWallet(blockchain, coopname)
|
||||
const prevAvailable = prevCoopWallet?.circulating_account?.available || '0.0000 RUB'
|
||||
const prevInitial = prevCoopWallet?.initial_account?.available || '0.0000 RUB'
|
||||
console.log('📦 CoopWallet BEFORE:', prevCoopWallet)
|
||||
|
||||
// 🔹 1. Создание аккаунта
|
||||
{
|
||||
const data: RegistratorContract.Actions.CreateAccount.ICreateAccount = {
|
||||
coopname,
|
||||
referer: '',
|
||||
username,
|
||||
public_key: account.publicKey,
|
||||
meta: '',
|
||||
}
|
||||
|
||||
const result = await blockchain.api.transact({
|
||||
actions: [{
|
||||
account: RegistratorContract.contractName.production,
|
||||
name: RegistratorContract.Actions.CreateAccount.actionName,
|
||||
authorization: [{ actor: coopname, permission: 'active' }],
|
||||
data,
|
||||
}],
|
||||
}, { blocksBehind: 3, expireSeconds: 30 })
|
||||
|
||||
getTotalRamUsage(result)
|
||||
console.log('✅ Аккаунт создан')
|
||||
}
|
||||
|
||||
// 🔹 2. Регистрация пайщика
|
||||
{
|
||||
const data: RegistratorContract.Actions.RegisterUser.IRegisterUser = {
|
||||
coopname,
|
||||
braname: '',
|
||||
username,
|
||||
type: 'individual',
|
||||
statement: fakeDocument,
|
||||
registration_hash,
|
||||
}
|
||||
|
||||
const result = await blockchain.api.transact({
|
||||
actions: [{
|
||||
account: RegistratorContract.contractName.production,
|
||||
name: RegistratorContract.Actions.RegisterUser.actionName,
|
||||
authorization: [{ actor: coopname, permission: 'active' }],
|
||||
data,
|
||||
}],
|
||||
}, { blocksBehind: 3, expireSeconds: 30 })
|
||||
|
||||
getTotalRamUsage(result)
|
||||
console.log('✅ Пайщик зарегистрирован')
|
||||
}
|
||||
|
||||
// 🔹 3. Подтверждение прихода
|
||||
{
|
||||
const data: GatewayContract.Actions.CompleteIncome.ICompleteIncome = {
|
||||
coopname,
|
||||
income_hash: registration_hash,
|
||||
}
|
||||
|
||||
const result = await blockchain.api.transact({
|
||||
actions: [{
|
||||
account: GatewayContract.contractName.production,
|
||||
name: GatewayContract.Actions.CompleteIncome.actionName,
|
||||
authorization: [{ actor: coopname, permission: 'active' }],
|
||||
data,
|
||||
}],
|
||||
}, { blocksBehind: 3, expireSeconds: 30 })
|
||||
|
||||
getTotalRamUsage(result)
|
||||
console.log('✅ Приход подтвержден')
|
||||
}
|
||||
|
||||
// 🔹 4. Исполнение решения
|
||||
const decisions = await blockchain.getTableRows(
|
||||
SovietContract.contractName.production,
|
||||
coopname,
|
||||
'decisions',
|
||||
1000,
|
||||
)
|
||||
const lastDecision = decisions.at(-1)
|
||||
console.log('📜 Последнее решение:', lastDecision)
|
||||
await processDecision(blockchain, lastDecision.id)
|
||||
console.log('✅ Решение выполнено')
|
||||
|
||||
// 🔹 5. Проверка участника
|
||||
const participant = await getParticipant(blockchain, coopname, username)
|
||||
console.log('🙋 Участник:', participant)
|
||||
|
||||
expect(participant).toBeDefined()
|
||||
expect(participant.has_vote).toBe(1)
|
||||
expect(participant.status).toBe('accepted')
|
||||
expect(participant.username).toBe(username)
|
||||
|
||||
// 🔹 6. Проверка балансов после регистрации
|
||||
const coopWalletAfter = await getCoopWallet(blockchain, coopname)
|
||||
const newAvailable = coopWalletAfter?.circulating_account?.available || '0.0000 RUB'
|
||||
const newInitial = coopWalletAfter?.initial_account?.available || '0.0000 RUB'
|
||||
|
||||
console.log('💰 CoopWallet AFTER:', coopWalletAfter)
|
||||
|
||||
const minAmount = parseFloat(participant.minimum_amount.split(' ')[0])
|
||||
const initAmount = parseFloat(participant.initial_amount.split(' ')[0])
|
||||
|
||||
compareTokenAmounts(prevAvailable, newAvailable, minAmount)
|
||||
compareTokenAmounts(prevInitial, newInitial, initAmount)
|
||||
|
||||
return participant
|
||||
}
|
||||
@@ -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'
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user