diff --git a/.graphqlconfig b/.graphqlconfig index 4ee48de2..e0599d84 100644 --- a/.graphqlconfig +++ b/.graphqlconfig @@ -4,7 +4,7 @@ "extensions": { "endpoints": { "Default GraphQL Endpoint": { - "url": "http://192.168.1.101:8000/api/v1/graphql/", + "url": "http://127.0.0.1:8000/api/v1/graphql/", "headers": { "user-agent": "JS GraphQL", "Authorization": "Bearer " diff --git a/Pipfile b/Pipfile index 0eaf496e..084ef18f 100644 --- a/Pipfile +++ b/Pipfile @@ -5,10 +5,10 @@ name = "pypi" [packages] django = ">=2.2" -django-treebeard = "~=4.5.1" -ofxtools = "~=0.9.5" -markdown = "~=3.4.1" -faker = "~=15.3.3" +django-treebeard = ">=4.5.1" +ofxtools = ">=0.9.5" +markdown = ">=3.4.1" +faker = ">=15.3.3" pillow = ">=9.3.0" [graphql] @@ -16,8 +16,6 @@ django-filter = ">=2.1.0" graphene = ">=3.2.1" graphene_django = ">=3.0.0" django-oauth-toolkit = ">=2.2.0" -#django-graphql-jwt = "*" -#django-graphql-auth = "*" [dev-packages] jupyterlab = "*" @@ -28,6 +26,7 @@ pylint = "*" furo = "*" twine = "*" python-dotenv = "*" +tabulate = "*" [requires] python_version = "3.11" diff --git a/Pipfile.lock b/Pipfile.lock index 53416e8c..3ddce93d 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "3503135607c69826f86ea744763d362f261f935cba4ced85cd9e65c9b25b75a7" + "sha256": "678d25be4f6119aa390394cc6b1b42780a74bd2d0aa9d8cc3fe40c0526d4d547" }, "pipfile-spec": 6, "requires": { @@ -26,35 +26,35 @@ }, "django": { "hashes": [ - "sha256:4b214a05fe4c99476e99e2445c8b978c8369c18d4dea8e22ec412862715ad763", - "sha256:ff56ebd7ead0fd5dbe06fe157b0024a7aaea2e0593bb3785fb594cf94dad58ef" + "sha256:ad33ed68db9398f5dfb33282704925bce044bef4261cd4fb59e4e7f9ae505a78", + "sha256:c36e2ab12824e2ac36afa8b2515a70c53c7742f0d6eaefa7311ec379558db997" ], "index": "pypi", - "version": "==4.1.5" + "version": "==4.2" }, "django-treebeard": { "hashes": [ - "sha256:7c2b1cdb1e9b46d595825186064a1228bc4d00dbbc186db5b0b9412357fba91c", - "sha256:80150017725239702054e5fa64dc66e383dc13ac262c8d47ee5a82cb005969da" + "sha256:84ab35040277d524eb77939e235568c9c856cb523cc9655793b66fe9a3ef468b", + "sha256:89bc79a7d9ae62e8071ff1f866c83b1484c408d6082ce11cf3eca689240712f6" ], "index": "pypi", - "version": "==4.5.1" + "version": "==4.6.1" }, "faker": { "hashes": [ - "sha256:2d5443724f640ce07658ca8ca8bbd40d26b58914e63eec6549727869aa67e2cc", - "sha256:c2a2ff9dd8dfd991109b517ab98d5cb465e857acb45f6b643a0e284a9eb2cc76" + "sha256:170ead9d0d140916168b142df69c44722b8f622ced2070802d0af9c476f0cb84", + "sha256:977ad0b7aa7a61ed57287d6a0723a827e9d3dd1f8cc82aaf08707f281b33bacc" ], "index": "pypi", - "version": "==15.3.4" + "version": "==18.4.0" }, "markdown": { "hashes": [ - "sha256:08fb8465cffd03d10b9dd34a5c3fea908e20391a2a90b88d66362cb05beed186", - "sha256:3b809086bb6efad416156e00a0da66fe47618a5d6918dd688f53f40c8e4cfeff" + "sha256:065fd4df22da73a625f14890dd77eb8040edcbd68794bcd35943be14490608b2", + "sha256:8bf101198e004dc93e84a12a7395e31aac6a9c9942848ae1d99b9d72cf9b3520" ], "index": "pypi", - "version": "==3.4.1" + "version": "==3.4.3" }, "ofxtools": { "hashes": [ @@ -65,86 +65,75 @@ }, "pillow": { "hashes": [ - "sha256:013016af6b3a12a2f40b704677f8b51f72cb007dac785a9933d5c86a72a7fe33", - "sha256:0845adc64fe9886db00f5ab68c4a8cd933ab749a87747555cec1c95acea64b0b", - "sha256:0884ba7b515163a1a05440a138adeb722b8a6ae2c2b33aea93ea3118dd3a899e", - "sha256:09b89ddc95c248ee788328528e6a2996e09eaccddeeb82a5356e92645733be35", - "sha256:0dd4c681b82214b36273c18ca7ee87065a50e013112eea7d78c7a1b89a739153", - "sha256:0e51f608da093e5d9038c592b5b575cadc12fd748af1479b5e858045fff955a9", - "sha256:0f3269304c1a7ce82f1759c12ce731ef9b6e95b6df829dccd9fe42912cc48569", - "sha256:16a8df99701f9095bea8a6c4b3197da105df6f74e6176c5b410bc2df2fd29a57", - "sha256:19005a8e58b7c1796bc0167862b1f54a64d3b44ee5d48152b06bb861458bc0f8", - "sha256:1b4b4e9dda4f4e4c4e6896f93e84a8f0bcca3b059de9ddf67dac3c334b1195e1", - "sha256:28676836c7796805914b76b1837a40f76827ee0d5398f72f7dcc634bae7c6264", - "sha256:2968c58feca624bb6c8502f9564dd187d0e1389964898f5e9e1fbc8533169157", - "sha256:3f4cc516e0b264c8d4ccd6b6cbc69a07c6d582d8337df79be1e15a5056b258c9", - "sha256:3fa1284762aacca6dc97474ee9c16f83990b8eeb6697f2ba17140d54b453e133", - "sha256:43521ce2c4b865d385e78579a082b6ad1166ebed2b1a2293c3be1d68dd7ca3b9", - "sha256:451f10ef963918e65b8869e17d67db5e2f4ab40e716ee6ce7129b0cde2876eab", - "sha256:46c259e87199041583658457372a183636ae8cd56dbf3f0755e0f376a7f9d0e6", - "sha256:46f39cab8bbf4a384ba7cb0bc8bae7b7062b6a11cfac1ca4bc144dea90d4a9f5", - "sha256:519e14e2c49fcf7616d6d2cfc5c70adae95682ae20f0395e9280db85e8d6c4df", - "sha256:53dcb50fbdc3fb2c55431a9b30caeb2f7027fcd2aeb501459464f0214200a503", - "sha256:54614444887e0d3043557d9dbc697dbb16cfb5a35d672b7a0fcc1ed0cf1c600b", - "sha256:575d8912dca808edd9acd6f7795199332696d3469665ef26163cd090fa1f8bfa", - "sha256:5dd5a9c3091a0f414a963d427f920368e2b6a4c2f7527fdd82cde8ef0bc7a327", - "sha256:5f532a2ad4d174eb73494e7397988e22bf427f91acc8e6ebf5bb10597b49c493", - "sha256:60e7da3a3ad1812c128750fc1bc14a7ceeb8d29f77e0a2356a8fb2aa8925287d", - "sha256:653d7fb2df65efefbcbf81ef5fe5e5be931f1ee4332c2893ca638c9b11a409c4", - "sha256:6663977496d616b618b6cfa43ec86e479ee62b942e1da76a2c3daa1c75933ef4", - "sha256:6abfb51a82e919e3933eb137e17c4ae9c0475a25508ea88993bb59faf82f3b35", - "sha256:6c6b1389ed66cdd174d040105123a5a1bc91d0aa7059c7261d20e583b6d8cbd2", - "sha256:6d9dfb9959a3b0039ee06c1a1a90dc23bac3b430842dcb97908ddde05870601c", - "sha256:765cb54c0b8724a7c12c55146ae4647e0274a839fb6de7bcba841e04298e1011", - "sha256:7a21222644ab69ddd9967cfe6f2bb420b460dae4289c9d40ff9a4896e7c35c9a", - "sha256:7ac7594397698f77bce84382929747130765f66406dc2cd8b4ab4da68ade4c6e", - "sha256:7cfc287da09f9d2a7ec146ee4d72d6ea1342e770d975e49a8621bf54eaa8f30f", - "sha256:83125753a60cfc8c412de5896d10a0a405e0bd88d0470ad82e0869ddf0cb3848", - "sha256:847b114580c5cc9ebaf216dd8c8dbc6b00a3b7ab0131e173d7120e6deade1f57", - "sha256:87708d78a14d56a990fbf4f9cb350b7d89ee8988705e58e39bdf4d82c149210f", - "sha256:8a2b5874d17e72dfb80d917213abd55d7e1ed2479f38f001f264f7ce7bae757c", - "sha256:8f127e7b028900421cad64f51f75c051b628db17fb00e099eb148761eed598c9", - "sha256:94cdff45173b1919350601f82d61365e792895e3c3a3443cf99819e6fbf717a5", - "sha256:99d92d148dd03fd19d16175b6d355cc1b01faf80dae93c6c3eb4163709edc0a9", - "sha256:9a3049a10261d7f2b6514d35bbb7a4dfc3ece4c4de14ef5876c4b7a23a0e566d", - "sha256:9d9a62576b68cd90f7075876f4e8444487db5eeea0e4df3ba298ee38a8d067b0", - "sha256:9e5f94742033898bfe84c93c831a6f552bb629448d4072dd312306bab3bd96f1", - "sha256:a1c2d7780448eb93fbcc3789bf3916aa5720d942e37945f4056680317f1cd23e", - "sha256:a2e0f87144fcbbe54297cae708c5e7f9da21a4646523456b00cc956bd4c65815", - "sha256:a4dfdae195335abb4e89cc9762b2edc524f3c6e80d647a9a81bf81e17e3fb6f0", - "sha256:a96e6e23f2b79433390273eaf8cc94fec9c6370842e577ab10dabdcc7ea0a66b", - "sha256:aabdab8ec1e7ca7f1434d042bf8b1e92056245fb179790dc97ed040361f16bfd", - "sha256:b222090c455d6d1a64e6b7bb5f4035c4dff479e22455c9eaa1bdd4c75b52c80c", - "sha256:b52ff4f4e002f828ea6483faf4c4e8deea8d743cf801b74910243c58acc6eda3", - "sha256:b70756ec9417c34e097f987b4d8c510975216ad26ba6e57ccb53bc758f490dab", - "sha256:b8c2f6eb0df979ee99433d8b3f6d193d9590f735cf12274c108bd954e30ca858", - "sha256:b9b752ab91e78234941e44abdecc07f1f0d8f51fb62941d32995b8161f68cfe5", - "sha256:ba6612b6548220ff5e9df85261bddc811a057b0b465a1226b39bfb8550616aee", - "sha256:bd752c5ff1b4a870b7661234694f24b1d2b9076b8bf337321a814c612665f343", - "sha256:c3c4ed2ff6760e98d262e0cc9c9a7f7b8a9f61aa4d47c58835cdaf7b0b8811bb", - "sha256:c5c1362c14aee73f50143d74389b2c158707b4abce2cb055b7ad37ce60738d47", - "sha256:cb362e3b0976dc994857391b776ddaa8c13c28a16f80ac6522c23d5257156bed", - "sha256:d197df5489004db87d90b918033edbeee0bd6df3848a204bca3ff0a903bef837", - "sha256:d3b56206244dc8711f7e8b7d6cad4663917cd5b2d950799425076681e8766286", - "sha256:d5b2f8a31bd43e0f18172d8ac82347c8f37ef3e0b414431157718aa234991b28", - "sha256:d7081c084ceb58278dd3cf81f836bc818978c0ccc770cbbb202125ddabec6628", - "sha256:db74f5562c09953b2c5f8ec4b7dfd3f5421f31811e97d1dbc0a7c93d6e3a24df", - "sha256:df41112ccce5d47770a0c13651479fbcd8793f34232a2dd9faeccb75eb5d0d0d", - "sha256:e1339790c083c5a4de48f688b4841f18df839eb3c9584a770cbd818b33e26d5d", - "sha256:e621b0246192d3b9cb1dc62c78cfa4c6f6d2ddc0ec207d43c0dedecb914f152a", - "sha256:e8c5cf126889a4de385c02a2c3d3aba4b00f70234bfddae82a5eaa3ee6d5e3e6", - "sha256:e9d7747847c53a16a729b6ee5e737cf170f7a16611c143d95aa60a109a59c336", - "sha256:eaef5d2de3c7e9b21f1e762f289d17b726c2239a42b11e25446abf82b26ac132", - "sha256:ed3e4b4e1e6de75fdc16d3259098de7c6571b1a6cc863b1a49e7d3d53e036070", - "sha256:ef21af928e807f10bf4141cad4746eee692a0dd3ff56cfb25fce076ec3cc8abe", - "sha256:f09598b416ba39a8f489c124447b007fe865f786a89dbfa48bb5cf395693132a", - "sha256:f0caf4a5dcf610d96c3bd32932bfac8aee61c96e60481c2a0ea58da435e25acd", - "sha256:f6e78171be3fb7941f9910ea15b4b14ec27725865a73c15277bc39f5ca4f8391", - "sha256:f715c32e774a60a337b2bb8ad9839b4abf75b267a0f18806f6f4f5f1688c4b5a", - "sha256:fb5c1ad6bad98c57482236a21bf985ab0ef42bd51f7ad4e4538e89a997624e12" + "sha256:07999f5834bdc404c442146942a2ecadd1cb6292f5229f4ed3b31e0a108746b1", + "sha256:0852ddb76d85f127c135b6dd1f0bb88dbb9ee990d2cd9aa9e28526c93e794fba", + "sha256:1781a624c229cb35a2ac31cc4a77e28cafc8900733a864870c49bfeedacd106a", + "sha256:1e7723bd90ef94eda669a3c2c19d549874dd5badaeefabefd26053304abe5799", + "sha256:229e2c79c00e85989a34b5981a2b67aa079fd08c903f0aaead522a1d68d79e51", + "sha256:22baf0c3cf0c7f26e82d6e1adf118027afb325e703922c8dfc1d5d0156bb2eeb", + "sha256:252a03f1bdddce077eff2354c3861bf437c892fb1832f75ce813ee94347aa9b5", + "sha256:2dfaaf10b6172697b9bceb9a3bd7b951819d1ca339a5ef294d1f1ac6d7f63270", + "sha256:322724c0032af6692456cd6ed554bb85f8149214d97398bb80613b04e33769f6", + "sha256:35f6e77122a0c0762268216315bf239cf52b88865bba522999dc38f1c52b9b47", + "sha256:375f6e5ee9620a271acb6820b3d1e94ffa8e741c0601db4c0c4d3cb0a9c224bf", + "sha256:3ded42b9ad70e5f1754fb7c2e2d6465a9c842e41d178f262e08b8c85ed8a1d8e", + "sha256:432b975c009cf649420615388561c0ce7cc31ce9b2e374db659ee4f7d57a1f8b", + "sha256:482877592e927fd263028c105b36272398e3e1be3269efda09f6ba21fd83ec66", + "sha256:489f8389261e5ed43ac8ff7b453162af39c3e8abd730af8363587ba64bb2e865", + "sha256:54f7102ad31a3de5666827526e248c3530b3a33539dbda27c6843d19d72644ec", + "sha256:560737e70cb9c6255d6dcba3de6578a9e2ec4b573659943a5e7e4af13f298f5c", + "sha256:5671583eab84af046a397d6d0ba25343c00cd50bce03787948e0fff01d4fd9b1", + "sha256:5ba1b81ee69573fe7124881762bb4cd2e4b6ed9dd28c9c60a632902fe8db8b38", + "sha256:5d4ebf8e1db4441a55c509c4baa7a0587a0210f7cd25fcfe74dbbce7a4bd1906", + "sha256:60037a8db8750e474af7ffc9faa9b5859e6c6d0a50e55c45576bf28be7419705", + "sha256:608488bdcbdb4ba7837461442b90ea6f3079397ddc968c31265c1e056964f1ef", + "sha256:6608ff3bf781eee0cd14d0901a2b9cc3d3834516532e3bd673a0a204dc8615fc", + "sha256:662da1f3f89a302cc22faa9f14a262c2e3951f9dbc9617609a47521c69dd9f8f", + "sha256:7002d0797a3e4193c7cdee3198d7c14f92c0836d6b4a3f3046a64bd1ce8df2bf", + "sha256:763782b2e03e45e2c77d7779875f4432e25121ef002a41829d8868700d119392", + "sha256:77165c4a5e7d5a284f10a6efaa39a0ae8ba839da344f20b111d62cc932fa4e5d", + "sha256:7c9af5a3b406a50e313467e3565fc99929717f780164fe6fbb7704edba0cebbe", + "sha256:7ec6f6ce99dab90b52da21cf0dc519e21095e332ff3b399a357c187b1a5eee32", + "sha256:833b86a98e0ede388fa29363159c9b1a294b0905b5128baf01db683672f230f5", + "sha256:84a6f19ce086c1bf894644b43cd129702f781ba5751ca8572f08aa40ef0ab7b7", + "sha256:8507eda3cd0608a1f94f58c64817e83ec12fa93a9436938b191b80d9e4c0fc44", + "sha256:85ec677246533e27770b0de5cf0f9d6e4ec0c212a1f89dfc941b64b21226009d", + "sha256:8aca1152d93dcc27dc55395604dcfc55bed5f25ef4c98716a928bacba90d33a3", + "sha256:8d935f924bbab8f0a9a28404422da8af4904e36d5c33fc6f677e4c4485515625", + "sha256:8f36397bf3f7d7c6a3abdea815ecf6fd14e7fcd4418ab24bae01008d8d8ca15e", + "sha256:91ec6fe47b5eb5a9968c79ad9ed78c342b1f97a091677ba0e012701add857829", + "sha256:965e4a05ef364e7b973dd17fc765f42233415974d773e82144c9bbaaaea5d089", + "sha256:96e88745a55b88a7c64fa49bceff363a1a27d9a64e04019c2281049444a571e3", + "sha256:99eb6cafb6ba90e436684e08dad8be1637efb71c4f2180ee6b8f940739406e78", + "sha256:9adf58f5d64e474bed00d69bcd86ec4bcaa4123bfa70a65ce72e424bfb88ed96", + "sha256:9b1af95c3a967bf1da94f253e56b6286b50af23392a886720f563c547e48e964", + "sha256:a0aa9417994d91301056f3d0038af1199eb7adc86e646a36b9e050b06f526597", + "sha256:a0f9bb6c80e6efcde93ffc51256d5cfb2155ff8f78292f074f60f9e70b942d99", + "sha256:a127ae76092974abfbfa38ca2d12cbeddcdeac0fb71f9627cc1135bedaf9d51a", + "sha256:aaf305d6d40bd9632198c766fb64f0c1a83ca5b667f16c1e79e1661ab5060140", + "sha256:aca1c196f407ec7cf04dcbb15d19a43c507a81f7ffc45b690899d6a76ac9fda7", + "sha256:ace6ca218308447b9077c14ea4ef381ba0b67ee78d64046b3f19cf4e1139ad16", + "sha256:b416f03d37d27290cb93597335a2f85ed446731200705b22bb927405320de903", + "sha256:bf548479d336726d7a0eceb6e767e179fbde37833ae42794602631a070d630f1", + "sha256:c1170d6b195555644f0616fd6ed929dfcf6333b8675fcca044ae5ab110ded296", + "sha256:c380b27d041209b849ed246b111b7c166ba36d7933ec6e41175fd15ab9eb1572", + "sha256:c446d2245ba29820d405315083d55299a796695d747efceb5717a8b450324115", + "sha256:c830a02caeb789633863b466b9de10c015bded434deb3ec87c768e53752ad22a", + "sha256:cb841572862f629b99725ebaec3287fc6d275be9b14443ea746c1dd325053cbd", + "sha256:cfa4561277f677ecf651e2b22dc43e8f5368b74a25a8f7d1d4a3a243e573f2d4", + "sha256:cfcc2c53c06f2ccb8976fb5c71d448bdd0a07d26d8e07e321c103416444c7ad1", + "sha256:d3c6b54e304c60c4181da1c9dadf83e4a54fd266a99c70ba646a9baa626819eb", + "sha256:d3d403753c9d5adc04d4694d35cf0391f0f3d57c8e0030aac09d7678fa8030aa", + "sha256:d9c206c29b46cfd343ea7cdfe1232443072bbb270d6a46f59c259460db76779a", + "sha256:e49eb4e95ff6fd7c0c402508894b1ef0e01b99a44320ba7d8ecbabefddcc5569", + "sha256:f8286396b351785801a976b1e85ea88e937712ee2c3ac653710a4a57a8da5d9c", + "sha256:f8fc330c3370a81bbf3f88557097d1ea26cd8b019d6433aa59f71195f5ddebbf", + "sha256:fbd359831c1657d69bb81f0db962905ee05e5e9451913b18b831febfe0519082", + "sha256:fe7e1c262d3392afcf5071df9afa574544f28eac825284596ac6db56e6d11062", + "sha256:fed1e1cf6a42577953abbe8e6cf2fe2f566daebde7c34724ec8803c4c0cda579" ], "index": "pypi", - "version": "==9.4.0" + "version": "==9.5.0" }, "python-dateutil": { "hashes": [ @@ -172,6 +161,22 @@ } }, "develop": { + "aiofiles": { + "hashes": [ + "sha256:1142fa8e80dbae46bb6339573ad4c8c0841358f79c6eb50a493dceca14621bad", + "sha256:9107f1ca0b2a5553987a94a3c9959fe5b491fdf731389aa5b7b1bd0733e32de6" + ], + "markers": "python_version >= '3.7' and python_version < '4.0'", + "version": "==22.1.0" + }, + "aiosqlite": { + "hashes": [ + "sha256:c3511b841e3a2c5614900ba1d179f366826857586f78abd75e7cbeb88e75a557", + "sha256:faa843ef5fb08bafe9a9b3859012d3d9d6f77ce3637899de20606b7fc39aa213" + ], + "markers": "python_version >= '3.7'", + "version": "==0.18.0" + }, "alabaster": { "hashes": [ "sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3", @@ -188,6 +193,13 @@ "markers": "python_full_version >= '3.6.2'", "version": "==3.6.2" }, + "appdirs": { + "hashes": [ + "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92", + "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e" + ], + "version": "==1.4.3" + }, "argon2-cffi": { "hashes": [ "sha256:8c976986f2c5c0e5000919e6de187906cfd81fb1c72bf9d88c01177e77da7f80", @@ -233,11 +245,11 @@ }, "astroid": { "hashes": [ - "sha256:14c1603c41cc61aae731cad1884a073c4645e26f126d13ac8346113c95577f3b", - "sha256:6afc22718a48a689ca24a97981ad377ba7fb78c133f40335dfd16772f29bcfb1" + "sha256:6e61b85c891ec53b07471aec5878f4ac6446a41e590ede0f2ce095f39f7d49dd", + "sha256:dea89d9f99f491c66ac9c04ebddf91e4acf8bd711722175fe6245c0725cc19bb" ], "markers": "python_full_version >= '3.7.2'", - "version": "==2.13.3" + "version": "==2.15.2" }, "asttokens": { "hashes": [ @@ -248,19 +260,19 @@ }, "attrs": { "hashes": [ - "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836", - "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99" + "sha256:69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79", + "sha256:f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399" ], - "markers": "python_version >= '3.6'", - "version": "==22.2.0" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==19.1.0" }, "babel": { "hashes": [ - "sha256:1ad3eca1c885218f6dce2ab67291178944f810a10a9b5f3cb8382a5a232b64fe", - "sha256:5ef4b3226b0180dedded4229651c8b0e1a3a6a2837d45a073272f313e4cf97f6" + "sha256:b4246fb7677d3b98f501a39d43396d3cafdc8eadb045f4a31be01863f655c610", + "sha256:cc2d99999cd01d44420ae725a21c9e3711b3aadc7976d6147f622d8581963455" ], - "markers": "python_version >= '3.6'", - "version": "==2.11.0" + "markers": "python_version >= '3.7'", + "version": "==2.12.1" }, "backcall": { "hashes": [ @@ -271,11 +283,11 @@ }, "beautifulsoup4": { "hashes": [ - "sha256:58d5c3d29f5a36ffeb94f02f0d786cd53014cf9b3b3951d42e0080d8a9498d30", - "sha256:ad9aa55b65ef2808eb405f46cf74df7fcb7044d5cbc26487f96eb2ef2e436693" + "sha256:492bbc69dca35d12daac71c4db1bfff0c876c00ef4a2ffacce226d4638eb72da", + "sha256:bd2520ca0d9d7d12694a53d44ac482d181b4ec1888909b035a3dbf40d0f57d4a" ], "markers": "python_full_version >= '3.6.0'", - "version": "==4.11.1" + "version": "==4.12.2" }, "behave": { "hashes": [ @@ -285,26 +297,21 @@ "index": "pypi", "version": "==1.2.6" }, - "bleach": { - "hashes": [ - "sha256:085f7f33c15bd408dd9b17a4ad77c577db66d76203e5984b1bd59baeee948b2a", - "sha256:0d03255c47eb9bd2f26aa9bb7f2107732e7e8fe195ca2f64709fcf3b0a4a085c" - ], - "markers": "python_version >= '3.7'", - "version": "==5.0.1" - }, - "cached-property": { + "black": { "hashes": [ - "sha256:9fa5755838eecbb2d234c3aa390bd80fbd3ac6b6869109bfc1b499f7bd89a130", - "sha256:df4f613cf7ad9a588cc381aaf4a512d26265ecebd5eb9e1ba12f1319eb85a6a0" + "sha256:09a9dcb7c46ed496a9850b76e4e825d6049ecd38b611f1224857a79bd985a8cf", + "sha256:68950ffd4d9169716bcb8719a56c07a2f4485354fec061cdd5910aa07369731c" ], - "version": "==1.5.2" + "markers": "python_version >= '3.6'", + "version": "==19.3b0" }, - "cerberus": { + "bleach": { "hashes": [ - "sha256:d1b21b3954b2498d9a79edf16b3170a3ac1021df88d197dc2ce5928ba519237c" + "sha256:1a1a85c1595e07d8db14c5f09f09e6433502c51c595970edc090551f0db99414", + "sha256:33c16e3353dbd13028ab4799a0f89a83f113405c766e9c122df8a06f5b85b3f4" ], - "version": "==1.3.4" + "markers": "python_version >= '3.7'", + "version": "==6.0.0" }, "certifi": { "hashes": [ @@ -383,176 +390,151 @@ ], "version": "==1.15.1" }, - "chardet": { - "hashes": [ - "sha256:0368df2bfd78b5fc20572bb4e9bb7fb53e2c094f60ae9993339e8671d0afb8aa", - "sha256:d3e64f022d254183001eccc5db4040520c0f23b1a3f33d6413e099eb7f126557" - ], - "markers": "python_version >= '3.6'", - "version": "==5.0.0" - }, "charset-normalizer": { "hashes": [ - "sha256:00d3ffdaafe92a5dc603cb9bd5111aaa36dfa187c8285c543be562e61b755f6b", - "sha256:024e606be3ed92216e2b6952ed859d86b4cfa52cd5bc5f050e7dc28f9b43ec42", - "sha256:0298eafff88c99982a4cf66ba2efa1128e4ddaca0b05eec4c456bbc7db691d8d", - "sha256:02a51034802cbf38db3f89c66fb5d2ec57e6fe7ef2f4a44d070a593c3688667b", - "sha256:083c8d17153ecb403e5e1eb76a7ef4babfc2c48d58899c98fcaa04833e7a2f9a", - "sha256:0a11e971ed097d24c534c037d298ad32c6ce81a45736d31e0ff0ad37ab437d59", - "sha256:0bf2dae5291758b6f84cf923bfaa285632816007db0330002fa1de38bfcb7154", - "sha256:0c0a590235ccd933d9892c627dec5bc7511ce6ad6c1011fdf5b11363022746c1", - "sha256:0f438ae3532723fb6ead77e7c604be7c8374094ef4ee2c5e03a3a17f1fca256c", - "sha256:109487860ef6a328f3eec66f2bf78b0b72400280d8f8ea05f69c51644ba6521a", - "sha256:11b53acf2411c3b09e6af37e4b9005cba376c872503c8f28218c7243582df45d", - "sha256:12db3b2c533c23ab812c2b25934f60383361f8a376ae272665f8e48b88e8e1c6", - "sha256:14e76c0f23218b8f46c4d87018ca2e441535aed3632ca134b10239dfb6dadd6b", - "sha256:16a8663d6e281208d78806dbe14ee9903715361cf81f6d4309944e4d1e59ac5b", - "sha256:292d5e8ba896bbfd6334b096e34bffb56161c81408d6d036a7dfa6929cff8783", - "sha256:2c03cc56021a4bd59be889c2b9257dae13bf55041a3372d3295416f86b295fb5", - "sha256:2e396d70bc4ef5325b72b593a72c8979999aa52fb8bcf03f701c1b03e1166918", - "sha256:2edb64ee7bf1ed524a1da60cdcd2e1f6e2b4f66ef7c077680739f1641f62f555", - "sha256:31a9ddf4718d10ae04d9b18801bd776693487cbb57d74cc3458a7673f6f34639", - "sha256:356541bf4381fa35856dafa6a965916e54bed415ad8a24ee6de6e37deccf2786", - "sha256:358a7c4cb8ba9b46c453b1dd8d9e431452d5249072e4f56cfda3149f6ab1405e", - "sha256:37f8febc8ec50c14f3ec9637505f28e58d4f66752207ea177c1d67df25da5aed", - "sha256:39049da0ffb96c8cbb65cbf5c5f3ca3168990adf3551bd1dee10c48fce8ae820", - "sha256:39cf9ed17fe3b1bc81f33c9ceb6ce67683ee7526e65fde1447c772afc54a1bb8", - "sha256:3ae1de54a77dc0d6d5fcf623290af4266412a7c4be0b1ff7444394f03f5c54e3", - "sha256:3b590df687e3c5ee0deef9fc8c547d81986d9a1b56073d82de008744452d6541", - "sha256:3e45867f1f2ab0711d60c6c71746ac53537f1684baa699f4f668d4c6f6ce8e14", - "sha256:3fc1c4a2ffd64890aebdb3f97e1278b0cc72579a08ca4de8cd2c04799a3a22be", - "sha256:4457ea6774b5611f4bed5eaa5df55f70abde42364d498c5134b7ef4c6958e20e", - "sha256:44ba614de5361b3e5278e1241fda3dc1838deed864b50a10d7ce92983797fa76", - "sha256:4a8fcf28c05c1f6d7e177a9a46a1c52798bfe2ad80681d275b10dcf317deaf0b", - "sha256:4b0d02d7102dd0f997580b51edc4cebcf2ab6397a7edf89f1c73b586c614272c", - "sha256:502218f52498a36d6bf5ea77081844017bf7982cdbe521ad85e64cabee1b608b", - "sha256:503e65837c71b875ecdd733877d852adbc465bd82c768a067badd953bf1bc5a3", - "sha256:5995f0164fa7df59db4746112fec3f49c461dd6b31b841873443bdb077c13cfc", - "sha256:59e5686dd847347e55dffcc191a96622f016bc0ad89105e24c14e0d6305acbc6", - "sha256:601f36512f9e28f029d9481bdaf8e89e5148ac5d89cffd3b05cd533eeb423b59", - "sha256:608862a7bf6957f2333fc54ab4399e405baad0163dc9f8d99cb236816db169d4", - "sha256:62595ab75873d50d57323a91dd03e6966eb79c41fa834b7a1661ed043b2d404d", - "sha256:70990b9c51340e4044cfc394a81f614f3f90d41397104d226f21e66de668730d", - "sha256:71140351489970dfe5e60fc621ada3e0f41104a5eddaca47a7acb3c1b851d6d3", - "sha256:72966d1b297c741541ca8cf1223ff262a6febe52481af742036a0b296e35fa5a", - "sha256:74292fc76c905c0ef095fe11e188a32ebd03bc38f3f3e9bcb85e4e6db177b7ea", - "sha256:761e8904c07ad053d285670f36dd94e1b6ab7f16ce62b9805c475b7aa1cffde6", - "sha256:772b87914ff1152b92a197ef4ea40efe27a378606c39446ded52c8f80f79702e", - "sha256:79909e27e8e4fcc9db4addea88aa63f6423ebb171db091fb4373e3312cb6d603", - "sha256:7e189e2e1d3ed2f4aebabd2d5b0f931e883676e51c7624826e0a4e5fe8a0bf24", - "sha256:7eb33a30d75562222b64f569c642ff3dc6689e09adda43a082208397f016c39a", - "sha256:81d6741ab457d14fdedc215516665050f3822d3e56508921cc7239f8c8e66a58", - "sha256:8499ca8f4502af841f68135133d8258f7b32a53a1d594aa98cc52013fff55678", - "sha256:84c3990934bae40ea69a82034912ffe5a62c60bbf6ec5bc9691419641d7d5c9a", - "sha256:87701167f2a5c930b403e9756fab1d31d4d4da52856143b609e30a1ce7160f3c", - "sha256:88600c72ef7587fe1708fd242b385b6ed4b8904976d5da0893e31df8b3480cb6", - "sha256:8ac7b6a045b814cf0c47f3623d21ebd88b3e8cf216a14790b455ea7ff0135d18", - "sha256:8b8af03d2e37866d023ad0ddea594edefc31e827fee64f8de5611a1dbc373174", - "sha256:8c7fe7afa480e3e82eed58e0ca89f751cd14d767638e2550c77a92a9e749c317", - "sha256:8eade758719add78ec36dc13201483f8e9b5d940329285edcd5f70c0a9edbd7f", - "sha256:911d8a40b2bef5b8bbae2e36a0b103f142ac53557ab421dc16ac4aafee6f53dc", - "sha256:93ad6d87ac18e2a90b0fe89df7c65263b9a99a0eb98f0a3d2e079f12a0735837", - "sha256:95dea361dd73757c6f1c0a1480ac499952c16ac83f7f5f4f84f0658a01b8ef41", - "sha256:9ab77acb98eba3fd2a85cd160851816bfce6871d944d885febf012713f06659c", - "sha256:9cb3032517f1627cc012dbc80a8ec976ae76d93ea2b5feaa9d2a5b8882597579", - "sha256:9cf4e8ad252f7c38dd1f676b46514f92dc0ebeb0db5552f5f403509705e24753", - "sha256:9d9153257a3f70d5f69edf2325357251ed20f772b12e593f3b3377b5f78e7ef8", - "sha256:a152f5f33d64a6be73f1d30c9cc82dfc73cec6477ec268e7c6e4c7d23c2d2291", - "sha256:a16418ecf1329f71df119e8a65f3aa68004a3f9383821edcb20f0702934d8087", - "sha256:a60332922359f920193b1d4826953c507a877b523b2395ad7bc716ddd386d866", - "sha256:a8d0fc946c784ff7f7c3742310cc8a57c5c6dc31631269876a88b809dbeff3d3", - "sha256:ab5de034a886f616a5668aa5d098af2b5385ed70142090e2a31bcbd0af0fdb3d", - "sha256:c22d3fe05ce11d3671297dc8973267daa0f938b93ec716e12e0f6dee81591dc1", - "sha256:c2ac1b08635a8cd4e0cbeaf6f5e922085908d48eb05d44c5ae9eabab148512ca", - "sha256:c512accbd6ff0270939b9ac214b84fb5ada5f0409c44298361b2f5e13f9aed9e", - "sha256:c75ffc45f25324e68ab238cb4b5c0a38cd1c3d7f1fb1f72b5541de469e2247db", - "sha256:c95a03c79bbe30eec3ec2b7f076074f4281526724c8685a42872974ef4d36b72", - "sha256:cadaeaba78750d58d3cc6ac4d1fd867da6fc73c88156b7a3212a3cd4819d679d", - "sha256:cd6056167405314a4dc3c173943f11249fa0f1b204f8b51ed4bde1a9cd1834dc", - "sha256:db72b07027db150f468fbada4d85b3b2729a3db39178abf5c543b784c1254539", - "sha256:df2c707231459e8a4028eabcd3cfc827befd635b3ef72eada84ab13b52e1574d", - "sha256:e62164b50f84e20601c1ff8eb55620d2ad25fb81b59e3cd776a1902527a788af", - "sha256:e696f0dd336161fca9adbb846875d40752e6eba585843c768935ba5c9960722b", - "sha256:eaa379fcd227ca235d04152ca6704c7cb55564116f8bc52545ff357628e10602", - "sha256:ebea339af930f8ca5d7a699b921106c6e29c617fe9606fa7baa043c1cdae326f", - "sha256:f4c39b0e3eac288fedc2b43055cfc2ca7a60362d0e5e87a637beac5d801ef478", - "sha256:f5057856d21e7586765171eac8b9fc3f7d44ef39425f85dbcccb13b3ebea806c", - "sha256:f6f45710b4459401609ebebdbcfb34515da4fc2aa886f95107f556ac69a9147e", - "sha256:f97e83fa6c25693c7a35de154681fcc257c1c41b38beb0304b9c4d2d9e164479", - "sha256:f9d0c5c045a3ca9bedfc35dca8526798eb91a07aa7a2c0fee134c6c6f321cbd7", - "sha256:ff6f3db31555657f3163b15a6b7c6938d08df7adbfc9dd13d9d19edad678f1e8" + "sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6", + "sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1", + "sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e", + "sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373", + "sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62", + "sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230", + "sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be", + "sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c", + "sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0", + "sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448", + "sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f", + "sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649", + "sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d", + "sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0", + "sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706", + "sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a", + "sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59", + "sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23", + "sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5", + "sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb", + "sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e", + "sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e", + "sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c", + "sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28", + "sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d", + "sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41", + "sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974", + "sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce", + "sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f", + "sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1", + "sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d", + "sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8", + "sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017", + "sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31", + "sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7", + "sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8", + "sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e", + "sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14", + "sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd", + "sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d", + "sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795", + "sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b", + "sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b", + "sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b", + "sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203", + "sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f", + "sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19", + "sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1", + "sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a", + "sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac", + "sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9", + "sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0", + "sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137", + "sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f", + "sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6", + "sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5", + "sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909", + "sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f", + "sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0", + "sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324", + "sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755", + "sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb", + "sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854", + "sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c", + "sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60", + "sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84", + "sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0", + "sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b", + "sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1", + "sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531", + "sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1", + "sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11", + "sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326", + "sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df", + "sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab" ], - "markers": "python_full_version >= '3.6.0'", - "version": "==3.0.1" + "markers": "python_full_version >= '3.7.0'", + "version": "==3.1.0" }, - "colorama": { + "click": { "hashes": [ - "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", - "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6" + "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", + "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6'", - "version": "==0.4.6" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==7.0" }, "comm": { "hashes": [ - "sha256:3e2f5826578e683999b93716285b3b1f344f157bf75fa9ce0a797564e742f062", - "sha256:9f3abf3515112fa7c55a42a6a5ab358735c9dccc8b5910a9d8e3ef5998130666" + "sha256:16613c6211e20223f215fc6d3b266a247b6e2641bf4e0a3ad34cb1aff2aa3f37", + "sha256:a61efa9daffcfbe66fd643ba966f846a624e4e6d6767eda9cf6e993aadaab93e" ], "markers": "python_version >= '3.6'", - "version": "==0.1.2" + "version": "==0.1.3" }, "cryptography": { "hashes": [ - "sha256:1a6915075c6d3a5e1215eab5d99bcec0da26036ff2102a1038401d6ef5bef25b", - "sha256:1ee1fd0de9851ff32dbbb9362a4d833b579b4a6cc96883e8e6d2ff2a6bc7104f", - "sha256:407cec680e811b4fc829de966f88a7c62a596faa250fc1a4b520a0355b9bc190", - "sha256:50386acb40fbabbceeb2986332f0287f50f29ccf1497bae31cf5c3e7b4f4b34f", - "sha256:6f97109336df5c178ee7c9c711b264c502b905c2d2a29ace99ed761533a3460f", - "sha256:754978da4d0457e7ca176f58c57b1f9de6556591c19b25b8bcce3c77d314f5eb", - "sha256:76c24dd4fd196a80f9f2f5405a778a8ca132f16b10af113474005635fe7e066c", - "sha256:7dacfdeee048814563eaaec7c4743c8aea529fe3dd53127313a792f0dadc1773", - "sha256:80ee674c08aaef194bc4627b7f2956e5ba7ef29c3cc3ca488cf15854838a8f72", - "sha256:844ad4d7c3850081dffba91cdd91950038ee4ac525c575509a42d3fc806b83c8", - "sha256:875aea1039d78557c7c6b4db2fe0e9d2413439f4676310a5f269dd342ca7a717", - "sha256:887cbc1ea60786e534b00ba8b04d1095f4272d380ebd5f7a7eb4cc274710fad9", - "sha256:ad04f413436b0781f20c52a661660f1e23bcd89a0e9bb1d6d20822d048cf2856", - "sha256:bae6c7f4a36a25291b619ad064a30a07110a805d08dc89984f4f441f6c1f3f96", - "sha256:c52a1a6f81e738d07f43dab57831c29e57d21c81a942f4602fac7ee21b27f288", - "sha256:e0a05aee6a82d944f9b4edd6a001178787d1546ec7c6223ee9a848a7ade92e39", - "sha256:e324de6972b151f99dc078defe8fb1b0a82c6498e37bff335f5bc6b1e3ab5a1e", - "sha256:e5d71c5d5bd5b5c3eebcf7c5c2bb332d62ec68921a8c593bea8c394911a005ce", - "sha256:f3ed2d864a2fa1666e749fe52fb8e23d8e06b8012e8bd8147c73797c506e86f1", - "sha256:f671c1bb0d6088e94d61d80c606d65baacc0d374e67bf895148883461cd848de", - "sha256:f6c0db08d81ead9576c4d94bbb27aed8d7a430fa27890f39084c2d0e2ec6b0df", - "sha256:f964c7dcf7802d133e8dbd1565914fa0194f9d683d82411989889ecd701e8adf", - "sha256:fec8b932f51ae245121c4671b4bbc030880f363354b2f0e0bd1366017d891458" + "sha256:0a4e3406cfed6b1f6d6e87ed243363652b2586b2d917b0609ca4f97072994405", + "sha256:1e0af458515d5e4028aad75f3bb3fe7a31e46ad920648cd59b64d3da842e4356", + "sha256:2803f2f8b1e95f614419926c7e6f55d828afc614ca5ed61543877ae668cc3472", + "sha256:28d63d75bf7ae4045b10de5413fb1d6338616e79015999ad9cf6fc538f772d41", + "sha256:32057d3d0ab7d4453778367ca43e99ddb711770477c4f072a51b3ca69602780a", + "sha256:3a4805a4ca729d65570a1b7cac84eac1e431085d40387b7d3bbaa47e39890b88", + "sha256:63dac2d25c47f12a7b8aa60e528bfb3c51c5a6c5a9f7c86987909c6c79765554", + "sha256:650883cc064297ef3676b1db1b7b1df6081794c4ada96fa457253c4cc40f97db", + "sha256:6f2bbd72f717ce33100e6467572abaedc61f1acb87b8d546001328d7f466b778", + "sha256:7c872413353c70e0263a9368c4993710070e70ab3e5318d85510cc91cce77e7c", + "sha256:918cb89086c7d98b1b86b9fdb70c712e5a9325ba6f7d7cfb509e784e0cfc6917", + "sha256:9618a87212cb5200500e304e43691111570e1f10ec3f35569fdfcd17e28fd797", + "sha256:a805a7bce4a77d51696410005b3e85ae2839bad9aa38894afc0aa99d8e0c3160", + "sha256:cc3a621076d824d75ab1e1e530e66e7e8564e357dd723f2533225d40fe35c60c", + "sha256:cd033d74067d8928ef00a6b1327c8ea0452523967ca4463666eeba65ca350d4c", + "sha256:cf91e428c51ef692b82ce786583e214f58392399cf65c341bc7301d096fa3ba2", + "sha256:d36bbeb99704aabefdca5aee4eba04455d7a27ceabd16f3b3ba9bdcc31da86c4", + "sha256:d8aa3609d337ad85e4eb9bb0f8bcf6e4409bfb86e706efa9a027912169e89122", + "sha256:f5d7b79fa56bc29580faafc2ff736ce05ba31feaa9d4735048b0de7d9ceb2b94" ], "markers": "python_version >= '3.6'", - "version": "==39.0.0" + "version": "==40.0.1" }, "debugpy": { "hashes": [ - "sha256:048368f121c08b00bbded161e8583817af5055982d2722450a69efe2051621c2", - "sha256:0f9afcc8cad6424695f3356dc9a7406d5b18e37ee2e73f34792881a44b02cc50", - "sha256:15bc5febe0edc79726517b1f8d57d7ac7c784567b5ba804aab8b1c9d07a57018", - "sha256:17039e392d6f38388a68bd02c5f823b32a92142a851e96ba3ec52aeb1ce9d900", - "sha256:286ae0c2def18ee0dc8a61fa76d51039ca8c11485b6ed3ef83e3efe8a23926ae", - "sha256:377391341c4b86f403d93e467da8e2d05c22b683f08f9af3e16d980165b06b90", - "sha256:500dd4a9ff818f5c52dddb4a608c7de5371c2d7d905c505eb745556c579a9f11", - "sha256:5e55e6c79e215239dd0794ee0bf655412b934735a58e9d705e5c544f596f1603", - "sha256:62a06eb78378292ba6c427d861246574dc8b84471904973797b29dd33c7c2495", - "sha256:696165f021a6a17da08163eaae84f3faf5d8be68fb78cd78488dd347e625279c", - "sha256:74e4eca42055759032e3f1909d1374ba1d729143e0c2729bb8cb5e8b5807c458", - "sha256:7e84d9e4420122384cb2cc762a00b4e17cbf998022890f89b195ce178f78ff47", - "sha256:8116e40a1cd0593bd2aba01d4d560ee08f018da8e8fbd4cbd24ff09b5f0e41ef", - "sha256:8f3fab217fe7e2acb2d90732af1a871947def4e2b6654945ba1ebd94bd0bea26", - "sha256:947c686e8adb46726f3d5f19854f6aebf66c2edb91225643c7f44b40b064a235", - "sha256:9984fc00ab372c97f63786c400107f54224663ea293daab7b365a5b821d26309", - "sha256:9e809ef787802c808995e5b6ade714a25fa187f892b41a412d418a15a9c4a432", - "sha256:b5a74ecebe5253344501d9b23f74459c46428b30437fa9254cfb8cb129943242" + "sha256:0679b7e1e3523bd7d7869447ec67b59728675aadfc038550a63a362b63029d2c", + "sha256:279d64c408c60431c8ee832dfd9ace7c396984fd7341fa3116aee414e7dcd88d", + "sha256:33edb4afa85c098c24cc361d72ba7c21bb92f501104514d4ffec1fb36e09c01a", + "sha256:38ed626353e7c63f4b11efad659be04c23de2b0d15efff77b60e4740ea685d07", + "sha256:5224eabbbeddcf1943d4e2821876f3e5d7d383f27390b82da5d9558fd4eb30a9", + "sha256:53f7a456bc50706a0eaabecf2d3ce44c4d5010e46dfc65b6b81a518b42866267", + "sha256:9cd10cf338e0907fdcf9eac9087faa30f150ef5445af5a545d307055141dd7a4", + "sha256:aaf6da50377ff4056c8ed470da24632b42e4087bc826845daad7af211e00faad", + "sha256:b3e7ac809b991006ad7f857f016fa92014445085711ef111fdc3f74f66144096", + "sha256:bae1123dff5bfe548ba1683eb972329ba6d646c3a80e6b4c06cd1b1dd0205e9b", + "sha256:c0ff93ae90a03b06d85b2c529eca51ab15457868a377c4cc40a23ab0e4e552a3", + "sha256:c4c2f0810fa25323abfdfa36cbbbb24e5c3b1a42cb762782de64439c575d67f2", + "sha256:d71b31117779d9a90b745720c0eab54ae1da76d5b38c8026c654f4a066b0130a", + "sha256:dbe04e7568aa69361a5b4c47b4493d5680bfa3a911d1e105fbea1b1f23f3eb45", + "sha256:de86029696e1b3b4d0d49076b9eba606c226e33ae312a57a46dca14ff370894d", + "sha256:e3876611d114a18aafef6383695dfc3f1217c98a9168c1aaf1a02b01ec7d8d1e", + "sha256:ed6d5413474e209ba50b1a75b2d9eecf64d41e6e4501977991cdc755dc83ab0f", + "sha256:f90a2d4ad9a035cee7331c06a4cf2245e38bd7c89554fe3b616d90ab8aab89cc" ], "markers": "python_version >= '3.7'", - "version": "==1.6.5" + "version": "==1.6.7" }, "decorator": { "hashes": [ @@ -578,13 +560,6 @@ "markers": "python_version >= '3.11'", "version": "==0.3.6" }, - "distlib": { - "hashes": [ - "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46", - "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e" - ], - "version": "==0.3.6" - }, "docutils": { "hashes": [ "sha256:33995a6753c30b7f577febfc2c50411fec6aac7f7ffeb7c4cfe5991072dcf9e6", @@ -593,14 +568,6 @@ "markers": "python_version >= '3.7'", "version": "==0.19" }, - "entrypoints": { - "hashes": [ - "sha256:b706eddaa9218a19ebcd67b56818f05bb27589b1ca9e8d797b74affad4ccacd4", - "sha256:f174b5ff827504fd3cd97cc3f8649f3693f51538c7e4bdf3ef002c8429d42f9f" - ], - "markers": "python_version >= '3.6'", - "version": "==0.4" - }, "executing": { "hashes": [ "sha256:0314a69e37426e3608aada02473b4161d4caf5a4b244d1d0c48072b8fee7bacc", @@ -610,10 +577,10 @@ }, "fastjsonschema": { "hashes": [ - "sha256:01e366f25d9047816fe3d288cbfc3e10541daf0af2044763f3d0ade42476da18", - "sha256:21f918e8d9a1a4ba9c22e09574ba72267a6762d47822db9add95f6454e51cc1c" + "sha256:04fbecc94300436f628517b05741b7ea009506ce8f946d40996567c669318490", + "sha256:4a30d6315a68c253cfa8f963b9697246315aa3db89f98b97235e345dedfb0b8e" ], - "version": "==2.16.2" + "version": "==2.16.3" }, "fqdn": { "hashes": [ @@ -624,11 +591,11 @@ }, "furo": { "hashes": [ - "sha256:7cb76c12a25ef65db85ab0743df907573d03027a33631f17d267e598ebb191f7", - "sha256:d8008f8efbe7587a97ba533c8b2df1f9c21ee9b3e5cad0d27f61193d38b1a986" + "sha256:4ab2be254a2d5e52792d0ca793a12c35582dd09897228a6dd47885dabd5c9521", + "sha256:b99e7867a5cc833b2b34d7230631dd6558c7a29f93071fdbb5709634bb33c5a5" ], "index": "pypi", - "version": "==2022.12.7" + "version": "==2023.3.27" }, "idna": { "hashes": [ @@ -648,27 +615,27 @@ }, "importlib-metadata": { "hashes": [ - "sha256:7efb448ec9a5e313a57655d35aa54cd3e01b7e1fbcf72dce1bf06119420f5bad", - "sha256:e354bedeb60efa6affdcc8ae121b73544a7aa74156d047311948f6d711cd378d" + "sha256:23c2bcae4762dfb0bbe072d358faec24957901d75b6c4ab11172c0c982532402", + "sha256:8f8bd2af397cf33bd344d35cfe7f489219b7d14fc79a3f854b75b8417e9226b0" ], "markers": "python_version >= '3.7'", - "version": "==6.0.0" + "version": "==6.3.0" }, "ipykernel": { "hashes": [ - "sha256:1893c5b847033cd7a58f6843b04a9349ffb1031bc6588401cadc9adb58da428e", - "sha256:5d0675d5f48bf6a95fd517d7b70bcb3b2c5631b2069949b5c2d6e1d7477fb5a0" + "sha256:1ae6047c1277508933078163721bbb479c3e7292778a04b4bacf0874550977d6", + "sha256:302558b81f1bc22dc259fb2a0c5c7cf2f4c0bdb21b50484348f7bafe7fb71421" ], "markers": "python_version >= '3.8'", - "version": "==6.20.2" + "version": "==6.22.0" }, "ipython": { "hashes": [ - "sha256:da01e6df1501e6e7c32b5084212ddadd4ee2471602e2cf3e0190f4de6b0ea481", - "sha256:f3bf2c08505ad2c3f4ed5c46ae0331a8547d36bf4b21a451e8ae80c0791db95b" + "sha256:1c183bf61b148b00bcebfa5d9b39312733ae97f6dad90d7e9b4d86c8647f498c", + "sha256:a950236df04ad75b5bc7f816f9af3d74dc118fd42f2ff7e80e8e60ca1f182e2d" ], "markers": "python_version >= '3.8'", - "version": "==8.8.0" + "version": "==8.12.0" }, "ipython-genutils": { "hashes": [ @@ -686,11 +653,11 @@ }, "isort": { "hashes": [ - "sha256:6db30c5ded9815d813932c04c2f85a360bcdd35fed496f4d8f35495ef0a261b6", - "sha256:c033fd0edb91000a7f09527fe5c75321878f98322a77ddcc81adbd83724afb7b" + "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504", + "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6" ], - "markers": "python_full_version >= '3.7.0'", - "version": "==5.11.4" + "markers": "python_full_version >= '3.8.0'", + "version": "==5.12.0" }, "jaraco.classes": { "hashes": [ @@ -748,19 +715,19 @@ }, "jupyter-client": { "hashes": [ - "sha256:214668aaea208195f4c13d28eb272ba79f945fc0cf3f11c7092c20b2ca1980e7", - "sha256:52be28e04171f07aed8f20e1616a5a552ab9fee9cbbe6c1896ae170c3880d392" + "sha256:3fbab64100a0dcac7701b1e0f1a4412f1ccb45546ff2ad9bc4fcbe4e19804811", + "sha256:d5b8e739d7816944be50f81121a109788a3d92732ecf1ad1e4dadebc948818fe" ], - "markers": "python_version >= '3.7'", - "version": "==7.4.9" + "markers": "python_version >= '3.8'", + "version": "==8.1.0" }, "jupyter-core": { "hashes": [ - "sha256:82e1cff0ef804c38677eff7070d5ff1d45037fef01a2d9ba9e6b7b8201831e9f", - "sha256:d23ab7db81ca1759f13780cd6b65f37f59bf8e0186ac422d5ca4982cc7d56716" + "sha256:6db75be0c83edbf1b7c9f91ec266a9a24ef945da630f3120e1a0046dc13713fc", + "sha256:d4201af84559bc8c70cead287e1ab94aeef3c512848dde077b7684b54d67730d" ], "markers": "python_version >= '3.8'", - "version": "==5.1.3" + "version": "==5.3.0" }, "jupyter-events": { "hashes": [ @@ -772,11 +739,19 @@ }, "jupyter-server": { "hashes": [ - "sha256:90cd6f2bd0581ddd9b2dbe82026a0f4c228a1d95c86e22460efbfdfc931fcf56", - "sha256:efaae5e4f0d5f22c7f2f2dc848635036ee74a2df02abed52d30d9d95121ad382" + "sha256:9fde612791f716fd34d610cd939704a9639643744751ba66e7ee8fdc9cead07e", + "sha256:e6bc1e9e96d7c55b9ce9699ff6cb9a910581fe7349e27c40389acb67632e24c0" ], "markers": "python_version >= '3.8'", - "version": "==2.1.0" + "version": "==2.5.0" + }, + "jupyter-server-fileid": { + "hashes": [ + "sha256:171538b7c7d08d11dbc57d4e6da196e0c258e4c2cd29249ef1e032bb423677f8", + "sha256:5b489c6fe6783c41174a728c7b81099608518387e53c3d53451a67f46a0cb7b0" + ], + "markers": "python_version >= '3.7'", + "version": "==0.9.0" }, "jupyter-server-terminals": { "hashes": [ @@ -786,13 +761,29 @@ "markers": "python_version >= '3.8'", "version": "==0.4.4" }, + "jupyter-server-ydoc": { + "hashes": [ + "sha256:969a3a1a77ed4e99487d60a74048dc9fa7d3b0dcd32e60885d835bbf7ba7be11", + "sha256:a6fe125091792d16c962cc3720c950c2b87fcc8c3ecf0c54c84e9a20b814526c" + ], + "markers": "python_version >= '3.7'", + "version": "==0.8.0" + }, + "jupyter-ydoc": { + "hashes": [ + "sha256:3ac51abfe378c6aeb62a449e8f0241bede1205f0199b0d27429140cbba950f79", + "sha256:98db7785215873c64d7dfcb1b741f41df11994c4b3d7e2957e004b392d6f11ea" + ], + "markers": "python_version >= '3.7'", + "version": "==0.2.3" + }, "jupyterlab": { "hashes": [ - "sha256:10ac094215ffb872ddffbe2982bf1c039a79fecc326e191e7cc5efd84f331dad", - "sha256:16e9b8320dcec469c70bb883e993e0bb84c4ea1a734063731f66922cf72add1b" + "sha256:373e9cfb8a72edd294be14f16662563a220cecf0fb26de7aab1af9a29b689b82", + "sha256:6aba0caa771697d02fbf409f9767b2fdb4ee32ce935940e3b9a0d5d48d994d0f" ], "index": "pypi", - "version": "==3.5.2" + "version": "==3.6.3" }, "jupyterlab-pygments": { "hashes": [ @@ -804,11 +795,11 @@ }, "jupyterlab-server": { "hashes": [ - "sha256:635a0b176a901f19351c02221a124e59317c476f511200409b7d867e8b2905c3", - "sha256:d18eb623428b4ee732c2258afaa365eedd70f38b609981ea040027914df32bc6" + "sha256:0f9f6752b0c534a7b22a6542b984fa6a2c18ab4d4e0a4c79f191138506a9a75f", + "sha256:f4a7263ada89958854631a64bed45285caeac482925233159709f643c5871490" ], "markers": "python_version >= '3.7'", - "version": "==2.16.3" + "version": "==2.22.0" }, "keyring": { "hashes": [ @@ -862,11 +853,11 @@ }, "markdown-it-py": { "hashes": [ - "sha256:93de681e5c021a432c63147656fe21790bc01231e0cd2da73626f1aa3ac0fe27", - "sha256:cf7e59fed14b5ae17c0006eff14a2d9a00ed5f3a846148153899a0224e2c07da" + "sha256:5a35f8d1870171d9acc47b99612dc146129b631baf04970128b568f190d0cc30", + "sha256:7c9a5e412688bc771c67432cbfebcdd686c93ce6484913dccf06cb5a0bea35a1" ], "markers": "python_version >= '3.7'", - "version": "==2.1.0" + "version": "==2.2.0" }, "markupsafe": { "hashes": [ @@ -950,50 +941,50 @@ }, "mistune": { "hashes": [ - "sha256:182cc5ee6f8ed1b807de6b7bb50155df7b66495412836b9a74c8fbdfc75fe36d", - "sha256:9ee0a66053e2267aba772c71e06891fa8f1af6d4b01d5e84e267b4570d4d9808" + "sha256:0246113cb2492db875c6be56974a7c893333bf26cd92891c85f63151cee09d34", + "sha256:bad7f5d431886fcbaf5f758118ecff70d31f75231b34024a1341120340a65ce8" ], - "version": "==2.0.4" + "version": "==2.0.5" }, "more-itertools": { "hashes": [ - "sha256:250e83d7e81d0c87ca6bd942e6aeab8cc9daa6096d12c5308f3f92fa5e5c1f41", - "sha256:5a6257e40878ef0520b1803990e3e22303a41b5714006c32a3fd8304b26ea1ab" + "sha256:cabaa341ad0389ea83c17a94566a53ae4c9d07349861ecb14dc6d0345cf9ac5d", + "sha256:d2bc7f02446e86a68911e58ded76d6561eea00cddfb2a91e7019bbb586c799f3" ], "markers": "python_version >= '3.7'", - "version": "==9.0.0" + "version": "==9.1.0" }, "nbclassic": { "hashes": [ - "sha256:c74d8a500f8e058d46b576a41e5bc640711e1032cf7541dde5f73ea49497e283", - "sha256:cbf05df5842b420d5cece0143462380ea9d308ff57c2dc0eb4d6e035b18fbfb3" + "sha256:47791b04dbcb89bf7fde910a3d848fd4793a4248a8936202453631a87da37d51", + "sha256:d2c91adc7909b0270c73e3e253d3687a6704b4f0a94bc156a37c85eba09f4d37" ], "markers": "python_version >= '3.7'", - "version": "==0.4.8" + "version": "==0.5.5" }, "nbclient": { "hashes": [ - "sha256:884a3f4a8c4fc24bb9302f263e0af47d97f0d01fe11ba714171b320c8ac09547", - "sha256:d97ac6257de2794f5397609df754fcbca1a603e94e924eb9b99787c031ae2e7c" + "sha256:26e41c6dca4d76701988bc34f64e1bfc2413ae6d368f13d7b5ac407efb08c755", + "sha256:8fa96f7e36693d5e83408f5e840f113c14a45c279befe609904dbe05dad646d1" ], "markers": "python_full_version >= '3.7.0'", - "version": "==0.7.2" + "version": "==0.7.3" }, "nbconvert": { "hashes": [ - "sha256:ac57f2812175441a883f50c8ff113133ca65fe7ae5a9f1e3da3bfd1a70dce2ee", - "sha256:ccedacde57a972836bfb46466485be29ed1364ed7c2f379f62bad47d340ece99" + "sha256:78685362b11d2e8058e70196fe83b09abed8df22d3e599cf271f4d39fdc48b9e", + "sha256:d2e95904666f1ff77d36105b9de4e0801726f93b862d5b28f69e93d99ad3b19c" ], "markers": "python_version >= '3.7'", - "version": "==7.2.8" + "version": "==7.3.1" }, "nbformat": { "hashes": [ - "sha256:22a98a6516ca216002b0a34591af5bcb8072ca6c63910baffc901cfa07fefbf0", - "sha256:4b021fca24d3a747bf4e626694033d792d594705829e5e35b14ee3369f9f6477" + "sha256:46dac64c781f1c34dfd8acba16547024110348f9fc7eab0f31981c2a3dc48d1f", + "sha256:d910082bd3e0bffcf07eabf3683ed7dda0727a326c446eeb2922abe102e65162" ], "markers": "python_version >= '3.7'", - "version": "==5.7.3" + "version": "==5.8.0" }, "nest-asyncio": { "hashes": [ @@ -1005,11 +996,11 @@ }, "notebook": { "hashes": [ - "sha256:c1897e5317e225fc78b45549a6ab4b668e4c996fd03a04e938fe5e7af2bfffd0", - "sha256:e04f9018ceb86e4fa841e92ea8fb214f8d23c1cedfde530cc96f92446924f0e4" + "sha256:517209568bd47261e2def27a140e97d49070602eea0d226a696f42a7f16c9a4e", + "sha256:dd17e78aefe64c768737b32bf171c1c766666a21cc79a44d37a1700771cab56f" ], "markers": "python_version >= '3.7'", - "version": "==6.5.2" + "version": "==6.5.4" }, "notebook-shim": { "hashes": [ @@ -1019,20 +1010,13 @@ "markers": "python_version >= '3.7'", "version": "==0.2.2" }, - "orderedmultidict": { - "hashes": [ - "sha256:04070bbb5e87291cc9bfa51df413677faf2141c73c61d2a5f7b26bea3cd882ad", - "sha256:43c839a17ee3cdd62234c47deca1a8508a3f2ca1d0678a3bf791c87cf84adbf3" - ], - "version": "==1.0.1" - }, "packaging": { "hashes": [ - "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5", - "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a" + "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2", + "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==20.9" + "markers": "python_version >= '3.7'", + "version": "==23.0" }, "pandocfilters": { "hashes": [ @@ -1064,14 +1048,6 @@ "markers": "python_version >= '3.6'", "version": "==0.8.3" }, - "pep517": { - "hashes": [ - "sha256:4ba4446d80aed5b5eac6509ade100bff3e7943a8489de249654a5ae9b33ee35b", - "sha256:ae69927c5c172be1add9203726d4b84cf3ebad1edcd5f71fcdc746e66e829f59" - ], - "markers": "python_version >= '3.6'", - "version": "==0.13.0" - }, "pexpect": { "hashes": [ "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937", @@ -1087,29 +1063,12 @@ ], "version": "==0.7.5" }, - "pip": { - "hashes": [ - "sha256:65fd48317359f3af8e593943e6ae1506b66325085ea64b706a998c6e83eeaf38", - "sha256:908c78e6bc29b676ede1c4d57981d490cb892eb45cd8c214ab6298125119e077" - ], - "markers": "python_version >= '3.7'", - "version": "==22.3.1" - }, - "pip-shims": { - "hashes": [ - "sha256:089e3586a92b1b8dbbc16b2d2859331dc1c412d3e3dbcd91d80e6b30d73db96c", - "sha256:2ae9f21c0155ca5c37d2734eb5f9a7d98c4c42a122d1ba3eddbacc9d9ea9fbae" - ], - "markers": "python_version >= '3.6'", - "version": "==0.7.3" - }, "pipenv-setup": { "hashes": [ - "sha256:0def7ec3363f58b38a43dc59b2078fcee67b47301fd51a41b8e34e6f79812b1a", - "sha256:6ceda7145a3088494d8ca68fded4b0473022dc62eb786a021c137632c44298b5" + "sha256:f65fe799297facfe5ac1b03e2f55525135e0bf521ca7c175331bb59133c302b3" ], "index": "pypi", - "version": "==3.2.0" + "version": "==2.0.0" }, "pipfile": { "hashes": [ @@ -1127,38 +1086,27 @@ }, "platformdirs": { "hashes": [ - "sha256:83c8f6d04389165de7c9b6f0c682439697887bca0aa2f1c87ef1826be3584490", - "sha256:e1fea1fe471b9ff8332e229df3cb7de4f53eeea4998d3b6bfff542115e998bd2" + "sha256:d5b638ca397f25f979350ff789db335903d7ea010ab28903f57b27e1b16c2b08", + "sha256:ebe11c0d7a805086e99506aa331612429a72ca7cd52a1f0d277dc4adc20cb10e" ], "markers": "python_version >= '3.7'", - "version": "==2.6.2" - }, - "plette": { - "extras": [ - "validation" - ], - "hashes": [ - "sha256:06b8c09eb90293ad0b8101cb5c95c4ea53e9b2b582901845d0904ff02d237454", - "sha256:42d68ce8c6b966874b68758d87d7f20fcff2eff0d861903eea1062126be4d98f" - ], - "markers": "python_version >= '3.7'", - "version": "==0.4.4" + "version": "==3.2.0" }, "prometheus-client": { "hashes": [ - "sha256:be26aa452490cfcf6da953f9436e95a9f2b4d578ca80094b4458930e5f584ab1", - "sha256:db7c05cbd13a0f79975592d112320f2605a325969b270a94b71dcabc47b931d2" + "sha256:0836af6eb2c8f4fed712b2f279f6c0a8bbab29f9f4aa15276b91c7cb0d1616ab", + "sha256:a03e35b359f14dd1630898543e2120addfdeacd1a6069c1367ae90fd93ad3f48" ], "markers": "python_version >= '3.6'", - "version": "==0.15.0" + "version": "==0.16.0" }, "prompt-toolkit": { "hashes": [ - "sha256:3e163f254bef5a03b146397d7c1963bd3e2812f0964bb9a24e6ec761fd28db63", - "sha256:aa64ad242a462c5ff0363a7b9cfe696c20d55d9fc60c11fd8e632d064804d305" + "sha256:23ac5d50538a9a38c8bde05fecb47d0b403ecd0662857a86f886f798563d5b9b", + "sha256:45ea77a2f7c60418850331366c81cf6b5b9cf4c7fd34616f733c5427e6abbb1f" ], - "markers": "python_full_version >= '3.6.2'", - "version": "==3.0.36" + "markers": "python_full_version >= '3.7.0'", + "version": "==3.0.38" }, "psutil": { "hashes": [ @@ -1203,27 +1151,19 @@ }, "pygments": { "hashes": [ - "sha256:b3ed06a9e8ac9a9aae5a6f5dbe78a8a58655d17b43b93c078f094ddc476ae297", - "sha256:fa7bd7bd2771287c0de303af8bfdfc731f51bd2c6a47ab69d117138893b82717" + "sha256:77a3299119af881904cd5ecd1ac6a66214b6e9bed1f2db16993b54adede64094", + "sha256:f7e36cffc4c517fbc252861b9a6e4644ca0e5abadf9a113c72d1358ad09b9500" ], - "markers": "python_version >= '3.6'", - "version": "==2.14.0" + "markers": "python_version >= '3.7'", + "version": "==2.15.0" }, "pylint": { "hashes": [ - "sha256:9df0d07e8948a1c3ffa3b6e2d7e6e63d9fb457c5da5b961ed63106594780cc7e", - "sha256:b3dc5ef7d33858f297ac0d06cc73862f01e4f2e74025ec3eff347ce0bc60baf5" + "sha256:001cc91366a7df2970941d7e6bbefcbf98694e00102c1f121c531a814ddc2ea8", + "sha256:1b647da5249e7c279118f657ca28b6aaebb299f86bf92affc632acf199f7adbb" ], "index": "pypi", - "version": "==2.15.10" - }, - "pyparsing": { - "hashes": [ - "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", - "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" - ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.4.7" + "version": "==2.17.2" }, "pyrsistent": { "hashes": [ @@ -1268,26 +1208,19 @@ }, "python-dotenv": { "hashes": [ - "sha256:1684eb44636dd462b66c3ee016599815514527ad99965de77f43e0944634a7e5", - "sha256:b77d08274639e3d34145dfa6c7008e66df0f04b7be7a75fd0d5292c191d79045" + "sha256:a8df96034aae6d2d50a4ebe8216326c61c3eb64836776504fcca410e5937a3ba", + "sha256:f5971a9226b701070a4bf2c38c89e5a3f0d64de8debda981d1db98583009122a" ], "index": "pypi", - "version": "==0.21.0" + "version": "==1.0.0" }, "python-json-logger": { "hashes": [ - "sha256:3b03487b14eb9e4f77e4fc2a023358b5394b82fd89cecf5586259baed57d8c6f", - "sha256:764d762175f99fcc4630bd4853b09632acb60a6224acb27ce08cd70f0b1b81bd" + "sha256:23e7ec02d34237c5aa1e29a070193a4ea87583bb4e7f8fd06d3de8264c4b2e1c", + "sha256:f380b826a991ebbe3de4d897aeec42760035ac760345e57b812938dc8b35e2bd" ], - "markers": "python_version >= '3.5'", - "version": "==2.0.4" - }, - "pytz": { - "hashes": [ - "sha256:01a0681c4b9684a28304615eba55d1ab31ae00bf68ec157ec3708a8182dbbcd0", - "sha256:78f4f37d8198e0627c5f1143240bb0206b8691d8d7ac6d78fee88b78733f8c4a" - ], - "version": "==2022.7.1" + "markers": "python_version >= '3.6'", + "version": "==2.0.7" }, "pyyaml": { "hashes": [ @@ -1337,86 +1270,86 @@ }, "pyzmq": { "hashes": [ - "sha256:00c94fd4c9dd3c95aace0c629a7fa713627a5c80c1819326b642adf6c4b8e2a2", - "sha256:01d53958c787cfea34091fcb8ef36003dbb7913b8e9f8f62a0715234ebc98b70", - "sha256:0282bba9aee6e0346aa27d6c69b5f7df72b5a964c91958fc9e0c62dcae5fdcdc", - "sha256:02f5cb60a7da1edd5591a15efa654ffe2303297a41e1b40c3c8942f8f11fc17c", - "sha256:0645b5a2d2a06fd8eb738018490c514907f7488bf9359c6ee9d92f62e844b76f", - "sha256:0a154ef810d44f9d28868be04641f837374a64e7449df98d9208e76c260c7ef1", - "sha256:0a90b2480a26aef7c13cff18703ba8d68e181facb40f78873df79e6d42c1facc", - "sha256:0e8d00228db627ddd1b418c7afd81820b38575f237128c9650365f2dd6ac3443", - "sha256:17e1cb97d573ea84d7cd97188b42ca6f611ab3ee600f6a75041294ede58e3d20", - "sha256:183e18742be3621acf8908903f689ec520aee3f08449bfd29f583010ca33022b", - "sha256:1f6116991568aac48b94d6d8aaed6157d407942ea385335a6ed313692777fb9d", - "sha256:20638121b0bdc80777ce0ec8c1f14f1ffec0697a1f88f0b564fa4a23078791c4", - "sha256:2754fa68da08a854f4816e05160137fa938a2347276471103d31e04bcee5365c", - "sha256:28bcb2e66224a7ac2843eb632e4109d6b161479e7a2baf24e37210461485b4f1", - "sha256:293a7c2128690f496057f1f1eb6074f8746058d13588389981089ec45d8fdc77", - "sha256:2a73af6504e0d2805e926abf136ebf536735a13c22f709be7113c2ec65b4bec3", - "sha256:2d05d904f03ddf1e0d83d97341354dfe52244a619b5a1440a5f47a5b3451e84e", - "sha256:2e7b87638ee30ab13230e37ce5331b3e730b1e0dda30120b9eeec3540ed292c8", - "sha256:3100dddcada66ec5940ed6391ebf9d003cc3ede3d320748b2737553019f58230", - "sha256:31e523d067ce44a04e876bed3ff9ea1ff8d1b6636d16e5fcace9d22f8c564369", - "sha256:3594c0ff604e685d7e907860b61d0e10e46c74a9ffca168f6e9e50ea934ee440", - "sha256:3670e8c5644768f214a3b598fe46378a4a6f096d5fb82a67dfd3440028460565", - "sha256:4046d03100aca266e70d54a35694cb35d6654cfbef633e848b3c4a8d64b9d187", - "sha256:4725412e27612f0d7d7c2f794d89807ad0227c2fc01dd6146b39ada49c748ef9", - "sha256:484c2c4ee02c1edc07039f42130bd16e804b1fe81c4f428e0042e03967f40c20", - "sha256:487305c2a011fdcf3db1f24e8814bb76d23bc4d2f46e145bc80316a59a9aa07d", - "sha256:4a1bc30f0c18444d51e9b0d0dd39e3a4e7c53ee74190bebef238cd58de577ea9", - "sha256:4c25c95416133942280faaf068d0fddfd642b927fb28aaf4ab201a738e597c1e", - "sha256:4cbb885f347eba7ab7681c450dee5b14aed9f153eec224ec0c3f299273d9241f", - "sha256:4d3d604fe0a67afd1aff906e54da557a5203368a99dcc50a70eef374f1d2abef", - "sha256:4e295f7928a31ae0f657e848c5045ba6d693fe8921205f408ca3804b1b236968", - "sha256:5049e75cc99db65754a3da5f079230fb8889230cf09462ec972d884d1704a3ed", - "sha256:5050f5c50b58a6e38ccaf9263a356f74ef1040f5ca4030225d1cb1a858c5b7b6", - "sha256:526f884a27e8bba62fe1f4e07c62be2cfe492b6d432a8fdc4210397f8cf15331", - "sha256:531866c491aee5a1e967c286cfa470dffac1e2a203b1afda52d62b58782651e9", - "sha256:5605621f2181f20b71f13f698944deb26a0a71af4aaf435b34dd90146092d530", - "sha256:58fc3ad5e1cfd2e6d24741fbb1e216b388115d31b0ca6670f894187f280b6ba6", - "sha256:60ecbfe7669d3808ffa8a7dd1487d6eb8a4015b07235e3b723d4b2a2d4de7203", - "sha256:610d2d112acd4e5501fac31010064a6c6efd716ceb968e443cae0059eb7b86de", - "sha256:6136bfb0e5a9cf8c60c6ac763eb21f82940a77e6758ea53516c8c7074f4ff948", - "sha256:62b9e80890c0d2408eb42d5d7e1fc62a5ce71be3288684788f74cf3e59ffd6e2", - "sha256:656281d496aaf9ca4fd4cea84e6d893e3361057c4707bd38618f7e811759103c", - "sha256:66509c48f7446b640eeae24b60c9c1461799a27b1b0754e438582e36b5af3315", - "sha256:6bf3842af37af43fa953e96074ebbb5315f6a297198f805d019d788a1021dbc8", - "sha256:731b208bc9412deeb553c9519dca47136b5a01ca66667cafd8733211941b17e4", - "sha256:75243e422e85a62f0ab7953dc315452a56b2c6a7e7d1a3c3109ac3cc57ed6b47", - "sha256:7877264aa851c19404b1bb9dbe6eed21ea0c13698be1eda3784aab3036d1c861", - "sha256:81f99fb1224d36eb91557afec8cdc2264e856f3464500b55749020ce4c848ef2", - "sha256:8539216173135e9e89f6b1cc392e74e6b935b91e8c76106cf50e7a02ab02efe5", - "sha256:85456f0d8f3268eecd63dede3b99d5bd8d3b306310c37d4c15141111d22baeaf", - "sha256:866eabf7c1315ef2e93e34230db7cbf672e0d7c626b37c11f7e870c8612c3dcc", - "sha256:926236ca003aec70574754f39703528947211a406f5c6c8b3e50eca04a9e87fc", - "sha256:930e6ad4f2eaac31a3d0c2130619d25db754b267487ebc186c6ad18af2a74018", - "sha256:94f0a7289d0f5c80807c37ebb404205e7deb737e8763eb176f4770839ee2a287", - "sha256:9a2d5e419bd39a1edb6cdd326d831f0120ddb9b1ff397e7d73541bf393294973", - "sha256:9ca6db34b26c4d3e9b0728841ec9aa39484eee272caa97972ec8c8e231b20c7e", - "sha256:9f72ea279b2941a5203e935a4588b9ba8a48aeb9a926d9dfa1986278bd362cb8", - "sha256:a0e7ef9ac807db50b4eb6f534c5dcc22f998f5dae920cc28873d2c1d080a4fc9", - "sha256:a1cd4a95f176cdc0ee0a82d49d5830f13ae6015d89decbf834c273bc33eeb3d3", - "sha256:a9c464cc508177c09a5a6122b67f978f20e2954a21362bf095a0da4647e3e908", - "sha256:ac97e7d647d5519bcef48dd8d3d331f72975afa5c4496c95f6e854686f45e2d9", - "sha256:af1fbfb7ad6ac0009ccee33c90a1d303431c7fb594335eb97760988727a37577", - "sha256:b055a1cddf8035966ad13aa51edae5dc8f1bba0b5d5e06f7a843d8b83dc9b66b", - "sha256:b6f75b4b8574f3a8a0d6b4b52606fc75b82cb4391471be48ab0b8677c82f9ed4", - "sha256:b90bb8dfbbd138558f1f284fecfe328f7653616ff9a972433a00711d9475d1a9", - "sha256:be05504af0619d1cffa500af1e0ede69fb683f301003851f5993b5247cc2c576", - "sha256:c21a5f4e54a807df5afdef52b6d24ec1580153a6bcf0607f70a6e1d9fa74c5c3", - "sha256:c48f257da280b3be6c94e05bd575eddb1373419dbb1a72c3ce64e88f29d1cd6d", - "sha256:cac602e02341eaaf4edfd3e29bd3fdef672e61d4e6dfe5c1d065172aee00acee", - "sha256:ccb3e1a863222afdbda42b7ca8ac8569959593d7abd44f5a709177d6fa27d266", - "sha256:e1081d7030a1229c8ff90120346fb7599b54f552e98fcea5170544e7c6725aab", - "sha256:e14df47c1265356715d3d66e90282a645ebc077b70b3806cf47efcb7d1d630cb", - "sha256:e4bba04ea779a3d7ef25a821bb63fd0939142c88e7813e5bd9c6265a20c523a2", - "sha256:e99629a976809fe102ef73e856cf4b2660acd82a412a51e80ba2215e523dfd0a", - "sha256:f330a1a2c7f89fd4b0aa4dcb7bf50243bf1c8da9a2f1efc31daf57a2046b31f2", - "sha256:f3f96d452e9580cb961ece2e5a788e64abaecb1232a80e61deffb28e105ff84a", - "sha256:fc7c1421c5b1c916acf3128bf3cc7ea7f5018b58c69a6866d70c14190e600ce9" + "sha256:032f5c8483c85bf9c9ca0593a11c7c749d734ce68d435e38c3f72e759b98b3c9", + "sha256:08bfcc21b5997a9be4fefa405341320d8e7f19b4d684fb9c0580255c5bd6d695", + "sha256:1a843d26a8da1b752c74bc019c7b20e6791ee813cd6877449e6a1415589d22ff", + "sha256:1f124cb73f1aa6654d31b183810febc8505fd0c597afa127c4f40076be4574e0", + "sha256:1f82906a2d8e4ee310f30487b165e7cc8ed09c009e4502da67178b03083c4ce0", + "sha256:21ec0bf4831988af43c8d66ba3ccd81af2c5e793e1bf6790eb2d50e27b3c570a", + "sha256:24683285cc6b7bf18ad37d75b9db0e0fefe58404e7001f1d82bf9e721806daa7", + "sha256:24abbfdbb75ac5039205e72d6c75f10fc39d925f2df8ff21ebc74179488ebfca", + "sha256:25e6873a70ad5aa31e4a7c41e5e8c709296edef4a92313e1cd5fc87bbd1874e2", + "sha256:269968f2a76c0513490aeb3ba0dc3c77b7c7a11daa894f9d1da88d4a0db09835", + "sha256:26b0358e8933990502f4513c991c9935b6c06af01787a36d133b7c39b1df37fa", + "sha256:28fdb9224a258134784a9cf009b59265a9dde79582fb750d4e88a6bcbc6fa3dc", + "sha256:2b9c9cc965cdf28381e36da525dcb89fc1571d9c54800fdcd73e3f73a2fc29bd", + "sha256:2da6813b7995b6b1d1307329c73d3e3be2fd2d78e19acfc4eff2e27262732388", + "sha256:3059a6a534c910e1d5d068df42f60d434f79e6cc6285aa469b384fa921f78cf8", + "sha256:312b3f0f066b4f1d17383aae509bacf833ccaf591184a1f3c7a1661c085063ae", + "sha256:34a6fddd159ff38aa9497b2e342a559f142ab365576284bc8f77cb3ead1f79c5", + "sha256:374b55516393bfd4d7a7daa6c3b36d6dd6a31ff9d2adad0838cd6a203125e714", + "sha256:38d9f78d69bcdeec0c11e0feb3bc70f36f9b8c44fc06e5d06d91dc0a21b453c7", + "sha256:4a31992a8f8d51663ebf79df0df6a04ffb905063083d682d4380ab8d2c67257c", + "sha256:4a4b4261eb8f9ed71f63b9eb0198dd7c934aa3b3972dac586d0ef502ba9ab08b", + "sha256:510d8e55b3a7cd13f8d3e9121edf0a8730b87d925d25298bace29a7e7bc82810", + "sha256:531e36d9fcd66f18de27434a25b51d137eb546931033f392e85674c7a7cea853", + "sha256:54a96cf77684a3a537b76acfa7237b1e79a8f8d14e7f00e0171a94b346c5293e", + "sha256:56a94ab1d12af982b55ca96c6853db6ac85505e820d9458ac76364c1998972f4", + "sha256:5c5fbb229e40a89a2fe73d0c1181916f31e30f253cb2d6d91bea7927c2e18413", + "sha256:5d496815074e3e3d183fe2c7fcea2109ad67b74084c254481f87b64e04e9a471", + "sha256:5eaeae038c68748082137d6896d5c4db7927e9349237ded08ee1bbd94f7361c9", + "sha256:62ec8d979f56c0053a92b2b6a10ff54b9ec8a4f187db2b6ec31ee3dd6d3ca6e2", + "sha256:64812f29d6eee565e129ca14b0c785744bfff679a4727137484101b34602d1a7", + "sha256:6526d097b75192f228c09d48420854d53dfbc7abbb41b0e26f363ccb26fbc177", + "sha256:659e62e1cbb063151c52f5b01a38e1df6b54feccfa3e2509d44c35ca6d7962ee", + "sha256:65c19a63b4a83ae45d62178b70223adeee5f12f3032726b897431b6553aa25af", + "sha256:67da1c213fbd208906ab3470cfff1ee0048838365135a9bddc7b40b11e6d6c89", + "sha256:6a821a506822fac55d2df2085a52530f68ab15ceed12d63539adc32bd4410f6e", + "sha256:6a979e59d2184a0c8f2ede4b0810cbdd86b64d99d9cc8a023929e40dce7c86cc", + "sha256:6b8c1bbb70e868dc88801aa532cae6bd4e3b5233784692b786f17ad2962e5149", + "sha256:6fadc60970714d86eff27821f8fb01f8328dd36bebd496b0564a500fe4a9e354", + "sha256:715cff7644a80a7795953c11b067a75f16eb9fc695a5a53316891ebee7f3c9d5", + "sha256:77942243ff4d14d90c11b2afd8ee6c039b45a0be4e53fb6fa7f5e4fd0b59da39", + "sha256:7b504ae43d37e282301da586529e2ded8b36d4ee2cd5e6db4386724ddeaa6bbc", + "sha256:827bf60e749e78acb408a6c5af6688efbc9993e44ecc792b036ec2f4b4acf485", + "sha256:8280ada89010735a12b968ec3ea9a468ac2e04fddcc1cede59cb7f5178783b9c", + "sha256:83d822e8687621bed87404afc1c03d83fa2ce39733d54c2fd52d8829edb8a7ff", + "sha256:8560756318ec7c4c49d2c341012167e704b5a46d9034905853c3d1ade4f55bee", + "sha256:85762712b74c7bd18e340c3639d1bf2f23735a998d63f46bb6584d904b5e401d", + "sha256:88649b19ede1cab03b96b66c364cbbf17c953615cdbc844f7f6e5f14c5e5261c", + "sha256:9a2e5fe42dfe6b73ca120b97ac9f34bfa8414feb15e00e37415dbd51cf227ef6", + "sha256:9af0bb0277e92f41af35e991c242c9c71920169d6aa53ade7e444f338f4c8128", + "sha256:9bdc40efb679b9dcc39c06d25629e55581e4c4f7870a5e88db4f1c51ce25e20d", + "sha256:9e1d2f2d86fc75ed7f8845a992c5f6f1ab5db99747fb0d78b5e4046d041164d2", + "sha256:a2e92ff20ad5d13266bc999a29ed29a3b5b101c21fdf4b2cf420c09db9fb690e", + "sha256:a35960c8b2f63e4ef67fd6731851030df68e4b617a6715dd11b4b10312d19fef", + "sha256:a6f6ae12478fdc26a6d5fdb21f806b08fa5403cd02fd312e4cb5f72df078f96f", + "sha256:a9b5eeb5278a8a636bb0abdd9ff5076bcbb836cd2302565df53ff1fa7d106d54", + "sha256:ab046e9cb902d1f62c9cc0eca055b1d11108bdc271caf7c2171487298f229b56", + "sha256:ab2c056ac503f25a63f6c8c6771373e2a711b98b304614151dfb552d3d6c81f6", + "sha256:abbce982a17c88d2312ec2cf7673985d444f1beaac6e8189424e0a0e0448dbb3", + "sha256:ac178e666c097c8d3deb5097b58cd1316092fc43e8ef5b5fdb259b51da7e7315", + "sha256:ad761cfbe477236802a7ab2c080d268c95e784fe30cafa7e055aacd1ca877eb0", + "sha256:affec1470351178e892121b3414c8ef7803269f207bf9bef85f9a6dd11cde264", + "sha256:b164cc3c8acb3d102e311f2eb6f3c305865ecb377e56adc015cb51f721f1dda6", + "sha256:b48616a09d7df9dbae2f45a0256eee7b794b903ddc6d8657a9948669b345f220", + "sha256:b491998ef886662c1f3d49ea2198055a9a536ddf7430b051b21054f2a5831800", + "sha256:b733076ff46e7db5504c5e7284f04a9852c63214c74688bdb6135808531755a3", + "sha256:c8fedc3ccd62c6b77dfe6f43802057a803a411ee96f14e946f4a76ec4ed0e117", + "sha256:cb1f69a0a2a2b1aae8412979dd6293cc6bcddd4439bf07e4758d864ddb112354", + "sha256:cca8524b61c0eaaa3505382dc9b9a3bc8165f1d6c010fdd1452c224225a26689", + "sha256:cfb9f7eae02d3ac42fbedad30006b7407c984a0eb4189a1322241a20944d61e5", + "sha256:d4427b4a136e3b7f85516c76dd2e0756c22eec4026afb76ca1397152b0ca8145", + "sha256:d488c5c8630f7e782e800869f82744c3aca4aca62c63232e5d8c490d3d66956a", + "sha256:dd771a440effa1c36d3523bc6ba4e54ff5d2e54b4adcc1e060d8f3ca3721d228", + "sha256:ed15e3a2c3c2398e6ae5ce86d6a31b452dfd6ad4cd5d312596b30929c4b6e182", + "sha256:edbbf06cc2719889470a8d2bf5072bb00f423e12de0eb9ffec946c2c9748e149", + "sha256:eef2a0b880ab40aca5a878933376cb6c1ec483fba72f7f34e015c0f675c90b20", + "sha256:f7c8b8368e84381ae7c57f1f5283b029c888504aaf4949c32e6e6fb256ec9bf0", + "sha256:ffc71111433bd6ec8607a37b9211f4ef42e3d3b271c6d76c813669834764b248" ], "markers": "python_version >= '3.6'", - "version": "==25.0.0" + "version": "==25.0.2" }, "readme-renderer": { "hashes": [ @@ -1442,14 +1375,6 @@ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==0.10.1" }, - "requirementslib": { - "hashes": [ - "sha256:28924cf11a2fa91adb03f8431d80c2a8c3dc386f1c48fb2be9a58e4c39072354", - "sha256:d26ec6ad45e1ffce9532303543996c9c71a99dc65f783908f112e3f2aae7e49c" - ], - "markers": "python_version >= '3.7'", - "version": "==1.6.9" - }, "rfc3339-validator": { "hashes": [ "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b", @@ -1476,11 +1401,11 @@ }, "rich": { "hashes": [ - "sha256:7c963f0d03819221e9ac561e1bc866e3f95a02248c1234daa48954e6d381c003", - "sha256:f1a00cdd3eebf999a15d85ec498bfe0b1a77efe9b34f645768a54132ef444ac5" + "sha256:540c7d6d26a1178e8e8b37e9ba44573a3cd1464ff6348b99ee7061b95d1c6333", + "sha256:dc84400a9d842b3a9c5ff74addd8eb798d155f36c1c91303888e0a66850d2a15" ], "markers": "python_full_version >= '3.7.0'", - "version": "==13.2.0" + "version": "==13.3.3" }, "secretstorage": { "hashes": [ @@ -1497,14 +1422,6 @@ ], "version": "==1.8.0" }, - "setuptools": { - "hashes": [ - "sha256:6f590d76b713d5de4e49fe4fbca24474469f53c83632d5d0fd056f7ff7e8112b", - "sha256:ac4008d396bc9cd983ea483cb7139c0240a07bbc74ffb6232fceffedc6cf03a8" - ], - "markers": "python_version >= '3.7'", - "version": "==66.1.1" - }, "six": { "hashes": [ "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", @@ -1530,19 +1447,19 @@ }, "soupsieve": { "hashes": [ - "sha256:3b2503d3c7084a42b1ebd08116e5f81aadfaea95863628c80a3b774a11b7c759", - "sha256:fc53893b3da2c33de295667a0e19f078c14bf86544af307354de5fcf12a3f30d" + "sha256:49e5368c2cda80ee7e84da9dbe3e110b70a4575f196efb74e51b94549d921955", + "sha256:e28dba9ca6c7c00173e34e4ba57448f0688bb681b7c5e8bf4971daafc093d69a" ], - "markers": "python_version >= '3.6'", - "version": "==2.3.2.post1" + "markers": "python_version >= '3.7'", + "version": "==2.4" }, "sphinx": { "hashes": [ - "sha256:309a8da80cb6da9f4713438e5b55861877d5d7976b69d87e336733637ea12693", - "sha256:ba3224a4e206e1fbdecf98a4fae4992ef9b24b85ebf7b584bb340156eaf08d89" + "sha256:0dac3b698538ffef41716cf97ba26c1c7788dba73ce6f150c1ff5b4720786dd2", + "sha256:807d1cb3d6be87eb78a381c3e70ebd8d346b9a25f3753e9947e866b2786865fc" ], "index": "pypi", - "version": "==5.1.1" + "version": "==6.1.3" }, "sphinx-basic-ng": { "hashes": [ @@ -1554,11 +1471,11 @@ }, "sphinxcontrib-applehelp": { "hashes": [ - "sha256:83749f09f6ac843b8cb685277dbc818a8bf2d76cc19602699094fe9a74db529e", - "sha256:ba0f2a22e6eeada8da6428d0d520215ee8864253f32facf958cca81e426f661d" + "sha256:29d341f67fb0f6f586b23ad80e072c8e6ad0b48417db2bde114a4c9746feb228", + "sha256:828f867945bbe39817c210a1abfd1bc4895c8b73fcaade56d45357a348a07d7e" ], "markers": "python_version >= '3.8'", - "version": "==1.0.3" + "version": "==1.0.4" }, "sphinxcontrib-devhelp": { "hashes": [ @@ -1570,11 +1487,11 @@ }, "sphinxcontrib-htmlhelp": { "hashes": [ - "sha256:d412243dfb797ae3ec2b59eca0e52dac12e75a241bf0e4eb861e450d06c6ed07", - "sha256:f5f8bb2d0d629f398bf47d0d69c07bc13b65f75a81ad9e2f71a63d4b7a2f6db2" + "sha256:0cbdd302815330058422b98a113195c9249825d681e18f11e8b1f78a2f11efff", + "sha256:c38cb46dccf316c79de6e5515e1770414b797162b23cd3d06e67020e1d2a6903" ], - "markers": "python_version >= '3.6'", - "version": "==2.0.0" + "markers": "python_version >= '3.8'", + "version": "==2.0.1" }, "sphinxcontrib-jsmath": { "hashes": [ @@ -1625,27 +1542,20 @@ }, "toml": { "hashes": [ - "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", - "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" + "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", + "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e", + "sha256:f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3" ], "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==0.10.2" - }, - "tomli": { - "hashes": [ - "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", - "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f" - ], - "markers": "python_version >= '3.7'", - "version": "==2.0.1" + "version": "==0.10.0" }, "tomlkit": { "hashes": [ - "sha256:07de26b0d8cfc18f871aec595fda24d95b08fef89d147caa861939f37230bf4b", - "sha256:71b952e5721688937fb02cf9d354dbcf0785066149d2855e44531ebdd2b65d73" + "sha256:5325463a7da2ef0c6bbfefb62a3dc883aebe679984709aee32a317907d0a8d3c", + "sha256:f392ef70ad87a672f02519f99967d28a4d3047133e2d1df936511465fbb3791d" ], - "markers": "python_version >= '3.6'", - "version": "==0.11.6" + "markers": "python_version >= '3.7'", + "version": "==0.11.7" }, "tornado": { "hashes": [ @@ -1666,11 +1576,11 @@ }, "traitlets": { "hashes": [ - "sha256:32500888f5ff7bbf3b9267ea31748fa657aaf34d56d85e60f91dda7dc7f5785b", - "sha256:a1ca5df6414f8b5760f7c5f256e326ee21b581742114545b462b35ffe3f04861" + "sha256:9e6ec080259b9a5940c797d58b613b5e31441c2257b87c2e795c5228ae80d2d8", + "sha256:f6cde21a9c68cf756af02035f72d5a723bf607e862e7be33ece505abf4a3bad9" ], "markers": "python_version >= '3.7'", - "version": "==5.8.1" + "version": "==5.9.0" }, "twine": { "hashes": [ @@ -1689,19 +1599,11 @@ }, "urllib3": { "hashes": [ - "sha256:076907bf8fd355cde77728471316625a4d2f7e713c125f51953bb5b3eecf4f72", - "sha256:75edcdc2f7d85b137124a6c3c9fc3933cdeaa12ecb9a6a959f22797a0feca7e1" + "sha256:8a388717b9476f934a21484e8c8e61875ab60644d29b9b39e11e4b9dc1c6b305", + "sha256:aa751d169e23c7479ce47a0cb0da579e3ede798f994f5816a74e4f4500dcea42" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", - "version": "==1.26.14" - }, - "vistir": { - "hashes": [ - "sha256:116bf6e9b6a3cf72100565ee303483abd821b7f67bba25d57df01a0f49e46bec", - "sha256:2ea487172e10ecbb6445870bb1f36ee8e2e7e46f39d743cbc995e1a15ba831b9" - ], - "markers": "python_version >= '3.7'", - "version": "==0.7.5" + "version": "==1.26.15" }, "wcwidth": { "hashes": [ @@ -1712,10 +1614,10 @@ }, "webcolors": { "hashes": [ - "sha256:16d043d3a08fd6a1b1b7e3e9e62640d09790dce80d2bdd4792a175b35fe794a9", - "sha256:d98743d81d498a2d3eaf165196e65481f0d2ea85281463d856b1e51b09f62dce" + "sha256:29bc7e8752c0a1bd4a1f03c14d6e6a72e93d82193738fa860cbff59d0fcc11bf", + "sha256:c225b674c83fa923be93d235330ce0300373d02885cef23238813b0d5668304a" ], - "version": "==1.12" + "version": "==1.13" }, "webencodings": { "hashes": [ @@ -1726,97 +1628,179 @@ }, "websocket-client": { "hashes": [ - "sha256:d6b06432f184438d99ac1f456eaf22fe1ade524c3dd16e661142dc54e9cba574", - "sha256:d6e8f90ca8e2dd4e8027c4561adeb9456b54044312dba655e7cae652ceb9ae59" + "sha256:3f09e6d8230892547132177f575a4e3e73cfdf06526e20cc02aa1c3b47184d40", + "sha256:cdf5877568b7e83aa7cf2244ab56a3213de587bbe0ce9d8b9600fc77b455d89e" ], "markers": "python_version >= '3.7'", - "version": "==1.4.2" - }, - "wheel": { - "hashes": [ - "sha256:965f5259b566725405b05e7cf774052044b1ed30119b5d586b2703aafe8719ac", - "sha256:b60533f3f5d530e971d6737ca6d58681ee434818fab630c83a734bb10c083ce8" - ], - "markers": "python_version >= '3.7'", - "version": "==0.38.4" + "version": "==1.5.1" }, "wrapt": { "hashes": [ - "sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3", - "sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b", - "sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4", - "sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2", - "sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656", - "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3", - "sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff", - "sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310", - "sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a", - "sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57", - "sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069", - "sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383", - "sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe", - "sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87", - "sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d", - "sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b", - "sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907", - "sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f", - "sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0", - "sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28", - "sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1", - "sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853", - "sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc", - "sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3", - "sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3", - "sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164", - "sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1", - "sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c", - "sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1", - "sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7", - "sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1", - "sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320", - "sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed", - "sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1", - "sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248", - "sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c", - "sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456", - "sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77", - "sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef", - "sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1", - "sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7", - "sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86", - "sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4", - "sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d", - "sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d", - "sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8", - "sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5", - "sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471", - "sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00", - "sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68", - "sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3", - "sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d", - "sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735", - "sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d", - "sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569", - "sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7", - "sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59", - "sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5", - "sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb", - "sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b", - "sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f", - "sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462", - "sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015", - "sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af" + "sha256:02fce1852f755f44f95af51f69d22e45080102e9d00258053b79367d07af39c0", + "sha256:077ff0d1f9d9e4ce6476c1a924a3332452c1406e59d90a2cf24aeb29eeac9420", + "sha256:078e2a1a86544e644a68422f881c48b84fef6d18f8c7a957ffd3f2e0a74a0d4a", + "sha256:0970ddb69bba00670e58955f8019bec4a42d1785db3faa043c33d81de2bf843c", + "sha256:1286eb30261894e4c70d124d44b7fd07825340869945c79d05bda53a40caa079", + "sha256:21f6d9a0d5b3a207cdf7acf8e58d7d13d463e639f0c7e01d82cdb671e6cb7923", + "sha256:230ae493696a371f1dbffaad3dafbb742a4d27a0afd2b1aecebe52b740167e7f", + "sha256:26458da5653aa5b3d8dc8b24192f574a58984c749401f98fff994d41d3f08da1", + "sha256:2cf56d0e237280baed46f0b5316661da892565ff58309d4d2ed7dba763d984b8", + "sha256:2e51de54d4fb8fb50d6ee8327f9828306a959ae394d3e01a1ba8b2f937747d86", + "sha256:2fbfbca668dd15b744418265a9607baa970c347eefd0db6a518aaf0cfbd153c0", + "sha256:38adf7198f8f154502883242f9fe7333ab05a5b02de7d83aa2d88ea621f13364", + "sha256:3a8564f283394634a7a7054b7983e47dbf39c07712d7b177b37e03f2467a024e", + "sha256:3abbe948c3cbde2689370a262a8d04e32ec2dd4f27103669a45c6929bcdbfe7c", + "sha256:3bbe623731d03b186b3d6b0d6f51865bf598587c38d6f7b0be2e27414f7f214e", + "sha256:40737a081d7497efea35ab9304b829b857f21558acfc7b3272f908d33b0d9d4c", + "sha256:41d07d029dd4157ae27beab04d22b8e261eddfc6ecd64ff7000b10dc8b3a5727", + "sha256:46ed616d5fb42f98630ed70c3529541408166c22cdfd4540b88d5f21006b0eff", + "sha256:493d389a2b63c88ad56cdc35d0fa5752daac56ca755805b1b0c530f785767d5e", + "sha256:4ff0d20f2e670800d3ed2b220d40984162089a6e2c9646fdb09b85e6f9a8fc29", + "sha256:54accd4b8bc202966bafafd16e69da9d5640ff92389d33d28555c5fd4f25ccb7", + "sha256:56374914b132c702aa9aa9959c550004b8847148f95e1b824772d453ac204a72", + "sha256:578383d740457fa790fdf85e6d346fda1416a40549fe8db08e5e9bd281c6a475", + "sha256:58d7a75d731e8c63614222bcb21dd992b4ab01a399f1f09dd82af17bbfc2368a", + "sha256:5c5aa28df055697d7c37d2099a7bc09f559d5053c3349b1ad0c39000e611d317", + "sha256:5fc8e02f5984a55d2c653f5fea93531e9836abbd84342c1d1e17abc4a15084c2", + "sha256:63424c681923b9f3bfbc5e3205aafe790904053d42ddcc08542181a30a7a51bd", + "sha256:64b1df0f83706b4ef4cfb4fb0e4c2669100fd7ecacfb59e091fad300d4e04640", + "sha256:74934ebd71950e3db69960a7da29204f89624dde411afbfb3b4858c1409b1e98", + "sha256:75669d77bb2c071333417617a235324a1618dba66f82a750362eccbe5b61d248", + "sha256:75760a47c06b5974aa5e01949bf7e66d2af4d08cb8c1d6516af5e39595397f5e", + "sha256:76407ab327158c510f44ded207e2f76b657303e17cb7a572ffe2f5a8a48aa04d", + "sha256:76e9c727a874b4856d11a32fb0b389afc61ce8aaf281ada613713ddeadd1cfec", + "sha256:77d4c1b881076c3ba173484dfa53d3582c1c8ff1f914c6461ab70c8428b796c1", + "sha256:780c82a41dc493b62fc5884fb1d3a3b81106642c5c5c78d6a0d4cbe96d62ba7e", + "sha256:7dc0713bf81287a00516ef43137273b23ee414fe41a3c14be10dd95ed98a2df9", + "sha256:7eebcdbe3677e58dd4c0e03b4f2cfa346ed4049687d839adad68cc38bb559c92", + "sha256:896689fddba4f23ef7c718279e42f8834041a21342d95e56922e1c10c0cc7afb", + "sha256:96177eb5645b1c6985f5c11d03fc2dbda9ad24ec0f3a46dcce91445747e15094", + "sha256:96e25c8603a155559231c19c0349245eeb4ac0096fe3c1d0be5c47e075bd4f46", + "sha256:9d37ac69edc5614b90516807de32d08cb8e7b12260a285ee330955604ed9dd29", + "sha256:9ed6aa0726b9b60911f4aed8ec5b8dd7bf3491476015819f56473ffaef8959bd", + "sha256:a487f72a25904e2b4bbc0817ce7a8de94363bd7e79890510174da9d901c38705", + "sha256:a4cbb9ff5795cd66f0066bdf5947f170f5d63a9274f99bdbca02fd973adcf2a8", + "sha256:a74d56552ddbde46c246b5b89199cb3fd182f9c346c784e1a93e4dc3f5ec9975", + "sha256:a89ce3fd220ff144bd9d54da333ec0de0399b52c9ac3d2ce34b569cf1a5748fb", + "sha256:abd52a09d03adf9c763d706df707c343293d5d106aea53483e0ec8d9e310ad5e", + "sha256:abd8f36c99512755b8456047b7be10372fca271bf1467a1caa88db991e7c421b", + "sha256:af5bd9ccb188f6a5fdda9f1f09d9f4c86cc8a539bd48a0bfdc97723970348418", + "sha256:b02f21c1e2074943312d03d243ac4388319f2456576b2c6023041c4d57cd7019", + "sha256:b06fa97478a5f478fb05e1980980a7cdf2712015493b44d0c87606c1513ed5b1", + "sha256:b0724f05c396b0a4c36a3226c31648385deb6a65d8992644c12a4963c70326ba", + "sha256:b130fe77361d6771ecf5a219d8e0817d61b236b7d8b37cc045172e574ed219e6", + "sha256:b56d5519e470d3f2fe4aa7585f0632b060d532d0696c5bdfb5e8319e1d0f69a2", + "sha256:b67b819628e3b748fd3c2192c15fb951f549d0f47c0449af0764d7647302fda3", + "sha256:ba1711cda2d30634a7e452fc79eabcadaffedf241ff206db2ee93dd2c89a60e7", + "sha256:bbeccb1aa40ab88cd29e6c7d8585582c99548f55f9b2581dfc5ba68c59a85752", + "sha256:bd84395aab8e4d36263cd1b9308cd504f6cf713b7d6d3ce25ea55670baec5416", + "sha256:c99f4309f5145b93eca6e35ac1a988f0dc0a7ccf9ccdcd78d3c0adf57224e62f", + "sha256:ca1cccf838cd28d5a0883b342474c630ac48cac5df0ee6eacc9c7290f76b11c1", + "sha256:cd525e0e52a5ff16653a3fc9e3dd827981917d34996600bbc34c05d048ca35cc", + "sha256:cdb4f085756c96a3af04e6eca7f08b1345e94b53af8921b25c72f096e704e145", + "sha256:ce42618f67741d4697684e501ef02f29e758a123aa2d669e2d964ff734ee00ee", + "sha256:d06730c6aed78cee4126234cf2d071e01b44b915e725a6cb439a879ec9754a3a", + "sha256:d5fe3e099cf07d0fb5a1e23d399e5d4d1ca3e6dfcbe5c8570ccff3e9208274f7", + "sha256:d6bcbfc99f55655c3d93feb7ef3800bd5bbe963a755687cbf1f490a71fb7794b", + "sha256:d787272ed958a05b2c86311d3a4135d3c2aeea4fc655705f074130aa57d71653", + "sha256:e169e957c33576f47e21864cf3fc9ff47c223a4ebca8960079b8bd36cb014fd0", + "sha256:e20076a211cd6f9b44a6be58f7eeafa7ab5720eb796975d0c03f05b47d89eb90", + "sha256:e826aadda3cae59295b95343db8f3d965fb31059da7de01ee8d1c40a60398b29", + "sha256:eef4d64c650f33347c1f9266fa5ae001440b232ad9b98f1f43dfe7a79435c0a6", + "sha256:f2e69b3ed24544b0d3dbe2c5c0ba5153ce50dcebb576fdc4696d52aa22db6034", + "sha256:f87ec75864c37c4c6cb908d282e1969e79763e0d9becdfe9fe5473b7bb1e5f09", + "sha256:fbec11614dba0424ca72f4e8ba3c420dba07b4a7c206c8c8e4e73f2e98f4c559", + "sha256:fd69666217b62fa5d7c6aa88e507493a34dec4fa20c5bd925e4bc12fce586639" ], "markers": "python_version >= '3.11'", - "version": "==1.14.1" + "version": "==1.15.0" + }, + "y-py": { + "hashes": [ + "sha256:05f805b58422d5d7c8e7e8e2141d1c3cac4daaa4557ae6a9b84b141fe8d6289e", + "sha256:065f90501cf008375d70be6ce72dd41745e09d088f0b545f5f914d2c3f04f7ae", + "sha256:0c0e333c20b0a6ce4a5851203d45898ab93f16426c342420b931e190c5b71d3d", + "sha256:13b9d2959d9a26536b6ad118fb026ff19bd79da52e4addf6f3a562e7c01d516e", + "sha256:1906f13e8d5ebfbd9c7948f57bc6f6f53b451b19c99350f42a0f648147a8acfe", + "sha256:1f54625b9ed4e787872c45d3044dcfd04c0da4258d9914f3d32308830b35246c", + "sha256:202b2a3e42e0a1eaedee26f8a3bc73cd9f994c4c2b15511ea56b9838178eb380", + "sha256:2532ea5aefb223fd688c93860199d348a7601d814aac9e8784d816314588ddeb", + "sha256:25637e3d011ca6f877a24f3083ff2549d1d619406d7e8a1455c445527205046c", + "sha256:2692c808bf28f797f8d693f45dc86563ac3b1626579f67ce9546dca69644d687", + "sha256:27c1e9a866146d250e9e16d99fe22a40c82f5b592ab85da97e5679fc3841c7ce", + "sha256:2ffebe5e62cbfee6e24593927dedba77dc13ac4cfb9c822074ab566b1fb63d59", + "sha256:50cfa0532bcee27edb8c64743b49570e28bb76a00cd384ead1d84b6f052d9368", + "sha256:55098440e32339c2dc3d652fb36bb77a4927dee5fd4ab0cb1fe12fdd163fd4f5", + "sha256:5dbd8d177ec7b9fef4a7b6d22eb2f8d5606fd5aac31cf2eab0dc18f0b3504c7c", + "sha256:63ef8e5b76cd54578a7fd5f72d8c698d9ccd7c555c7900ebfd38a24d397c3b15", + "sha256:73200c59bb253b880825466717941ac57267f2f685b053e183183cb6fe82874d", + "sha256:7353af0e9c1f42fbf0ab340e253eeb333d58c890fa91d3eadb1b9adaf9336732", + "sha256:742c486d5b792c4ad76e09426161302edddca85efe826fa01dcee50907326cd7", + "sha256:753aaae817d658a1e9d271663439d8e83d9d8effa45590ecdcadc600c7cf77e3", + "sha256:76b3480e7037ac9390c450e2aff9e46e2c9e61520c0d88afe228110ec728adc5", + "sha256:800e73d2110b97a74c52db2c8ce03a78e96f0d66a7e0c87d8254170a67c2db0e", + "sha256:85585e669d7679126e4a04e4bc0a063a641175a74eecfe47539e8da3e5b1da6e", + "sha256:8d4dfc276f988175baaa4ab321c3321a16ce33db3356c9bc5f4dea0db3de55aa", + "sha256:91be189fae8ba242528333e266e38d65cae3d9a09fe45867fab8578a3ddf2ea2", + "sha256:9484a3fc33f812234e58a5ee834b42bb0a628054d61b5c06c323aa56c12e557d", + "sha256:9513ae81fcc805671ae134c4c7421ca322acf92ce8b33817e1775ea8c0176973", + "sha256:95d13b38c9055d607565b77cbae12e2bf0c1671c5cb8f2ee2e1230d41d2d6d34", + "sha256:9983e99e3a61452b39ffce98206c7e4c6d260f4e917c8fe53fb54aaf25df89a3", + "sha256:9a59603cf42c20d02ee5add2e3d0ce48e89c480a2a02f642fb77f142c4f37958", + "sha256:a57d81260e048caacf43a2f851766687f53e8a8356df6947fb0eee7336a7e2de", + "sha256:a7977eeaceaeb0dfffcc5643c985c337ebc33a0b1d792ae0a9b1331cdd97366f", + "sha256:add793f5f5c7c7a3eb1b09ffc771bdaae10a0bd482a370bf696b83f8dee8d1b4", + "sha256:ae82a6d9cbaff8cb7505e81b5b7f9cd7756bb7e7110aef7914375fe56b012a90", + "sha256:af6df5ec1d66ee2d962026635d60e84ad35fc01b2a1e36b993360c0ce60ae349", + "sha256:afa9a11aa2880dd8689894f3269b653e6d3bd1956963d5329be9a5bf021dab62", + "sha256:b0ed760e6aa5316227a0ba2d5d29634a4ef2d72c8bc55169ac01664e17e4b536", + "sha256:b44473bb32217c78e18db66f497f6c8be33e339bab5f52398bb2468c904d5140", + "sha256:b67dad339f9b6701f74ff7a6e901c7909eca4eea02cf955b28d87a42650bd1be", + "sha256:bc9052a814e8b7ec756371a191f38de68b956437e0bb429c2dd503e658f298f9", + "sha256:c1f5f287cc7ae127ed6a2fb1546e631b316a41d087d7d2db9caa3e5f59906dcf", + "sha256:c3ae6d22b7cc599220a26b06da6ead9fd582eea5fdb6273b06fa3f060d0a26a7", + "sha256:c42f3a6cd20153925b00c49af855a3277989d411bb8ea849095be943ee160821", + "sha256:c7ca64a2a97f708569dcabd55865915943e30267bf6d26c4d212d005951efe62", + "sha256:caf9b1feb69379d424a1d3d7c899b8e0389a3fb3131d39c3c03dcc3d4a93dbdc", + "sha256:cb68445414940efe547291340e91604c7b8379b60822678ef29f4fc2a0e11c62", + "sha256:cc8e5f38842a4b043c9592bfa9a740147ddb8fac2d7a5b7bf6d52466c090ec23", + "sha256:cd6f373dbf592ad83aaf95c16abebc8678928e49bd509ebd593259e1908345ae", + "sha256:d2da2a9e28dceab4832945a745cad507579f52b4d0c9e2f54ae156eb56875861", + "sha256:d373c6bb8e21d5f7ec0833b76fa1ab480086ada602ef5bbf4724a25a21a00b6a", + "sha256:d722d6a27230c1f395535da5cee6a9a16497c6343afd262c846090075c083009", + "sha256:db1ac7f2d1862eb4c448cf76183399d555a63dbe2452bafecb1c2f691e36d687", + "sha256:df78a0409dca11554a4b6442d7a8e61f762c3cfc78d55d98352392869a6b9ae0", + "sha256:e30fe2491d095c6d695a2c96257967fd3e2497f0f777030c8492d03c18d46e2a", + "sha256:e370ce076781adea161b04d2f666e8b4f89bc7e8927ef842fbb0283d3bfa73e0", + "sha256:ecd3cb0d13ac92e7b9235d1024dba9af0788161246f12dcf1f635d634ccb206a", + "sha256:ed0fd5265905cc7e23709479bc152d69f4972dec32fa322d20cb77f749707e78", + "sha256:f6d87d0c2e87990bc00c049742d36a5dbbb1510949459af17198728890ee748a", + "sha256:f7434c77cd23592973ed63341b8d337e6aebaba5ed40d7f22e2d43dfd0c3a56e", + "sha256:f8b67ae37af8aac6160fda66c0f73bcdf65c06da9022eb76192c3fc45cfab994", + "sha256:f8f238144a302f17eb26b122cad9382fcff5ec6653b8a562130b9a5e44010098", + "sha256:fa685f7e43ce490dfb1e392ac48f584b75cd21f05dc526c160d15308236ce8a0", + "sha256:fce5feb57f6231376eb10d1fb68c60da106ffa0b520b3129471c466eff0304cc", + "sha256:fdafb93bfd5532b13a53c4090675bcd31724160017ecc73e492dc1211bc0377a", + "sha256:fe70d0134fe2115c08866f0cac0eb5c0788093872b5026eb438a74e1ebafd659", + "sha256:ff3ddedaa95284f4f22a92b362f658f3d92f272d8c0fa009051bd5490c4d5a04" + ], + "version": "==0.5.9" + }, + "ypy-websocket": { + "hashes": [ + "sha256:491b2cc4271df4dde9be83017c15f4532b597dc43148472eb20c5aeb838a5b46", + "sha256:9049d5a7d61c26c2b5a39757c9ffcbe2274bf3553adeea8de7fe1c04671d4145" + ], + "markers": "python_version >= '3.7'", + "version": "==0.8.2" }, "zipp": { "hashes": [ - "sha256:83a28fcb75844b5c0cdaf5aa4003c2d728c77e05f5aeabe8e95e56727005fbaa", - "sha256:a7a22e05929290a67401440b39690ae6563279bced5f314609d9d03798f56766" + "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b", + "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556" ], "markers": "python_version >= '3.7'", - "version": "==3.11.0" + "version": "==3.15.0" } }, "graphql": { @@ -1914,126 +1898,109 @@ }, "charset-normalizer": { "hashes": [ - "sha256:00d3ffdaafe92a5dc603cb9bd5111aaa36dfa187c8285c543be562e61b755f6b", - "sha256:024e606be3ed92216e2b6952ed859d86b4cfa52cd5bc5f050e7dc28f9b43ec42", - "sha256:0298eafff88c99982a4cf66ba2efa1128e4ddaca0b05eec4c456bbc7db691d8d", - "sha256:02a51034802cbf38db3f89c66fb5d2ec57e6fe7ef2f4a44d070a593c3688667b", - "sha256:083c8d17153ecb403e5e1eb76a7ef4babfc2c48d58899c98fcaa04833e7a2f9a", - "sha256:0a11e971ed097d24c534c037d298ad32c6ce81a45736d31e0ff0ad37ab437d59", - "sha256:0bf2dae5291758b6f84cf923bfaa285632816007db0330002fa1de38bfcb7154", - "sha256:0c0a590235ccd933d9892c627dec5bc7511ce6ad6c1011fdf5b11363022746c1", - "sha256:0f438ae3532723fb6ead77e7c604be7c8374094ef4ee2c5e03a3a17f1fca256c", - "sha256:109487860ef6a328f3eec66f2bf78b0b72400280d8f8ea05f69c51644ba6521a", - "sha256:11b53acf2411c3b09e6af37e4b9005cba376c872503c8f28218c7243582df45d", - "sha256:12db3b2c533c23ab812c2b25934f60383361f8a376ae272665f8e48b88e8e1c6", - "sha256:14e76c0f23218b8f46c4d87018ca2e441535aed3632ca134b10239dfb6dadd6b", - "sha256:16a8663d6e281208d78806dbe14ee9903715361cf81f6d4309944e4d1e59ac5b", - "sha256:292d5e8ba896bbfd6334b096e34bffb56161c81408d6d036a7dfa6929cff8783", - "sha256:2c03cc56021a4bd59be889c2b9257dae13bf55041a3372d3295416f86b295fb5", - "sha256:2e396d70bc4ef5325b72b593a72c8979999aa52fb8bcf03f701c1b03e1166918", - "sha256:2edb64ee7bf1ed524a1da60cdcd2e1f6e2b4f66ef7c077680739f1641f62f555", - "sha256:31a9ddf4718d10ae04d9b18801bd776693487cbb57d74cc3458a7673f6f34639", - "sha256:356541bf4381fa35856dafa6a965916e54bed415ad8a24ee6de6e37deccf2786", - "sha256:358a7c4cb8ba9b46c453b1dd8d9e431452d5249072e4f56cfda3149f6ab1405e", - "sha256:37f8febc8ec50c14f3ec9637505f28e58d4f66752207ea177c1d67df25da5aed", - "sha256:39049da0ffb96c8cbb65cbf5c5f3ca3168990adf3551bd1dee10c48fce8ae820", - "sha256:39cf9ed17fe3b1bc81f33c9ceb6ce67683ee7526e65fde1447c772afc54a1bb8", - "sha256:3ae1de54a77dc0d6d5fcf623290af4266412a7c4be0b1ff7444394f03f5c54e3", - "sha256:3b590df687e3c5ee0deef9fc8c547d81986d9a1b56073d82de008744452d6541", - "sha256:3e45867f1f2ab0711d60c6c71746ac53537f1684baa699f4f668d4c6f6ce8e14", - "sha256:3fc1c4a2ffd64890aebdb3f97e1278b0cc72579a08ca4de8cd2c04799a3a22be", - "sha256:4457ea6774b5611f4bed5eaa5df55f70abde42364d498c5134b7ef4c6958e20e", - "sha256:44ba614de5361b3e5278e1241fda3dc1838deed864b50a10d7ce92983797fa76", - "sha256:4a8fcf28c05c1f6d7e177a9a46a1c52798bfe2ad80681d275b10dcf317deaf0b", - "sha256:4b0d02d7102dd0f997580b51edc4cebcf2ab6397a7edf89f1c73b586c614272c", - "sha256:502218f52498a36d6bf5ea77081844017bf7982cdbe521ad85e64cabee1b608b", - "sha256:503e65837c71b875ecdd733877d852adbc465bd82c768a067badd953bf1bc5a3", - "sha256:5995f0164fa7df59db4746112fec3f49c461dd6b31b841873443bdb077c13cfc", - "sha256:59e5686dd847347e55dffcc191a96622f016bc0ad89105e24c14e0d6305acbc6", - "sha256:601f36512f9e28f029d9481bdaf8e89e5148ac5d89cffd3b05cd533eeb423b59", - "sha256:608862a7bf6957f2333fc54ab4399e405baad0163dc9f8d99cb236816db169d4", - "sha256:62595ab75873d50d57323a91dd03e6966eb79c41fa834b7a1661ed043b2d404d", - "sha256:70990b9c51340e4044cfc394a81f614f3f90d41397104d226f21e66de668730d", - "sha256:71140351489970dfe5e60fc621ada3e0f41104a5eddaca47a7acb3c1b851d6d3", - "sha256:72966d1b297c741541ca8cf1223ff262a6febe52481af742036a0b296e35fa5a", - "sha256:74292fc76c905c0ef095fe11e188a32ebd03bc38f3f3e9bcb85e4e6db177b7ea", - "sha256:761e8904c07ad053d285670f36dd94e1b6ab7f16ce62b9805c475b7aa1cffde6", - "sha256:772b87914ff1152b92a197ef4ea40efe27a378606c39446ded52c8f80f79702e", - "sha256:79909e27e8e4fcc9db4addea88aa63f6423ebb171db091fb4373e3312cb6d603", - "sha256:7e189e2e1d3ed2f4aebabd2d5b0f931e883676e51c7624826e0a4e5fe8a0bf24", - "sha256:7eb33a30d75562222b64f569c642ff3dc6689e09adda43a082208397f016c39a", - "sha256:81d6741ab457d14fdedc215516665050f3822d3e56508921cc7239f8c8e66a58", - "sha256:8499ca8f4502af841f68135133d8258f7b32a53a1d594aa98cc52013fff55678", - "sha256:84c3990934bae40ea69a82034912ffe5a62c60bbf6ec5bc9691419641d7d5c9a", - "sha256:87701167f2a5c930b403e9756fab1d31d4d4da52856143b609e30a1ce7160f3c", - "sha256:88600c72ef7587fe1708fd242b385b6ed4b8904976d5da0893e31df8b3480cb6", - "sha256:8ac7b6a045b814cf0c47f3623d21ebd88b3e8cf216a14790b455ea7ff0135d18", - "sha256:8b8af03d2e37866d023ad0ddea594edefc31e827fee64f8de5611a1dbc373174", - "sha256:8c7fe7afa480e3e82eed58e0ca89f751cd14d767638e2550c77a92a9e749c317", - "sha256:8eade758719add78ec36dc13201483f8e9b5d940329285edcd5f70c0a9edbd7f", - "sha256:911d8a40b2bef5b8bbae2e36a0b103f142ac53557ab421dc16ac4aafee6f53dc", - "sha256:93ad6d87ac18e2a90b0fe89df7c65263b9a99a0eb98f0a3d2e079f12a0735837", - "sha256:95dea361dd73757c6f1c0a1480ac499952c16ac83f7f5f4f84f0658a01b8ef41", - "sha256:9ab77acb98eba3fd2a85cd160851816bfce6871d944d885febf012713f06659c", - "sha256:9cb3032517f1627cc012dbc80a8ec976ae76d93ea2b5feaa9d2a5b8882597579", - "sha256:9cf4e8ad252f7c38dd1f676b46514f92dc0ebeb0db5552f5f403509705e24753", - "sha256:9d9153257a3f70d5f69edf2325357251ed20f772b12e593f3b3377b5f78e7ef8", - "sha256:a152f5f33d64a6be73f1d30c9cc82dfc73cec6477ec268e7c6e4c7d23c2d2291", - "sha256:a16418ecf1329f71df119e8a65f3aa68004a3f9383821edcb20f0702934d8087", - "sha256:a60332922359f920193b1d4826953c507a877b523b2395ad7bc716ddd386d866", - "sha256:a8d0fc946c784ff7f7c3742310cc8a57c5c6dc31631269876a88b809dbeff3d3", - "sha256:ab5de034a886f616a5668aa5d098af2b5385ed70142090e2a31bcbd0af0fdb3d", - "sha256:c22d3fe05ce11d3671297dc8973267daa0f938b93ec716e12e0f6dee81591dc1", - "sha256:c2ac1b08635a8cd4e0cbeaf6f5e922085908d48eb05d44c5ae9eabab148512ca", - "sha256:c512accbd6ff0270939b9ac214b84fb5ada5f0409c44298361b2f5e13f9aed9e", - "sha256:c75ffc45f25324e68ab238cb4b5c0a38cd1c3d7f1fb1f72b5541de469e2247db", - "sha256:c95a03c79bbe30eec3ec2b7f076074f4281526724c8685a42872974ef4d36b72", - "sha256:cadaeaba78750d58d3cc6ac4d1fd867da6fc73c88156b7a3212a3cd4819d679d", - "sha256:cd6056167405314a4dc3c173943f11249fa0f1b204f8b51ed4bde1a9cd1834dc", - "sha256:db72b07027db150f468fbada4d85b3b2729a3db39178abf5c543b784c1254539", - "sha256:df2c707231459e8a4028eabcd3cfc827befd635b3ef72eada84ab13b52e1574d", - "sha256:e62164b50f84e20601c1ff8eb55620d2ad25fb81b59e3cd776a1902527a788af", - "sha256:e696f0dd336161fca9adbb846875d40752e6eba585843c768935ba5c9960722b", - "sha256:eaa379fcd227ca235d04152ca6704c7cb55564116f8bc52545ff357628e10602", - "sha256:ebea339af930f8ca5d7a699b921106c6e29c617fe9606fa7baa043c1cdae326f", - "sha256:f4c39b0e3eac288fedc2b43055cfc2ca7a60362d0e5e87a637beac5d801ef478", - "sha256:f5057856d21e7586765171eac8b9fc3f7d44ef39425f85dbcccb13b3ebea806c", - "sha256:f6f45710b4459401609ebebdbcfb34515da4fc2aa886f95107f556ac69a9147e", - "sha256:f97e83fa6c25693c7a35de154681fcc257c1c41b38beb0304b9c4d2d9e164479", - "sha256:f9d0c5c045a3ca9bedfc35dca8526798eb91a07aa7a2c0fee134c6c6f321cbd7", - "sha256:ff6f3db31555657f3163b15a6b7c6938d08df7adbfc9dd13d9d19edad678f1e8" + "sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6", + "sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1", + "sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e", + "sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373", + "sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62", + "sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230", + "sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be", + "sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c", + "sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0", + "sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448", + "sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f", + "sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649", + "sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d", + "sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0", + "sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706", + "sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a", + "sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59", + "sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23", + "sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5", + "sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb", + "sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e", + "sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e", + "sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c", + "sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28", + "sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d", + "sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41", + "sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974", + "sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce", + "sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f", + "sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1", + "sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d", + "sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8", + "sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017", + "sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31", + "sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7", + "sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8", + "sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e", + "sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14", + "sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd", + "sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d", + "sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795", + "sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b", + "sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b", + "sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b", + "sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203", + "sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f", + "sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19", + "sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1", + "sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a", + "sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac", + "sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9", + "sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0", + "sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137", + "sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f", + "sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6", + "sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5", + "sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909", + "sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f", + "sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0", + "sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324", + "sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755", + "sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb", + "sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854", + "sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c", + "sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60", + "sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84", + "sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0", + "sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b", + "sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1", + "sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531", + "sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1", + "sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11", + "sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326", + "sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df", + "sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab" ], - "markers": "python_full_version >= '3.6.0'", - "version": "==3.0.1" + "markers": "python_full_version >= '3.7.0'", + "version": "==3.1.0" }, "cryptography": { "hashes": [ - "sha256:1a6915075c6d3a5e1215eab5d99bcec0da26036ff2102a1038401d6ef5bef25b", - "sha256:1ee1fd0de9851ff32dbbb9362a4d833b579b4a6cc96883e8e6d2ff2a6bc7104f", - "sha256:407cec680e811b4fc829de966f88a7c62a596faa250fc1a4b520a0355b9bc190", - "sha256:50386acb40fbabbceeb2986332f0287f50f29ccf1497bae31cf5c3e7b4f4b34f", - "sha256:6f97109336df5c178ee7c9c711b264c502b905c2d2a29ace99ed761533a3460f", - "sha256:754978da4d0457e7ca176f58c57b1f9de6556591c19b25b8bcce3c77d314f5eb", - "sha256:76c24dd4fd196a80f9f2f5405a778a8ca132f16b10af113474005635fe7e066c", - "sha256:7dacfdeee048814563eaaec7c4743c8aea529fe3dd53127313a792f0dadc1773", - "sha256:80ee674c08aaef194bc4627b7f2956e5ba7ef29c3cc3ca488cf15854838a8f72", - "sha256:844ad4d7c3850081dffba91cdd91950038ee4ac525c575509a42d3fc806b83c8", - "sha256:875aea1039d78557c7c6b4db2fe0e9d2413439f4676310a5f269dd342ca7a717", - "sha256:887cbc1ea60786e534b00ba8b04d1095f4272d380ebd5f7a7eb4cc274710fad9", - "sha256:ad04f413436b0781f20c52a661660f1e23bcd89a0e9bb1d6d20822d048cf2856", - "sha256:bae6c7f4a36a25291b619ad064a30a07110a805d08dc89984f4f441f6c1f3f96", - "sha256:c52a1a6f81e738d07f43dab57831c29e57d21c81a942f4602fac7ee21b27f288", - "sha256:e0a05aee6a82d944f9b4edd6a001178787d1546ec7c6223ee9a848a7ade92e39", - "sha256:e324de6972b151f99dc078defe8fb1b0a82c6498e37bff335f5bc6b1e3ab5a1e", - "sha256:e5d71c5d5bd5b5c3eebcf7c5c2bb332d62ec68921a8c593bea8c394911a005ce", - "sha256:f3ed2d864a2fa1666e749fe52fb8e23d8e06b8012e8bd8147c73797c506e86f1", - "sha256:f671c1bb0d6088e94d61d80c606d65baacc0d374e67bf895148883461cd848de", - "sha256:f6c0db08d81ead9576c4d94bbb27aed8d7a430fa27890f39084c2d0e2ec6b0df", - "sha256:f964c7dcf7802d133e8dbd1565914fa0194f9d683d82411989889ecd701e8adf", - "sha256:fec8b932f51ae245121c4671b4bbc030880f363354b2f0e0bd1366017d891458" + "sha256:0a4e3406cfed6b1f6d6e87ed243363652b2586b2d917b0609ca4f97072994405", + "sha256:1e0af458515d5e4028aad75f3bb3fe7a31e46ad920648cd59b64d3da842e4356", + "sha256:2803f2f8b1e95f614419926c7e6f55d828afc614ca5ed61543877ae668cc3472", + "sha256:28d63d75bf7ae4045b10de5413fb1d6338616e79015999ad9cf6fc538f772d41", + "sha256:32057d3d0ab7d4453778367ca43e99ddb711770477c4f072a51b3ca69602780a", + "sha256:3a4805a4ca729d65570a1b7cac84eac1e431085d40387b7d3bbaa47e39890b88", + "sha256:63dac2d25c47f12a7b8aa60e528bfb3c51c5a6c5a9f7c86987909c6c79765554", + "sha256:650883cc064297ef3676b1db1b7b1df6081794c4ada96fa457253c4cc40f97db", + "sha256:6f2bbd72f717ce33100e6467572abaedc61f1acb87b8d546001328d7f466b778", + "sha256:7c872413353c70e0263a9368c4993710070e70ab3e5318d85510cc91cce77e7c", + "sha256:918cb89086c7d98b1b86b9fdb70c712e5a9325ba6f7d7cfb509e784e0cfc6917", + "sha256:9618a87212cb5200500e304e43691111570e1f10ec3f35569fdfcd17e28fd797", + "sha256:a805a7bce4a77d51696410005b3e85ae2839bad9aa38894afc0aa99d8e0c3160", + "sha256:cc3a621076d824d75ab1e1e530e66e7e8564e357dd723f2533225d40fe35c60c", + "sha256:cd033d74067d8928ef00a6b1327c8ea0452523967ca4463666eeba65ca350d4c", + "sha256:cf91e428c51ef692b82ce786583e214f58392399cf65c341bc7301d096fa3ba2", + "sha256:d36bbeb99704aabefdca5aee4eba04455d7a27ceabd16f3b3ba9bdcc31da86c4", + "sha256:d8aa3609d337ad85e4eb9bb0f8bcf6e4409bfb86e706efa9a027912169e89122", + "sha256:f5d7b79fa56bc29580faafc2ff736ce05ba31feaa9d4735048b0de7d9ceb2b94" ], "markers": "python_version >= '3.6'", - "version": "==39.0.0" + "version": "==40.0.1" }, "deprecated": { "hashes": [ @@ -2045,19 +2012,19 @@ }, "django": { "hashes": [ - "sha256:4b214a05fe4c99476e99e2445c8b978c8369c18d4dea8e22ec412862715ad763", - "sha256:ff56ebd7ead0fd5dbe06fe157b0024a7aaea2e0593bb3785fb594cf94dad58ef" + "sha256:ad33ed68db9398f5dfb33282704925bce044bef4261cd4fb59e4e7f9ae505a78", + "sha256:c36e2ab12824e2ac36afa8b2515a70c53c7742f0d6eaefa7311ec379558db997" ], "index": "pypi", - "version": "==4.1.5" + "version": "==4.2" }, "django-filter": { "hashes": [ - "sha256:ed429e34760127e3520a67f415bec4c905d4649fbe45d0d6da37e6ff5e0287eb", - "sha256:ed473b76e84f7e83b2511bb2050c3efb36d135207d0128dfe3ae4b36e3594ba5" + "sha256:dee5dcf2cea4d7f767e271b6d01f767fce7500676d5e5dc58dac8154000b87df", + "sha256:e3c52ad83c32fb5882125105efb5fea2a1d6a85e7dc64b04ef52edbf14451b6c" ], "index": "pypi", - "version": "==22.1" + "version": "==23.1" }, "django-oauth-toolkit": { "hashes": [ @@ -2069,11 +2036,11 @@ }, "graphene": { "hashes": [ - "sha256:2ef689f514ba9e65e88961798cf4c637ca580e541168f9aee2ffbe21fd46f388", - "sha256:722243a9da2caeab703b1af9ec0deec602589c97035f86c486106a52d0c67082" + "sha256:5b03e72770dc901f40be55784058d6bb1d952a49eb819a4a085962d5e1cf5fcf", + "sha256:753de13948cbf42e32cc87fb533167c88907066eb984251fdbb006c0aab8da00" ], "index": "pypi", - "version": "==3.2.1" + "version": "==3.2.2" }, "graphene-django": { "hashes": [ @@ -2168,81 +2135,92 @@ }, "urllib3": { "hashes": [ - "sha256:076907bf8fd355cde77728471316625a4d2f7e713c125f51953bb5b3eecf4f72", - "sha256:75edcdc2f7d85b137124a6c3c9fc3933cdeaa12ecb9a6a959f22797a0feca7e1" + "sha256:8a388717b9476f934a21484e8c8e61875ab60644d29b9b39e11e4b9dc1c6b305", + "sha256:aa751d169e23c7479ce47a0cb0da579e3ede798f994f5816a74e4f4500dcea42" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", - "version": "==1.26.14" + "version": "==1.26.15" }, "wrapt": { "hashes": [ - "sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3", - "sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b", - "sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4", - "sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2", - "sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656", - "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3", - "sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff", - "sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310", - "sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a", - "sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57", - "sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069", - "sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383", - "sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe", - "sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87", - "sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d", - "sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b", - "sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907", - "sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f", - "sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0", - "sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28", - "sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1", - "sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853", - "sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc", - "sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3", - "sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3", - "sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164", - "sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1", - "sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c", - "sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1", - "sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7", - "sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1", - "sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320", - "sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed", - "sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1", - "sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248", - "sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c", - "sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456", - "sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77", - "sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef", - "sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1", - "sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7", - "sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86", - "sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4", - "sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d", - "sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d", - "sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8", - "sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5", - "sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471", - "sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00", - "sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68", - "sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3", - "sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d", - "sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735", - "sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d", - "sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569", - "sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7", - "sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59", - "sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5", - "sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb", - "sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b", - "sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f", - "sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462", - "sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015", - "sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af" + "sha256:02fce1852f755f44f95af51f69d22e45080102e9d00258053b79367d07af39c0", + "sha256:077ff0d1f9d9e4ce6476c1a924a3332452c1406e59d90a2cf24aeb29eeac9420", + "sha256:078e2a1a86544e644a68422f881c48b84fef6d18f8c7a957ffd3f2e0a74a0d4a", + "sha256:0970ddb69bba00670e58955f8019bec4a42d1785db3faa043c33d81de2bf843c", + "sha256:1286eb30261894e4c70d124d44b7fd07825340869945c79d05bda53a40caa079", + "sha256:21f6d9a0d5b3a207cdf7acf8e58d7d13d463e639f0c7e01d82cdb671e6cb7923", + "sha256:230ae493696a371f1dbffaad3dafbb742a4d27a0afd2b1aecebe52b740167e7f", + "sha256:26458da5653aa5b3d8dc8b24192f574a58984c749401f98fff994d41d3f08da1", + "sha256:2cf56d0e237280baed46f0b5316661da892565ff58309d4d2ed7dba763d984b8", + "sha256:2e51de54d4fb8fb50d6ee8327f9828306a959ae394d3e01a1ba8b2f937747d86", + "sha256:2fbfbca668dd15b744418265a9607baa970c347eefd0db6a518aaf0cfbd153c0", + "sha256:38adf7198f8f154502883242f9fe7333ab05a5b02de7d83aa2d88ea621f13364", + "sha256:3a8564f283394634a7a7054b7983e47dbf39c07712d7b177b37e03f2467a024e", + "sha256:3abbe948c3cbde2689370a262a8d04e32ec2dd4f27103669a45c6929bcdbfe7c", + "sha256:3bbe623731d03b186b3d6b0d6f51865bf598587c38d6f7b0be2e27414f7f214e", + "sha256:40737a081d7497efea35ab9304b829b857f21558acfc7b3272f908d33b0d9d4c", + "sha256:41d07d029dd4157ae27beab04d22b8e261eddfc6ecd64ff7000b10dc8b3a5727", + "sha256:46ed616d5fb42f98630ed70c3529541408166c22cdfd4540b88d5f21006b0eff", + "sha256:493d389a2b63c88ad56cdc35d0fa5752daac56ca755805b1b0c530f785767d5e", + "sha256:4ff0d20f2e670800d3ed2b220d40984162089a6e2c9646fdb09b85e6f9a8fc29", + "sha256:54accd4b8bc202966bafafd16e69da9d5640ff92389d33d28555c5fd4f25ccb7", + "sha256:56374914b132c702aa9aa9959c550004b8847148f95e1b824772d453ac204a72", + "sha256:578383d740457fa790fdf85e6d346fda1416a40549fe8db08e5e9bd281c6a475", + "sha256:58d7a75d731e8c63614222bcb21dd992b4ab01a399f1f09dd82af17bbfc2368a", + "sha256:5c5aa28df055697d7c37d2099a7bc09f559d5053c3349b1ad0c39000e611d317", + "sha256:5fc8e02f5984a55d2c653f5fea93531e9836abbd84342c1d1e17abc4a15084c2", + "sha256:63424c681923b9f3bfbc5e3205aafe790904053d42ddcc08542181a30a7a51bd", + "sha256:64b1df0f83706b4ef4cfb4fb0e4c2669100fd7ecacfb59e091fad300d4e04640", + "sha256:74934ebd71950e3db69960a7da29204f89624dde411afbfb3b4858c1409b1e98", + "sha256:75669d77bb2c071333417617a235324a1618dba66f82a750362eccbe5b61d248", + "sha256:75760a47c06b5974aa5e01949bf7e66d2af4d08cb8c1d6516af5e39595397f5e", + "sha256:76407ab327158c510f44ded207e2f76b657303e17cb7a572ffe2f5a8a48aa04d", + "sha256:76e9c727a874b4856d11a32fb0b389afc61ce8aaf281ada613713ddeadd1cfec", + "sha256:77d4c1b881076c3ba173484dfa53d3582c1c8ff1f914c6461ab70c8428b796c1", + "sha256:780c82a41dc493b62fc5884fb1d3a3b81106642c5c5c78d6a0d4cbe96d62ba7e", + "sha256:7dc0713bf81287a00516ef43137273b23ee414fe41a3c14be10dd95ed98a2df9", + "sha256:7eebcdbe3677e58dd4c0e03b4f2cfa346ed4049687d839adad68cc38bb559c92", + "sha256:896689fddba4f23ef7c718279e42f8834041a21342d95e56922e1c10c0cc7afb", + "sha256:96177eb5645b1c6985f5c11d03fc2dbda9ad24ec0f3a46dcce91445747e15094", + "sha256:96e25c8603a155559231c19c0349245eeb4ac0096fe3c1d0be5c47e075bd4f46", + "sha256:9d37ac69edc5614b90516807de32d08cb8e7b12260a285ee330955604ed9dd29", + "sha256:9ed6aa0726b9b60911f4aed8ec5b8dd7bf3491476015819f56473ffaef8959bd", + "sha256:a487f72a25904e2b4bbc0817ce7a8de94363bd7e79890510174da9d901c38705", + "sha256:a4cbb9ff5795cd66f0066bdf5947f170f5d63a9274f99bdbca02fd973adcf2a8", + "sha256:a74d56552ddbde46c246b5b89199cb3fd182f9c346c784e1a93e4dc3f5ec9975", + "sha256:a89ce3fd220ff144bd9d54da333ec0de0399b52c9ac3d2ce34b569cf1a5748fb", + "sha256:abd52a09d03adf9c763d706df707c343293d5d106aea53483e0ec8d9e310ad5e", + "sha256:abd8f36c99512755b8456047b7be10372fca271bf1467a1caa88db991e7c421b", + "sha256:af5bd9ccb188f6a5fdda9f1f09d9f4c86cc8a539bd48a0bfdc97723970348418", + "sha256:b02f21c1e2074943312d03d243ac4388319f2456576b2c6023041c4d57cd7019", + "sha256:b06fa97478a5f478fb05e1980980a7cdf2712015493b44d0c87606c1513ed5b1", + "sha256:b0724f05c396b0a4c36a3226c31648385deb6a65d8992644c12a4963c70326ba", + "sha256:b130fe77361d6771ecf5a219d8e0817d61b236b7d8b37cc045172e574ed219e6", + "sha256:b56d5519e470d3f2fe4aa7585f0632b060d532d0696c5bdfb5e8319e1d0f69a2", + "sha256:b67b819628e3b748fd3c2192c15fb951f549d0f47c0449af0764d7647302fda3", + "sha256:ba1711cda2d30634a7e452fc79eabcadaffedf241ff206db2ee93dd2c89a60e7", + "sha256:bbeccb1aa40ab88cd29e6c7d8585582c99548f55f9b2581dfc5ba68c59a85752", + "sha256:bd84395aab8e4d36263cd1b9308cd504f6cf713b7d6d3ce25ea55670baec5416", + "sha256:c99f4309f5145b93eca6e35ac1a988f0dc0a7ccf9ccdcd78d3c0adf57224e62f", + "sha256:ca1cccf838cd28d5a0883b342474c630ac48cac5df0ee6eacc9c7290f76b11c1", + "sha256:cd525e0e52a5ff16653a3fc9e3dd827981917d34996600bbc34c05d048ca35cc", + "sha256:cdb4f085756c96a3af04e6eca7f08b1345e94b53af8921b25c72f096e704e145", + "sha256:ce42618f67741d4697684e501ef02f29e758a123aa2d669e2d964ff734ee00ee", + "sha256:d06730c6aed78cee4126234cf2d071e01b44b915e725a6cb439a879ec9754a3a", + "sha256:d5fe3e099cf07d0fb5a1e23d399e5d4d1ca3e6dfcbe5c8570ccff3e9208274f7", + "sha256:d6bcbfc99f55655c3d93feb7ef3800bd5bbe963a755687cbf1f490a71fb7794b", + "sha256:d787272ed958a05b2c86311d3a4135d3c2aeea4fc655705f074130aa57d71653", + "sha256:e169e957c33576f47e21864cf3fc9ff47c223a4ebca8960079b8bd36cb014fd0", + "sha256:e20076a211cd6f9b44a6be58f7eeafa7ab5720eb796975d0c03f05b47d89eb90", + "sha256:e826aadda3cae59295b95343db8f3d965fb31059da7de01ee8d1c40a60398b29", + "sha256:eef4d64c650f33347c1f9266fa5ae001440b232ad9b98f1f43dfe7a79435c0a6", + "sha256:f2e69b3ed24544b0d3dbe2c5c0ba5153ce50dcebb576fdc4696d52aa22db6034", + "sha256:f87ec75864c37c4c6cb908d282e1969e79763e0d9becdfe9fe5473b7bb1e5f09", + "sha256:fbec11614dba0424ca72f4e8ba3c420dba07b4a7c206c8c8e4e73f2e98f4c559", + "sha256:fd69666217b62fa5d7c6aa88e507493a34dec4fa20c5bd925e4bc12fce586639" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==1.14.1" + "version": "==1.15.0" } } } diff --git a/dev_env/urls.py b/dev_env/urls.py index 6535f3cf..b6647740 100644 --- a/dev_env/urls.py +++ b/dev_env/urls.py @@ -1,4 +1,4 @@ -from django.conf.global_settings import DEBUG +from django.conf import settings from django.contrib import admin from django.urls import path, include @@ -13,10 +13,10 @@ try: if DJANGO_LEDGER_GRAPHQL_ENABLED: from django_ledger.contrib.django_ledger_graphene.api import schema - from django_ledger.contrib.django_ledger_graphene.views import ProtectedOAuth2GraphQLView + from django_ledger.contrib.django_ledger_graphene.views import DjangoLedgerOAuth2GraphQLView urlpatterns += [ - path('api/v1/graphql/', ProtectedOAuth2GraphQLView.as_view(graphiql=DEBUG, schema=schema)), + path('api/v1/graphql/', DjangoLedgerOAuth2GraphQLView.as_view(graphiql=settings.DEBUG, schema=schema)), path('api/v1/o/', include('oauth2_provider.urls', namespace='oauth2_provider')), ] diff --git a/django_ledger/__init__.py b/django_ledger/__init__.py index 9a0ec960..fd6d4ff5 100644 --- a/django_ledger/__init__.py +++ b/django_ledger/__init__.py @@ -9,7 +9,7 @@ default_app_config = 'django_ledger.apps.DjangoLedgerConfig' """Django Ledger""" -__version__ = '0.5.2.17' +__version__ = '0.5.2.18' __license__ = 'GPLv3 License' __author__ = 'Miguel Sanda' diff --git a/django_ledger/contrib/django_ledger_graphene/api.py b/django_ledger/contrib/django_ledger_graphene/api.py index c589cad9..9fb605d5 100644 --- a/django_ledger/contrib/django_ledger_graphene/api.py +++ b/django_ledger/contrib/django_ledger_graphene/api.py @@ -1,21 +1,17 @@ import graphene -from django.urls import reverse -from django.utils.functional import SimpleLazyObject -from django_ledger.contrib.django_ledger_graphene.coa.schema import ChartOfAccountsQuery -from django_ledger.contrib.django_ledger_graphene.entity.schema import EntityModelQuery - -API_PATH = SimpleLazyObject(lambda: reverse("api")) +from django_ledger.contrib.django_ledger_graphene.coa.schema import ChartOfAccountsModelType +from django_ledger.contrib.django_ledger_graphene.entity.schema import EntityModelQuery, EntityModelType class Query( + EntityModelQuery, + # ChartOfAccountsModelQuery # CustomerQuery, # Bill_list_Query, # Accountlist_Query, # Bank_account_Query , # ChartOfAccountsQuery, - EntityModelQuery, - ChartOfAccountsQuery # UnitOfMeasureQuery, # VendorsQuery, # EntityUnitQuery, @@ -28,15 +24,19 @@ class Query( pass -class Mutation( - # CustomerMutations, - # BankAccountMutations, - # AuthMutation, -): - pass +# class Mutation( +# # CustomerMutations, +# # BankAccountMutations, +# # AuthMutation, +# ): +# pass schema = graphene.Schema( + types=[ + EntityModelType, + ChartOfAccountsModelType + ], query=Query, # mutation=Mutation ) diff --git a/django_ledger/contrib/django_ledger_graphene/coa/schema.py b/django_ledger/contrib/django_ledger_graphene/coa/schema.py index 092e6e9c..9f1d679a 100644 --- a/django_ledger/contrib/django_ledger_graphene/coa/schema.py +++ b/django_ledger/contrib/django_ledger_graphene/coa/schema.py @@ -1,4 +1,3 @@ - import graphene from graphene import relay from graphene_django import DjangoObjectType @@ -6,7 +5,7 @@ from django_ledger.models import ChartOfAccountModel -class ChartOfAccountsModelListNode(DjangoObjectType): +class ChartOfAccountsModelType(DjangoObjectType): class Meta: model = ChartOfAccountModel fields = [ @@ -17,16 +16,15 @@ class Meta: ] interfaces = (relay.Node,) - -class ChartOfAccountsQuery(graphene.ObjectType): - all_coa = graphene.List(ChartOfAccountsModelListNode, slug=graphene.String(required=True)) - - def resolve_all_coa(self, info, slug, **kwargs): - - if info.context.user.is_authenticated: - return ChartOfAccountModel.objects.for_entity( - entity_slug=slug, - user_model=info.context.user, - ) - else: - return ChartOfAccountModel.objects.none() +# class ChartOfAccountsModelQuery(graphene.ObjectType): +# all_coa = graphene.List(ChartOfAccountsModelType, slug=graphene.String(required=True)) +# +# def resolve_all_coa(self, info, slug, **kwargs): +# +# if info.context.user.is_authenticated: +# return ChartOfAccountModel.objects.for_entity( +# entity_slug=slug, +# user_model=info.context.user, +# ) +# else: +# return ChartOfAccountModel.objects.none() diff --git a/django_ledger/contrib/django_ledger_graphene/entity/schema.py b/django_ledger/contrib/django_ledger_graphene/entity/schema.py index df5102ac..5fa7a274 100644 --- a/django_ledger/contrib/django_ledger_graphene/entity/schema.py +++ b/django_ledger/contrib/django_ledger_graphene/entity/schema.py @@ -18,7 +18,7 @@ ] -class EntityModelListNode(DjangoObjectType): +class EntityModelType(DjangoObjectType): is_admin = graphene.Boolean() def resolve_is_admin(self, info): @@ -29,29 +29,32 @@ class Meta: model = EntityModel fields = ENTITY_MODEL_BASE_FIELDS filter_fields = { - 'name': ['exact', 'icontains', 'istartswith'], + 'name': [ + 'exact', + 'icontains', + 'istartswith' + ], } interfaces = (relay.Node,) -class EntityModelDetailNode(EntityModelListNode): +class EntityModelTypeDetail(EntityModelType): class Meta: model = EntityModel fields = ENTITY_MODEL_BASE_FIELDS + [ - 'default_coa', - # 'chartofaccountmodel_set' + 'default_coa' ] class EntityModelQuery(graphene.ObjectType): - all_entity_list = graphene.List(EntityModelListNode) - visible_entity_list = graphene.List(EntityModelListNode) - hidden_entity_list = graphene.List(EntityModelListNode) - managed_entity_list = graphene.List(EntityModelListNode) - admin_entity_list = graphene.List(EntityModelListNode) + entity_model_list_all = graphene.List(EntityModelType) + entity_model_list_visible = graphene.List(EntityModelType) + entity_model_list_hidden = graphene.List(EntityModelType) + entity_model_list_managed = graphene.List(EntityModelType) + entity_model_list_is_admin = graphene.List(EntityModelType) - entity_detail_by_uuid = graphene.Field(EntityModelDetailNode, uuid=graphene.String(required=True)) - entity_detail_by_slug = graphene.Field(EntityModelDetailNode, slug=graphene.String(required=True)) + entity_model_detail_by_uuid = graphene.Field(EntityModelTypeDetail, uuid=graphene.String(required=True)) + entity_model_detail_by_slug = graphene.Field(EntityModelTypeDetail, slug=graphene.String(required=True)) @staticmethod def get_base_queryset(info): @@ -60,32 +63,32 @@ def get_base_queryset(info): return EntityModel.objects.none() # list .... - def resolve_all_entity_list(self, info, **kwargs): + def resolve_entity_model_list_all(self, info, **kwargs): return EntityModelQuery.get_base_queryset(info) - def resolve_visible_entity_list(self, info, **kwargs): + def resolve_entity_model_list_visible(self, info, **kwargs): qs = EntityModelQuery.get_base_queryset(info) return qs.visible() - def resolve_hidden_entity_list(self, info, **kwargs): + def resolve_entity_model_list_hidden(self, info, **kwargs): qs = EntityModelQuery.get_base_queryset(info) return qs.hidden() - def resolve_managed_entity_list(self, info, **kwargs): + def resolve_entity_model_list_managed(self, info, **kwargs): qs: EntityModelQuerySet = EntityModelQuery.get_base_queryset(info) user_model = info.context.resource_owner return qs.filter(managers__in=[user_model]) - def resolve_admin_entity_list(self, info, **kwargs): + def resolve_entity_model_list_is_admin(self, info, **kwargs): qs: EntityModelQuerySet = EntityModelQuery.get_base_queryset(info) user_model = info.context.resource_owner return qs.filter(admin=user_model) # detail... - def resolve_entity_detail_by_slug(self, info, slug, **kwargs): + def resolve_entity_model_detail_by_slug(self, info, slug, **kwargs): qs: EntityModelQuerySet = EntityModelQuery.get_base_queryset(info) - return qs.select_related('default_coa', ).get(slug__exact=slug) + return qs.select_related('default_coa').get(slug__exact=slug) - def resolve_entity_detail_by_uuid(self, info, uuid, **kwargs): + def resolve_entity_model_detail_by_uuid(self, info, uuid, **kwargs): qs: EntityModelQuerySet = EntityModelQuery.get_base_queryset(info) - return qs.select_related('default_coa', ).get(uuid__exact=uuid) + return qs.select_related('default_coa').get(uuid__exact=uuid) diff --git a/django_ledger/contrib/django_ledger_graphene/schema.graphql b/django_ledger/contrib/django_ledger_graphene/schema.graphql index 0772a6ba..8fdddc6a 100644 --- a/django_ledger/contrib/django_ledger_graphene/schema.graphql +++ b/django_ledger/contrib/django_ledger_graphene/schema.graphql @@ -1,37 +1,4 @@ -type Query { - allCoa(slug: String!): [ChartOfAccountsModelListNode] - allEntityList: [EntityModelListNode] - visibleEntityList: [EntityModelListNode] - hiddenEntityList: [EntityModelListNode] - managedEntityList: [EntityModelListNode] - adminEntityList: [EntityModelListNode] - entityDetailByUuid(uuid: String!): EntityModelDetailNode - entityDetailBySlug(slug: String!): EntityModelDetailNode -} - -type ChartOfAccountsModelListNode implements Node { - slug: String! - name: String - uuid: UUID! - locked: Boolean! - - """The ID of the object""" - id: ID! -} - -"""An object with an ID""" -interface Node { - """The ID of the object""" - id: ID! -} - -""" -Leverages the internal Python implementation of UUID (uuid.UUID) to provide native UUID objects -in fields, resolvers and input. -""" -scalar UUID - -type EntityModelListNode implements Node { +type EntityModelType implements Node { slug: String! created: DateTime! updated: DateTime @@ -47,6 +14,12 @@ type EntityModelListNode implements Node { isAdmin: Boolean } +"""An object with an ID""" +interface Node { + """The ID of the object""" + id: ID! +} + """ The `DateTime` scalar type represents a DateTime value as specified by @@ -54,6 +27,12 @@ value as specified by """ scalar DateTime +""" +Leverages the internal Python implementation of UUID (uuid.UUID) to provide native UUID objects +in fields, resolvers and input. +""" +scalar UUID + enum DjangoLedgerEntityModelFyStartMonthChoices { """January""" A_1 @@ -92,7 +71,27 @@ enum DjangoLedgerEntityModelFyStartMonthChoices { A_12 } -type EntityModelDetailNode { +type ChartOfAccountsModelType implements Node { + slug: String! + name: String + uuid: UUID! + locked: Boolean! + + """The ID of the object""" + id: ID! +} + +type Query { + entityModelListAll: [EntityModelType] + entityModelListVisible: [EntityModelType] + entityModelListHidden: [EntityModelType] + entityModelListManaged: [EntityModelType] + entityModelListIsAdmin: [EntityModelType] + entityModelDetailByUuid(uuid: String!): EntityModelTypeDetail + entityModelDetailBySlug(slug: String!): EntityModelTypeDetail +} + +type EntityModelTypeDetail { slug: String! created: DateTime! updated: DateTime @@ -102,6 +101,6 @@ type EntityModelDetailNode { accrualMethod: Boolean! fyStartMonth: DjangoLedgerEntityModelFyStartMonthChoices! picture: String - defaultCoa: ChartOfAccountsModelListNode + defaultCoa: ChartOfAccountsModelType isAdmin: Boolean } \ No newline at end of file diff --git a/django_ledger/contrib/django_ledger_graphene/schema.json b/django_ledger/contrib/django_ledger_graphene/schema.json index dac1cab8..c30931bb 100644 --- a/django_ledger/contrib/django_ledger_graphene/schema.json +++ b/django_ledger/contrib/django_ledger_graphene/schema.json @@ -109,32 +109,17 @@ "enumValues": null, "fields": [ { - "args": [ - { - "defaultValue": null, - "description": null, - "name": "slug", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - } - } - ], + "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, - "name": "allCoa", + "name": "slug", "type": { - "kind": "LIST", + "kind": "NON_NULL", "name": null, "ofType": { - "kind": "OBJECT", - "name": "ChartOfAccountsModelListNode", + "kind": "SCALAR", + "name": "String", "ofType": null } } @@ -144,13 +129,13 @@ "deprecationReason": null, "description": null, "isDeprecated": false, - "name": "allEntityList", + "name": "created", "type": { - "kind": "LIST", + "kind": "NON_NULL", "name": null, "ofType": { - "kind": "OBJECT", - "name": "EntityModelListNode", + "kind": "SCALAR", + "name": "DateTime", "ofType": null } } @@ -160,15 +145,11 @@ "deprecationReason": null, "description": null, "isDeprecated": false, - "name": "visibleEntityList", + "name": "updated", "type": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "EntityModelListNode", - "ofType": null - } + "kind": "SCALAR", + "name": "DateTime", + "ofType": null } }, { @@ -176,13 +157,13 @@ "deprecationReason": null, "description": null, "isDeprecated": false, - "name": "hiddenEntityList", + "name": "uuid", "type": { - "kind": "LIST", + "kind": "NON_NULL", "name": null, "ofType": { - "kind": "OBJECT", - "name": "EntityModelListNode", + "kind": "SCALAR", + "name": "UUID", "ofType": null } } @@ -192,13 +173,13 @@ "deprecationReason": null, "description": null, "isDeprecated": false, - "name": "managedEntityList", + "name": "name", "type": { - "kind": "LIST", + "kind": "NON_NULL", "name": null, "ofType": { - "kind": "OBJECT", - "name": "EntityModelListNode", + "kind": "SCALAR", + "name": "String", "ofType": null } } @@ -208,94 +189,45 @@ "deprecationReason": null, "description": null, "isDeprecated": false, - "name": "adminEntityList", + "name": "hidden", "type": { - "kind": "LIST", + "kind": "NON_NULL", "name": null, "ofType": { - "kind": "OBJECT", - "name": "EntityModelListNode", + "kind": "SCALAR", + "name": "Boolean", "ofType": null } } }, { - "args": [ - { - "defaultValue": null, - "description": null, - "name": "uuid", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - } - } - ], + "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, - "name": "entityDetailByUuid", + "name": "accrualMethod", "type": { - "kind": "OBJECT", - "name": "EntityModelDetailNode", - "ofType": null - } - }, - { - "args": [ - { - "defaultValue": null, - "description": null, - "name": "slug", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - } + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null } - ], - "deprecationReason": null, - "description": null, - "isDeprecated": false, - "name": "entityDetailBySlug", - "type": { - "kind": "OBJECT", - "name": "EntityModelDetailNode", - "ofType": null } - } - ], - "inputFields": null, - "interfaces": [], - "kind": "OBJECT", - "name": "Query", - "possibleTypes": null - }, - { - "description": null, - "enumValues": null, - "fields": [ + }, { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, - "name": "slug", + "name": "fyStartMonth", "type": { "kind": "NON_NULL", "name": null, "ofType": { - "kind": "SCALAR", - "name": "String", + "kind": "ENUM", + "name": "DjangoLedgerEntityModelFyStartMonthChoices", "ofType": null } } @@ -305,7 +237,7 @@ "deprecationReason": null, "description": null, "isDeprecated": false, - "name": "name", + "name": "picture", "type": { "kind": "SCALAR", "name": "String", @@ -315,15 +247,15 @@ { "args": [], "deprecationReason": null, - "description": null, + "description": "The ID of the object", "isDeprecated": false, - "name": "uuid", + "name": "id", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", - "name": "UUID", + "name": "ID", "ofType": null } } @@ -333,31 +265,11 @@ "deprecationReason": null, "description": null, "isDeprecated": false, - "name": "locked", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "Boolean", - "ofType": null - } - } - }, - { - "args": [], - "deprecationReason": null, - "description": "The ID of the object", - "isDeprecated": false, - "name": "id", + "name": "isAdmin", "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "ID", - "ofType": null - } + "kind": "SCALAR", + "name": "Boolean", + "ofType": null } } ], @@ -370,7 +282,7 @@ } ], "kind": "OBJECT", - "name": "ChartOfAccountsModelListNode", + "name": "EntityModelType", "possibleTypes": null }, { @@ -401,12 +313,12 @@ "possibleTypes": [ { "kind": "OBJECT", - "name": "ChartOfAccountsModelListNode", + "name": "EntityModelType", "ofType": null }, { "kind": "OBJECT", - "name": "EntityModelListNode", + "name": "ChartOfAccountsModelType", "ofType": null } ] @@ -431,6 +343,16 @@ "name": "String", "possibleTypes": null }, + { + "description": "The `DateTime` scalar type represents a DateTime\nvalue as specified by\n[iso8601](https://en.wikipedia.org/wiki/ISO_8601).", + "enumValues": null, + "fields": null, + "inputFields": null, + "interfaces": null, + "kind": "SCALAR", + "name": "DateTime", + "possibleTypes": null + }, { "description": "Leverages the internal Python implementation of UUID (uuid.UUID) to provide native UUID objects\nin fields, resolvers and input.", "enumValues": null, @@ -453,36 +375,103 @@ }, { "description": null, - "enumValues": null, - "fields": [ + "enumValues": [ { - "args": [], "deprecationReason": null, - "description": null, + "description": "January", "isDeprecated": false, - "name": "slug", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - } + "name": "A_1" + }, + { + "deprecationReason": null, + "description": "February", + "isDeprecated": false, + "name": "A_2" + }, + { + "deprecationReason": null, + "description": "March", + "isDeprecated": false, + "name": "A_3" + }, + { + "deprecationReason": null, + "description": "April", + "isDeprecated": false, + "name": "A_4" + }, + { + "deprecationReason": null, + "description": "May", + "isDeprecated": false, + "name": "A_5" + }, + { + "deprecationReason": null, + "description": "June", + "isDeprecated": false, + "name": "A_6" + }, + { + "deprecationReason": null, + "description": "July", + "isDeprecated": false, + "name": "A_7" + }, + { + "deprecationReason": null, + "description": "August", + "isDeprecated": false, + "name": "A_8" }, + { + "deprecationReason": null, + "description": "September", + "isDeprecated": false, + "name": "A_9" + }, + { + "deprecationReason": null, + "description": "October", + "isDeprecated": false, + "name": "A_10" + }, + { + "deprecationReason": null, + "description": "November", + "isDeprecated": false, + "name": "A_11" + }, + { + "deprecationReason": null, + "description": "December", + "isDeprecated": false, + "name": "A_12" + } + ], + "fields": null, + "inputFields": null, + "interfaces": null, + "kind": "ENUM", + "name": "DjangoLedgerEntityModelFyStartMonthChoices", + "possibleTypes": null + }, + { + "description": null, + "enumValues": null, + "fields": [ { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, - "name": "created", + "name": "slug", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", - "name": "DateTime", + "name": "String", "ofType": null } } @@ -492,10 +481,10 @@ "deprecationReason": null, "description": null, "isDeprecated": false, - "name": "updated", + "name": "name", "type": { "kind": "SCALAR", - "name": "DateTime", + "name": "String", "ofType": null } }, @@ -520,13 +509,13 @@ "deprecationReason": null, "description": null, "isDeprecated": false, - "name": "name", + "name": "locked", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", - "name": "String", + "name": "Boolean", "ofType": null } } @@ -534,31 +523,48 @@ { "args": [], "deprecationReason": null, - "description": null, + "description": "The ID of the object", "isDeprecated": false, - "name": "hidden", + "name": "id", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", - "name": "Boolean", + "name": "ID", "ofType": null } } - }, + } + ], + "inputFields": null, + "interfaces": [ + { + "kind": "INTERFACE", + "name": "Node", + "ofType": null + } + ], + "kind": "OBJECT", + "name": "ChartOfAccountsModelType", + "possibleTypes": null + }, + { + "description": null, + "enumValues": null, + "fields": [ { "args": [], "deprecationReason": null, "description": null, "isDeprecated": false, - "name": "accrualMethod", + "name": "entityModelListAll", "type": { - "kind": "NON_NULL", + "kind": "LIST", "name": null, "ofType": { - "kind": "SCALAR", - "name": "Boolean", + "kind": "OBJECT", + "name": "EntityModelType", "ofType": null } } @@ -568,13 +574,13 @@ "deprecationReason": null, "description": null, "isDeprecated": false, - "name": "fyStartMonth", + "name": "entityModelListVisible", "type": { - "kind": "NON_NULL", + "kind": "LIST", "name": null, "ofType": { - "kind": "ENUM", - "name": "DjangoLedgerEntityModelFyStartMonthChoices", + "kind": "OBJECT", + "name": "EntityModelType", "ofType": null } } @@ -584,25 +590,29 @@ "deprecationReason": null, "description": null, "isDeprecated": false, - "name": "picture", + "name": "entityModelListHidden", "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "EntityModelType", + "ofType": null + } } }, { "args": [], "deprecationReason": null, - "description": "The ID of the object", + "description": null, "isDeprecated": false, - "name": "id", + "name": "entityModelListManaged", "type": { - "kind": "NON_NULL", + "kind": "LIST", "name": null, "ofType": { - "kind": "SCALAR", - "name": "ID", + "kind": "OBJECT", + "name": "EntityModelType", "ofType": null } } @@ -612,117 +622,76 @@ "deprecationReason": null, "description": null, "isDeprecated": false, - "name": "isAdmin", + "name": "entityModelListIsAdmin", "type": { - "kind": "SCALAR", - "name": "Boolean", - "ofType": null + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "EntityModelType", + "ofType": null + } } - } - ], - "inputFields": null, - "interfaces": [ - { - "kind": "INTERFACE", - "name": "Node", - "ofType": null - } - ], - "kind": "OBJECT", - "name": "EntityModelListNode", - "possibleTypes": null - }, - { - "description": "The `DateTime` scalar type represents a DateTime\nvalue as specified by\n[iso8601](https://en.wikipedia.org/wiki/ISO_8601).", - "enumValues": null, - "fields": null, - "inputFields": null, - "interfaces": null, - "kind": "SCALAR", - "name": "DateTime", - "possibleTypes": null - }, - { - "description": null, - "enumValues": [ - { - "deprecationReason": null, - "description": "January", - "isDeprecated": false, - "name": "A_1" - }, - { - "deprecationReason": null, - "description": "February", - "isDeprecated": false, - "name": "A_2" - }, - { - "deprecationReason": null, - "description": "March", - "isDeprecated": false, - "name": "A_3" - }, - { - "deprecationReason": null, - "description": "April", - "isDeprecated": false, - "name": "A_4" - }, - { - "deprecationReason": null, - "description": "May", - "isDeprecated": false, - "name": "A_5" - }, - { - "deprecationReason": null, - "description": "June", - "isDeprecated": false, - "name": "A_6" - }, - { - "deprecationReason": null, - "description": "July", - "isDeprecated": false, - "name": "A_7" - }, - { - "deprecationReason": null, - "description": "August", - "isDeprecated": false, - "name": "A_8" - }, - { - "deprecationReason": null, - "description": "September", - "isDeprecated": false, - "name": "A_9" - }, - { - "deprecationReason": null, - "description": "October", - "isDeprecated": false, - "name": "A_10" }, { + "args": [ + { + "defaultValue": null, + "description": null, + "name": "uuid", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + } + ], "deprecationReason": null, - "description": "November", + "description": null, "isDeprecated": false, - "name": "A_11" + "name": "entityModelDetailByUuid", + "type": { + "kind": "OBJECT", + "name": "EntityModelTypeDetail", + "ofType": null + } }, { + "args": [ + { + "defaultValue": null, + "description": null, + "name": "slug", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + } + ], "deprecationReason": null, - "description": "December", + "description": null, "isDeprecated": false, - "name": "A_12" + "name": "entityModelDetailBySlug", + "type": { + "kind": "OBJECT", + "name": "EntityModelTypeDetail", + "ofType": null + } } ], - "fields": null, "inputFields": null, - "interfaces": null, - "kind": "ENUM", - "name": "DjangoLedgerEntityModelFyStartMonthChoices", + "interfaces": [], + "kind": "OBJECT", + "name": "Query", "possibleTypes": null }, { @@ -873,7 +842,7 @@ "name": "defaultCoa", "type": { "kind": "OBJECT", - "name": "ChartOfAccountsModelListNode", + "name": "ChartOfAccountsModelType", "ofType": null } }, @@ -893,7 +862,7 @@ "inputFields": null, "interfaces": [], "kind": "OBJECT", - "name": "EntityModelDetailNode", + "name": "EntityModelTypeDetail", "possibleTypes": null }, { diff --git a/django_ledger/contrib/django_ledger_graphene/views.py b/django_ledger/contrib/django_ledger_graphene/views.py index 1f3c74c3..89a797b8 100644 --- a/django_ledger/contrib/django_ledger_graphene/views.py +++ b/django_ledger/contrib/django_ledger_graphene/views.py @@ -5,7 +5,7 @@ @method_decorator(csrf_exempt, name='dispatch') -class ProtectedOAuth2GraphQLView( +class DjangoLedgerOAuth2GraphQLView( ProtectedResourceView, GraphQLView ): diff --git a/django_ledger/exceptions.py b/django_ledger/exceptions.py index d4cb622c..282a13b6 100644 --- a/django_ledger/exceptions.py +++ b/django_ledger/exceptions.py @@ -9,6 +9,10 @@ from django.core.exceptions import ValidationError +class DjangoLedgerConfigurationError(Exception): + pass + + class InvalidDateInputError(ValidationError): pass diff --git a/django_ledger/io/data_generator.py b/django_ledger/io/data_generator.py index 8906ba49..0c10f615 100644 --- a/django_ledger/io/data_generator.py +++ b/django_ledger/io/data_generator.py @@ -459,7 +459,7 @@ def create_bill(self, date_draft): bill_model: BillModel = BillModel( vendor=choice(self.vendor_models), progress=Decimal(str(round(random(), 2))), - terms=choice(BillModel.TERMS)[0], + terms=choice(BillModel.TERM_CHOICES)[0], amount_due=0, cash_account=choice(self.accounts_by_role[ASSET_CA_CASH]), prepaid_account=choice(self.accounts_by_role[ASSET_CA_PREPAID]), @@ -546,14 +546,14 @@ def create_po(self, date_draft: date): self.logger.info(f'Creating entity purchase order {po_model.po_number}...') po_items = po_model.itemtransactionmodel_set.bulk_create(po_items) - po_model.update_state(itemtxs_list=po_items) + po_model.update_state(itemtxs_qs=po_items) po_model.full_clean() po_model.save() # mark as approved... if random() > 0.25: date_review = self.get_next_date(date_draft) - po_model.mark_as_review(commit=True, date_review=date_review) + po_model.mark_as_review(commit=True, date_in_review=date_review) if random() > 0.5: date_approved = self.get_next_date(date_review) po_model.mark_as_approved(commit=True, date_approved=date_approved) @@ -655,7 +655,7 @@ def create_invoice(self, date_draft: date): invoice_model = InvoiceModel( customer=choice(self.customer_models), progress=Decimal(str(round(random(), 2))), - terms=choice(InvoiceModel.TERMS)[0], + terms=choice(InvoiceModel.TERM_CHOICES)[0], cash_account=choice(self.accounts_by_role[ASSET_CA_CASH]), prepaid_account=choice(self.accounts_by_role[ASSET_CA_RECEIVABLES]), unearned_account=choice(self.accounts_by_role[LIABILITY_CL_DEFERRED_REVENUE]), diff --git a/django_ledger/models/bank_account.py b/django_ledger/models/bank_account.py index 49075178..d233ae42 100644 --- a/django_ledger/models/bank_account.py +++ b/django_ledger/models/bank_account.py @@ -97,22 +97,17 @@ class BackAccountModelAbstract(BankAccountInfoMixIn, CreateUpdateMixIn): Attributes - __________ + ---------- uuid : UUID This is a unique primary key generated for the table. The default value of this field is uuid4(). - name: str A user defined name for the bank account as a String. - entity_model: EntityModel The EntityModel associated with the BankAccountModel instance. - cash_account: AccountModel The AccountModel associated with the BankAccountModel instance. Must be an account with role ASSET_CA_CASH. - active: bool Determines whether the BackAccountModel instance bank account is active. Defaults to True. - hidden: bool Determines whether the BackAccountModel instance bank account is hidden. Defaults to False. """ diff --git a/django_ledger/models/bill.py b/django_ledger/models/bill.py index 316c48d0..71103651 100644 --- a/django_ledger/models/bill.py +++ b/django_ledger/models/bill.py @@ -21,12 +21,12 @@ from datetime import date from decimal import Decimal -from typing import Union, Optional, Tuple, Dict +from typing import Union, Optional, Tuple, Dict, List from uuid import uuid4 from django.core.exceptions import ValidationError, ObjectDoesNotExist from django.db import models, transaction, IntegrityError -from django.db.models import Q, Sum, F +from django.db.models import Q, Sum, F, Count from django.db.models.signals import post_delete from django.shortcuts import get_object_or_404 from django.urls import reverse @@ -34,8 +34,8 @@ from django.utils.translation import gettext_lazy as _ from django_ledger.models.entity import EntityModel -from django_ledger.models.items import ItemTransactionModelQuerySet -from django_ledger.models.mixins import CreateUpdateMixIn, LedgerWrapperMixIn, MarkdownNotesMixIn, PaymentTermsMixIn +from django_ledger.models.items import ItemTransactionModelQuerySet, ItemTransactionModel +from django_ledger.models.mixins import CreateUpdateMixIn, AccrualMixIn, MarkdownNotesMixIn, PaymentTermsMixIn from django_ledger.models.utils import lazy_loader from django_ledger.settings import (DJANGO_LEDGER_DOCUMENT_NUMBER_PADDING, DJANGO_LEDGER_BILL_NUMBER_PREFIX) @@ -238,7 +238,7 @@ def for_entity(self, entity_slug, user_model) -> BillModelQuerySet: ) -class BillModelAbstract(LedgerWrapperMixIn, +class BillModelAbstract(AccrualMixIn, PaymentTermsMixIn, MarkdownNotesMixIn, CreateUpdateMixIn): @@ -483,13 +483,13 @@ def get_migrate_state_desc(self) -> str: """ return f'Bill {self.bill_number} account adjustment.' - def validate_item_transaction_qs(self, queryset: ItemTransactionModelQuerySet): + def validate_item_transaction_qs(self, queryset: Union[ItemTransactionModelQuerySet, List[ItemTransactionModel]]): """ Validates that the entire ItemTransactionModelQuerySet is bound to the BillModel. Parameters ---------- - queryset: ItemTransactionModelQuerySet + queryset: ItemTransactionModelQuerySet or list of ItemTransactionModel. ItemTransactionModelQuerySet to validate. """ valid = all([ @@ -499,17 +499,20 @@ def validate_item_transaction_qs(self, queryset: ItemTransactionModelQuerySet): raise BillModelValidationError(f'Invalid queryset. All items must be assigned to Bill {self.uuid}') def get_itemtxs_data(self, - queryset: ItemTransactionModelQuerySet = None) -> Tuple[ItemTransactionModelQuerySet, Dict]: + queryset: Optional[ItemTransactionModelQuerySet] = None, + aggregate_on_db: bool = False, + ) -> Tuple[ItemTransactionModelQuerySet, Dict]: """ Fetches the BillModel Items and aggregates the QuerySet. Parameters - __________ + ---------- queryset: Optional pre-fetched ItemModelQueryset to use. Avoids additional DB query if provided. - + aggregate_on_db: bool + If True, performs aggregation of ItemsTransactions in the DB resulting in one additional DB query. Returns - _______ + ------- A tuple: ItemTransactionModelQuerySet, dict """ if not queryset: @@ -517,6 +520,11 @@ def get_itemtxs_data(self, else: self.validate_item_transaction_qs(queryset) + if aggregate_on_db and isinstance(queryset, ItemTransactionModelQuerySet): + return queryset, queryset.aggregate( + total_amount__sum=Sum('total_amount'), + total_items=Count('uuid') + ) return queryset, { 'total_amount__sum': sum(i.total_amount for i in queryset), 'total_items': len(queryset) @@ -552,14 +560,14 @@ def get_migration_data(self, ) def update_amount_due(self, - itemtxs_qs: Optional[ItemTransactionModelQuerySet] = None + itemtxs_qs: Optional[Union[ItemTransactionModelQuerySet, List[ItemTransactionModel]]] = None ) -> ItemTransactionModelQuerySet: """ Updates the BillModel amount due. Parameters ---------- - itemtxs_qs: ItemTransactionModelQuerySet + itemtxs_qs: ItemTransactionModelQuerySet or list of ItemTransactionModel Optional pre-fetched ItemTransactionModelQuerySet. Avoids additional DB if provided. Queryset is validated if provided. @@ -1662,7 +1670,7 @@ def clean(self, commit: bool = True): If True, commits into DB the generated BillModel number if generated. """ - super(LedgerWrapperMixIn, self).clean() + super(AccrualMixIn, self).clean() super(PaymentTermsMixIn, self).clean() if self.accrue: diff --git a/django_ledger/models/coa.py b/django_ledger/models/coa.py index c0c24a94..f293893e 100644 --- a/django_ledger/models/coa.py +++ b/django_ledger/models/coa.py @@ -60,11 +60,11 @@ def for_user(self, user_model) -> ChartOfAccountQuerySet: """ Fetches a QuerySet of ChartOfAccountModel that the UserModel as access to. May include ChartOfAccountModel from multiple Entities. The user has access to bills if: - 1. Is listed as Manager of Entity. - 2. Is the Admin of the Entity. + 1. Is listed as Manager of Entity. + 2. Is the Admin of the Entity. Parameters - __________ + ---------- user_model Logged in and authenticated django UserModel instance. diff --git a/django_ledger/models/coa_default.py b/django_ledger/models/coa_default.py index 702e61e4..78e99e46 100644 --- a/django_ledger/models/coa_default.py +++ b/django_ledger/models/coa_default.py @@ -3,27 +3,135 @@ Copyright© EDMA Group Inc licensed under the GPLv3 Agreement. Contributions to this module: -Miguel Sanda -Pranav P Tulshyan -""" + * Miguel Sanda + * Pranav P Tulshyan -""" -This is the base Chart of Accounts that has all the possible accounts that are useful for the prepeartion of the Financial Statements. +This is the base Chart of Accounts that has all the possible accounts that are useful for the preparation of the +Financial Statements. A user may choose to use the default CoA at the creation of each EntityModel but it is not +required. The default CoA is intended to provide a QuickStart solution for most use cases. + +The Chart of Accounts is broadly bifurcated into 5 different Sections: + 1. Assets: + 2. Liabilities + 3. Shareholder's Equity + 4. Expenses + 5. Revenue + +The Django Ledger Default Chart of Accounts must include the following fields: + * Code - String + * Role - A choice from any of the possible account roles (see django_ledger.roles module). + * Balance Type - A CREDIT or DEBIT balance account setting. + * Name - A human readable name. + * Parent - The parent account of the AccountModel instance. + +If the DEFAULT_CHART_OF_ACCOUNTS setting is present, the default CoA will be replace by such setting. -The Chart of Accounts is Braodly Bifurcted into 5 different Sections: +Default Chart of Accounts Table +=============================== -1. Assets: -2. Liabilities -3. Shareholder's Equity -4. Expenses -5. Revenue +====== ========================== ============== =================================================== ======== ================ + code role balance_type name parent root_group +====== ========================== ============== =================================================== ======== ================ + 1910 asset_adjustment debit Securities Unrealized Gains/Losses root_assets + 1920 asset_adjustment debit PPE Unrealized Gains/Losses root_assets + 1010 asset_ca_cash debit Cash root_assets + 1200 asset_ca_inv debit Inventory root_assets + 1050 asset_ca_mkt_sec debit Short Term Investments root_assets + 1300 asset_ca_prepaid debit Prepaid Expenses root_assets + 1100 asset_ca_recv debit Accounts Receivable root_assets + 1110 asset_ca_uncoll credit Uncollectibles root_assets + 1810 asset_ia debit Goodwill root_assets + 1820 asset_ia debit Intellectual Property root_assets + 1830 asset_ia_accum_amort credit Less: Intangible Assets Accumulated Amortization root_assets + 1520 asset_lti_land debit Land root_assets + 1510 asset_lti_notes debit Notes Receivable root_assets + 1530 asset_lti_sec debit Securities root_assets + 1610 asset_ppe_build debit Buildings root_assets + 1611 asset_ppe_build_accum_depr credit Less: Buildings Accumulated Depreciation root_assets + 1630 asset_ppe_equip debit Equipment root_assets + 1631 asset_ppe_equip_accum_depr credit Less: Equipment Accumulated Depreciation root_assets + 1620 asset_ppe_plant debit Plant root_assets + 1640 asset_ppe_plant debit Vehicles root_assets + 1650 asset_ppe_plant debit Furniture & Fixtures root_assets + 1621 asset_ppe_plant_depr credit Less: Plant Accumulated Depreciation root_assets + 1641 asset_ppe_plant_depr credit Less: Vehicles Accumulated Depreciation root_assets + 1651 asset_ppe_plant_depr credit Less: Furniture & Fixtures Accumulated Depreciation root_assets + 3910 eq_adjustment credit Available for Sale root_capital + 3920 eq_adjustment credit PPE Unrealized Gains/Losses root_capital + 3010 eq_capital credit Capital Account 1 root_capital + 3020 eq_capital credit Capital Account 2 root_capital + 3030 eq_capital credit Capital Account 3 root_capital + 3930 eq_dividends debit Dividends & Distributions root_capital + 3110 eq_stock_common credit Common Stock root_capital + 3120 eq_stock_preferred credit Preferred Stock root_capital + 5010 cogs_regular debit Cost of Goods Sold root_cogs + 6075 ex_amortization debit Amortization Expense root_expenses + 6070 ex_depreciation debit Depreciation Expense root_expenses + 6130 ex_interest debit Interest Expense root_expenses + 6500 ex_other debit Misc. Expense root_expenses + 6010 ex_regular debit Advertising root_expenses + 6020 ex_regular debit Amortization root_expenses + 6030 ex_regular debit Auto Expense root_expenses + 6040 ex_regular debit Bad Debt root_expenses + 6050 ex_regular debit Bank Charges root_expenses + 6060 ex_regular debit Commission Expense root_expenses + 6080 ex_regular debit Employee Benefits root_expenses + 6090 ex_regular debit Freight root_expenses + 6110 ex_regular debit Gifts root_expenses + 6120 ex_regular debit Insurance root_expenses + 6140 ex_regular debit Professional Fees root_expenses + 6150 ex_regular debit License Expense root_expenses + 6170 ex_regular debit Maintenance Expense root_expenses + 6180 ex_regular debit Meals & Entertainment root_expenses + 6190 ex_regular debit Office Expense root_expenses + 6220 ex_regular debit Printing root_expenses + 6230 ex_regular debit Postage root_expenses + 6240 ex_regular debit Rent root_expenses + 6250 ex_regular debit Maintenance & Repairs root_expenses + 6251 ex_regular debit Maintenance root_expenses + 6252 ex_regular debit Repairs root_expenses + 6253 ex_regular debit HOA root_expenses + 6254 ex_regular debit Snow Removal root_expenses + 6255 ex_regular debit Lawn Care root_expenses + 6260 ex_regular debit Salaries root_expenses + 6270 ex_regular debit Supplies root_expenses + 6290 ex_regular debit Utilities root_expenses + 6292 ex_regular debit Sewer root_expenses + 6293 ex_regular debit Gas root_expenses + 6294 ex_regular debit Garbage root_expenses + 6295 ex_regular debit Electricity root_expenses + 6300 ex_regular debit Property Management root_expenses + 6400 ex_regular debit Vacancy root_expenses + 6210 ex_taxes debit Payroll Taxes root_expenses + 6280 ex_taxes debit Taxes root_expenses + 4040 in_gain_loss credit Capital Gain/Loss Income root_income + 4030 in_interest credit Interest Income root_income + 4010 in_operational credit Sales Income root_income + 4050 in_other credit Other Income root_income + 4020 in_passive credit Investing Income root_income + 2010 lia_cl_acc_payable credit Accounts Payable root_liabilities + 2060 lia_cl_def_rev credit Deferred Revenues root_liabilities + 2030 lia_cl_int_payable credit Interest Payable root_liabilities + 2050 lia_cl_ltd_mat credit Current Maturities LT Debt root_liabilities + 2070 lia_cl_other credit Other Payables root_liabilities + 2040 lia_cl_st_notes_payable credit Short-Term Notes Payable root_liabilities + 2020 lia_cl_wages_payable credit Wages Payable root_liabilities + 2120 lia_ltl_bonds credit Bonds Payable root_liabilities + 2130 lia_ltl_mortgage credit Mortgage Payable root_liabilities + 2110 lia_ltl_notes credit Long Term Notes Payable root_liabilities +====== ========================== ============== =================================================== ======== ================ """ from itertools import groupby +from typing import Optional, Dict, List +from django_ledger.exceptions import DjangoLedgerConfigurationError from django_ledger.io import roles, ROOT_ASSETS, ROOT_INCOME, ROOT_EXPENSES, ROOT_LIABILITIES, ROOT_CAPITAL, ROOT_COGS +from django_ledger.settings import DJANGO_LEDGER_DEFAULT_COA -CHART_OF_ACCOUNTS = [ +# todo: include a function to use a user-defined CHART_OF_ACCOUNTS option. + +DEFAULT_CHART_OF_ACCOUNTS = [ # ---------# ASSETS START #---------# # CURRENT ASSETS ------ @@ -143,10 +251,13 @@ # EXPENSE ACCOUNTS ------ {'code': '6010', 'role': roles.EXPENSE_OPERATIONAL, 'balance_type': 'debit', 'name': 'Advertising', 'parent': None}, - {'code': '6020', 'role': roles.EXPENSE_OPERATIONAL, 'balance_type': 'debit', 'name': 'Amortization', 'parent': None}, - {'code': '6030', 'role': roles.EXPENSE_OPERATIONAL, 'balance_type': 'debit', 'name': 'Auto Expense', 'parent': None}, + {'code': '6020', 'role': roles.EXPENSE_OPERATIONAL, 'balance_type': 'debit', 'name': 'Amortization', + 'parent': None}, + {'code': '6030', 'role': roles.EXPENSE_OPERATIONAL, 'balance_type': 'debit', 'name': 'Auto Expense', + 'parent': None}, {'code': '6040', 'role': roles.EXPENSE_OPERATIONAL, 'balance_type': 'debit', 'name': 'Bad Debt', 'parent': None}, - {'code': '6050', 'role': roles.EXPENSE_OPERATIONAL, 'balance_type': 'debit', 'name': 'Bank Charges', 'parent': None}, + {'code': '6050', 'role': roles.EXPENSE_OPERATIONAL, 'balance_type': 'debit', 'name': 'Bank Charges', + 'parent': None}, {'code': '6060', 'role': roles.EXPENSE_OPERATIONAL, 'balance_type': 'debit', 'name': 'Commission Expense', 'parent': None}, {'code': '6080', 'role': roles.EXPENSE_OPERATIONAL, 'balance_type': 'debit', 'name': 'Employee Benefits', @@ -156,12 +267,14 @@ {'code': '6120', 'role': roles.EXPENSE_OPERATIONAL, 'balance_type': 'debit', 'name': 'Insurance', 'parent': None}, {'code': '6140', 'role': roles.EXPENSE_OPERATIONAL, 'balance_type': 'debit', 'name': 'Professional Fees', 'parent': None}, - {'code': '6150', 'role': roles.EXPENSE_OPERATIONAL, 'balance_type': 'debit', 'name': 'License Expense', 'parent': None}, + {'code': '6150', 'role': roles.EXPENSE_OPERATIONAL, 'balance_type': 'debit', 'name': 'License Expense', + 'parent': None}, {'code': '6170', 'role': roles.EXPENSE_OPERATIONAL, 'balance_type': 'debit', 'name': 'Maintenance Expense', 'parent': None}, {'code': '6180', 'role': roles.EXPENSE_OPERATIONAL, 'balance_type': 'debit', 'name': 'Meals & Entertainment', 'parent': None}, - {'code': '6190', 'role': roles.EXPENSE_OPERATIONAL, 'balance_type': 'debit', 'name': 'Office Expense', 'parent': None}, + {'code': '6190', 'role': roles.EXPENSE_OPERATIONAL, 'balance_type': 'debit', 'name': 'Office Expense', + 'parent': None}, {'code': '6220', 'role': roles.EXPENSE_OPERATIONAL, 'balance_type': 'debit', 'name': 'Printing', 'parent': None}, {'code': '6230', 'role': roles.EXPENSE_OPERATIONAL, 'balance_type': 'debit', 'name': 'Postage', 'parent': None}, {'code': '6240', 'role': roles.EXPENSE_OPERATIONAL, 'balance_type': 'debit', 'name': 'Rent', 'parent': None}, @@ -170,7 +283,8 @@ {'code': '6251', 'role': roles.EXPENSE_OPERATIONAL, 'balance_type': 'debit', 'name': 'Maintenance', 'parent': None}, {'code': '6252', 'role': roles.EXPENSE_OPERATIONAL, 'balance_type': 'debit', 'name': 'Repairs', 'parent': None}, {'code': '6253', 'role': roles.EXPENSE_OPERATIONAL, 'balance_type': 'debit', 'name': 'HOA', 'parent': None}, - {'code': '6254', 'role': roles.EXPENSE_OPERATIONAL, 'balance_type': 'debit', 'name': 'Snow Removal', 'parent': None}, + {'code': '6254', 'role': roles.EXPENSE_OPERATIONAL, 'balance_type': 'debit', 'name': 'Snow Removal', + 'parent': None}, {'code': '6255', 'role': roles.EXPENSE_OPERATIONAL, 'balance_type': 'debit', 'name': 'Lawn Care', 'parent': None}, {'code': '6260', 'role': roles.EXPENSE_OPERATIONAL, 'balance_type': 'debit', 'name': 'Salaries', 'parent': None}, {'code': '6270', 'role': roles.EXPENSE_OPERATIONAL, 'balance_type': 'debit', 'name': 'Supplies', 'parent': None}, @@ -204,19 +318,51 @@ 'cogs': ROOT_COGS } -for i in CHART_OF_ACCOUNTS: +for i in DEFAULT_CHART_OF_ACCOUNTS: i['root_group'] = PREFIX_MAP[i['role'].split('_')[0]] -CHART_OF_ACCOUNTS.sort(key=lambda x: (x['root_group'], x['role'], x['code'])) +DEFAULT_CHART_OF_ACCOUNTS.sort(key=lambda x: (x['root_group'], x['role'], x['code'])) CHART_OF_ACCOUNTS_ROOT_MAP = { - k: list(v) for k, v in groupby(CHART_OF_ACCOUNTS, key=lambda x: x['root_group']) + k: list(v) for k, v in groupby(DEFAULT_CHART_OF_ACCOUNTS, key=lambda x: x['root_group']) } def verify_unique_code(): - code_list = list(i['code'] for i in CHART_OF_ACCOUNTS) - code_list.sort() - code_gb = groupby(code_list) - return { - code: sum([bool(v) for v in l]) for code, l in code_gb - } + """ + A function that verifies that there are no duplicate code in the Default CoA during the development and launch. + """ + code_list = [i['code'] for i in DEFAULT_CHART_OF_ACCOUNTS] + code_set = set(code_list) + if not len(code_list) == len(code_set): + raise DjangoLedgerConfigurationError('Default CoA is not unique.') + + +def get_default_coa() -> List[Dict]: + if DJANGO_LEDGER_DEFAULT_COA is not None and isinstance(DJANGO_LEDGER_DEFAULT_COA, list): + return DJANGO_LEDGER_DEFAULT_COA + return DEFAULT_CHART_OF_ACCOUNTS + + +def get_default_coa_rst(default_coa: Optional[Dict] = None) -> str: + """ + Converts the provided Chart of Account into restructuredText format. + Parameters + ---------- + default_coa: + A dictionary of chart of accounts. Must follow the same keys as CHART_OF_ACCOUNTS. + + Returns + ------- + str: + The table in RestructuredText format. + """ + try: + from tabulate import tabulate + except ModuleNotFoundError as e: + raise DjangoLedgerConfigurationError(e.msg) + if default_coa: + return tabulate(default_coa, headers='keys', tablefmt='rst') + return tabulate(get_default_coa(), headers='keys', tablefmt='rst') + + +verify_unique_code() diff --git a/django_ledger/models/entity.py b/django_ledger/models/entity.py index c2836209..e6586c5a 100644 --- a/django_ledger/models/entity.py +++ b/django_ledger/models/entity.py @@ -516,6 +516,10 @@ class Meta: def __str__(self): return f'EntityModel: {self.name}' + # ## Logging ### + def get_logger_name(self): + return f'EntityModel {self.uuid}' + def is_admin_user(self, user_model): return user_model.id == self.admin_id diff --git a/django_ledger/models/estimate.py b/django_ledger/models/estimate.py index 7417adbf..ca0c3e90 100644 --- a/django_ledger/models/estimate.py +++ b/django_ledger/models/estimate.py @@ -4,6 +4,14 @@ Contributions to this module: * Miguel Sanda + +The EstimateModel provides the means to estimate customer requests, jobs or quotes that may ultimately be considered +contracts, if approved. The EstimateModels will estimate revenues and costs associated with a specific scope of work +which is documented using ItemTransactionModels. + +Once approved, the user may initiate purchase orders, bills and invoices that will be associated with the EstimateModel +for tracking purposes. It is however not required to always have an EstimateModel, but recommended in order to be able +to produce more specific financial reports associated with a specific scope of work. """ from datetime import date from decimal import Decimal @@ -38,7 +46,7 @@ class EstimateModelValidationError(ValidationError): class EstimateModelQuerySet(models.QuerySet): """ - A custom defined QuerySet for the EstimateModel. + A custom-defined LedgerModelManager that implements custom QuerySet methods related to the EstimateModel. """ def approved(self): @@ -94,8 +102,7 @@ def estimates(self): class EstimateModelManager(models.Manager): """ - A custom defined EstimateModelManager that will act as an interface to handling the initial DB queries - to the EstimateModel. + A custom defined EstimateModelManager that that implements custom QuerySet methods related to the EstimateModel. """ def for_entity(self, entity_slug: Union[EntityModel, str], user_model): @@ -149,67 +156,50 @@ class EstimateModelAbstract(CreateUpdateMixIn, MarkdownNotesMixIn): ---------- uuid : UUID This is a unique primary key generated for the table. The default value of this field is uuid4(). - estimate_number: str Auto assigned number at creation by generate_estimate_number() function. Prefix be customized with DJANGO_LEDGER_ESTIMATE_NUMBER_PREFIX setting. Includes a reference to the Fiscal Year and a sequence number. Max Length is 20. - entity: EntityModel The EntityModel associated with te EntityModel instance. - customer: CustomerModel The CustomerModel associated with the EstimateModel instance. - title: str A string representing the name or title of the EstimateModel instance. - status: str The status of the EstimateModel instance. Must be one of Draft, In Review, Approved, Completed Void or Canceled. - terms: str The contract terms that will be associated with this EstimateModel instance. Choices are Fixed Price, Target Price, Time & Materials and Other. - date_draft: date The draft date represents the date when the EstimateModel was first created. Defaults to :func:`localdate `. - date_in_review: date The in review date represents the date when the EstimateModel was marked as In Review status. Will be null if EstimateModel is canceled during draft status. Defaults to :func:`localdate `. - date_approved: date The approved date represents the date when the EstimateModel was approved. Will be null if EstimateModel is canceled. Defaults to :func:`localdate `. - date_completed: date The paid date represents the date when the EstimateModel was completed and fulfilled. Will be null if EstimateModel is canceled. Defaults to :func:`localdate `. - date_void: date The void date represents the date when the EstimateModel was void, if applicable. Will be null unless EstimateModel is void. Defaults to :func:`localdate `. - date_canceled: date The canceled date represents the date when the EstimateModel was canceled, if applicable. Will be null unless EstimateModel is canceled. Defaults to :func:`localdate `. - revenue_estimate: Decimal The total estimated revenue of the EstimateModel instance. - labor_estimate: Decimal The total labor costs estimate of the EstimateModel instance. - material_estimate: Decimal The total material costs estimate of the EstimateModel instance. - equipment_estimate: Decimal The total equipment costs estimate of the EstimateModel instance. - other_estimate: Decimal the total miscellaneous costs estimate of the EstimateModel instance. """ @@ -999,7 +989,9 @@ def get_html_id(self): # ItemThroughModels... def get_itemtxs_data(self, - itemtxs_qs: Optional[ItemTransactionModelQuerySet] = None) -> ItemTransactionModelQuerySet: + itemtxs_qs: Optional[Union[ItemTransactionModelQuerySet, List[ItemTransactionModel]]] = None + ) -> ItemTransactionModelQuerySet: + # todo: this needs to return an aggregate for consistency... """ Returns all ItemTransactionModels associated with the EstimateModel and a total aggregate. @@ -1112,7 +1104,8 @@ def update_cost_estimate(self, itemtxs_qs: Optional[ItemTransactionModelQuerySet 'updated' ]) - def update_state(self, itemtxs_qs: Optional[ItemTransactionModelQuerySet] = None): + def update_state(self, + itemtxs_qs: Optional[Union[ItemTransactionModelQuerySet, List[ItemTransactionModel]]] = None): itemtxs_qs = self.get_itemtxs_data(itemtxs_qs=itemtxs_qs) self.update_cost_estimate(itemtxs_qs) self.update_revenue_estimate(itemtxs_qs) diff --git a/django_ledger/models/invoice.py b/django_ledger/models/invoice.py index 1cb8cb17..1b6ac040 100644 --- a/django_ledger/models/invoice.py +++ b/django_ledger/models/invoice.py @@ -37,7 +37,7 @@ from django_ledger.models import lazy_loader, ItemTransactionModelQuerySet from django_ledger.models.entity import EntityModel -from django_ledger.models.mixins import CreateUpdateMixIn, LedgerWrapperMixIn, MarkdownNotesMixIn, PaymentTermsMixIn +from django_ledger.models.mixins import CreateUpdateMixIn, AccrualMixIn, MarkdownNotesMixIn, PaymentTermsMixIn from django_ledger.settings import DJANGO_LEDGER_DOCUMENT_NUMBER_PADDING, DJANGO_LEDGER_INVOICE_NUMBER_PREFIX UserModel = get_user_model() @@ -176,16 +176,14 @@ def for_entity(self, entity_slug, user_model) -> InvoiceModelQuerySet: May pass an instance of EntityModel or a String representing the EntityModel slug. Parameters - __________ - + ---------- entity_slug: str or EntityModel The entity slug or EntityModel used for filtering the QuerySet. - user_model The request UserModel to check for privileges. Returns - _______ + ------- InvoiceModelQuerySet A Filtered InvoiceModelQuerySet. """ @@ -203,7 +201,7 @@ def for_entity_unpaid(self, entity_slug, user_model): return qs.approved() -class InvoiceModelAbstract(LedgerWrapperMixIn, +class InvoiceModelAbstract(AccrualMixIn, PaymentTermsMixIn, MarkdownNotesMixIn, CreateUpdateMixIn): @@ -218,65 +216,48 @@ class InvoiceModelAbstract(LedgerWrapperMixIn, Attributes - __________ - + ---------- uuid: UUID This is a unique primary key generated for the table. The default value of this field is uuid4(). - invoice_number: str Auto assigned number at creation by generate_invoice_number() function. Prefix be customized with DJANGO_LEDGER_INVOICE_NUMBER_PREFIX setting. Includes a reference to the Fiscal Year, Entity Unit and a sequence number. Max Length is 20. - invoice_status: str Current status of the InvoiceModel. Must be one of the choices as mentioned under "INVOICE_STATUS". By default, the status will be "Draft" - xref: str This is the field for capturing of any external reference number like the PO number of the buyer. Any other reference number like the Vendor code in buyer books may also be captured. - customer: :obj:`CustomerModel` This is the foreign key reference to the CustomerModel from whom the purchase has been made. - additional_info: dict Any additional metadata about the InvoiceModel may be stored here as a dictionary object. The data is serialized and stored as a JSON document in the Database. - invoice_items: A foreign key reference to the list of ItemTransactionModel that make the invoice amount. - ce_model: EstimateModel A foreign key to the InvoiceModel associated EstimateModel for overall Job/Contract tracking. - date_draft: date The draft date represents the date when the InvoiceModel was first created. Defaults to :func:`localdate `. - date_in_review: date The in review date represents the date when the InvoiceModel was marked as In Review status. Will be null if InvoiceModel is canceled during draft status. Defaults to :func:`localdate `. - date_approved: date The approved date represents the date when the InvoiceModel was approved. Will be null if InvoiceModel is canceled. Defaults to :func:`localdate `. - date_paid: date The paid date represents the date when the InvoiceModel was paid and amount_due equals amount_paid. Will be null if InvoiceModel is canceled. Defaults to :func:`localdate `. - date_void: date The void date represents the date when the InvoiceModel was void, if applicable. Will be null unless InvoiceModel is void. Defaults to :func:`localdate `. - date_canceled: date The canceled date represents the date when the InvoiceModel was canceled, if applicable. Will be null unless InvoiceModel is canceled. Defaults to :func:`localdate `. - - objects: InvoiceModelManager - Custom defined InvoiceModelManager. """ IS_DEBIT_BALANCE = True @@ -1349,13 +1330,13 @@ def mark_as_delete(self, **kwargs): def get_mark_as_delete_html_id(self) -> str: """ - InvoiceModel Mark as Delete HTML ID Tag. + InvoiceModel Mark as Delete URL. Returns _______ str - HTML ID as a String. + URL as a String. """ return f'djl-invoice-model-{self.uuid}-mark-as-delete' @@ -1563,7 +1544,7 @@ def clean(self, commit: bool = True): If True, commits into DB the generated InvoiceModel number if generated. """ - super(LedgerWrapperMixIn, self).clean() + super(AccrualMixIn, self).clean() super(PaymentTermsMixIn, self).clean() if self.accrue: diff --git a/django_ledger/models/items.py b/django_ledger/models/items.py index 0bd5f605..6bce87b1 100644 --- a/django_ledger/models/items.py +++ b/django_ledger/models/items.py @@ -3,35 +3,44 @@ Copyright© EDMA Group Inc licensed under the GPLv3 Agreement. Contributions to this module: -Miguel Sanda -Pranav P Tulshyan + * Miguel Sanda + * Pranav P Tulshyan +The Items refer to the additional detail provided to Bills, Invoices, Purchase Orders and Estimates for the purposes of +documenting a breakdown of materials, labor, equipment, and other resources used for the purposes of the business +operations. + +The items associated with any of the aforementioned models are responsible for calculating the different amounts +that ultimately drive the behavior of Journal Entries onto the company books. + +Each item must be assigned a UnitOfMeasureModel which is the way or method used to quantify such resource. Examples +are Pounds, Gallons, Man Hours, etc used to measure how resources are quantified when associated with a specific +ItemTransactionModel. If many unit of measures are used for the same item, it would constitute a different item hence a +new record must be created. + +ItemsTransactionModels constitute the way multiple items and used resources are associated with Bills, Invoices, +Purchase Orders and Estimates. Each transaction will record the unit of measure and quantity of each resource. +Totals will be calculated and associated with the containing model at the time of update. """ from decimal import Decimal from string import ascii_lowercase, digits -from uuid import uuid4 +from uuid import uuid4, UUID from django.core.exceptions import ValidationError, ObjectDoesNotExist from django.core.validators import MinValueValidator from django.db import models, transaction, IntegrityError -from django.db.models import Q, Sum, F, ExpressionWrapper, DecimalField, Value, Case, When +from django.db.models import Q, Sum, F, ExpressionWrapper, DecimalField, Value, Case, When, QuerySet from django.db.models.functions import Coalesce from django.utils.translation import gettext_lazy as _ -from django_ledger.models import lazy_loader from django_ledger.models.mixins import CreateUpdateMixIn +from django_ledger.models.utils import lazy_loader from django_ledger.settings import (DJANGO_LEDGER_TRANSACTION_MAX_TOLERANCE, DJANGO_LEDGER_DOCUMENT_NUMBER_PADDING, DJANGO_LEDGER_EXPENSE_NUMBER_PREFIX, DJANGO_LEDGER_INVENTORY_NUMBER_PREFIX, DJANGO_LEDGER_PRODUCT_NUMBER_PREFIX) ITEM_LIST_RANDOM_SLUG_SUFFIX = ascii_lowercase + digits -""" -The Item list is a collection of all the products that are sold by any organization. -The tems may include Products or even services. - -""" - class ItemModelValidationError(ValidationError): pass @@ -39,9 +48,35 @@ class ItemModelValidationError(ValidationError): # UNIT OF MEASURES MODEL.... class UnitOfMeasureModelManager(models.Manager): + """ + A custom defined QuerySet Manager for the UnitOfMeasureModel. + """ - def for_entity(self, entity_slug: str, user_model): + def for_entity(self, entity_slug: str, user_model) -> QuerySet: + """ + Fetches the UnitOfMeasureModels associated with the provided EntityModel and UserModel. + + Parameters + ---------- + entity_slug: str or EntityModel + The EntityModel slug or EntityModel used to filter the QuerySet. + user_model: UserModel + The Django UserModel to check permissions. + + Returns + ------- + QuerySet + A QuerySet with applied filters. + """ qs = self.get_queryset() + if isinstance(entity_slug, lazy_loader.get_entity_model()): + return qs.filter( + Q(entity=entity_slug) & + ( + Q(entity__admin=user_model) | + Q(entity__managers__in=[user_model]) + ) + ) return qs.filter( Q(entity__slug__exact=entity_slug) & ( @@ -51,15 +86,48 @@ def for_entity(self, entity_slug: str, user_model): ) def for_entity_active(self, entity_slug: str, user_model): + """ + Fetches the Active UnitOfMeasureModels associated with the provided EntityModel and UserModel. + + Parameters + ---------- + entity_slug: str or EntityModel + The EntityModel slug or EntityModel used to filter the QuerySet. + user_model: UserModel + The Django UserModel to check permissions. + + Returns + ------- + QuerySet + A QuerySet with applied filters. + """ qs = self.for_entity(entity_slug=entity_slug, user_model=user_model) return qs.filter(is_active=True) class UnitOfMeasureModelAbstract(CreateUpdateMixIn): + """ + Base implementation of a Unit of Measure assigned to each Item Transaction. + + Attributes + ---------- + uuid: UUID + This is a unique primary key generated for the table. The default value of this field is uuid4(). + name: str + The name of the unit of measure. Maximum of 50 characters. + unit_abbr: str + An abbreviation of the unit of measure used as an identifier or slug for URLs and queries. + is_active: bool + A boolean representing of the UnitOfMeasureModel instance is active to be used on new transactions. + entity: EntityModel + The EntityModel associated with the UnitOfMeasureModel instance. + """ uuid = models.UUIDField(default=uuid4, editable=False, primary_key=True) name = models.CharField(max_length=50, verbose_name=_('Unit of Measure Name')) unit_abbr = models.SlugField(max_length=10, verbose_name=_('UoM Abbreviation')) is_active = models.BooleanField(default=True, verbose_name=_('Is Active')) + + # todo: rename to entity_model entity = models.ForeignKey('django_ledger.EntityModel', editable=False, on_delete=models.CASCADE, @@ -82,15 +150,53 @@ def __str__(self): # ITEM MODEL.... class ItemModelQuerySet(models.QuerySet): + """ + A custom-defined ItemModelQuerySet that implements custom QuerySet methods related to the ItemModel. + """ def active(self): + """ + Filters the QuerySet to only active Item Models. + + Returns + ------- + ItemModelQuerySet + A QuerySet with applied filters. + """ return self.filter(active=True) class ItemModelManager(models.Manager): + """ + A custom defined ItemModelManager that implement custom QuerySet methods related to the ItemModel + """ - def for_entity(self, entity_slug: str, user_model): + def for_entity(self, entity_slug, user_model): + """ + Returns a QuerySet of ItemModel associated with a specific EntityModel & UserModel. + May pass an instance of EntityModel or a String representing the EntityModel slug. + + Parameters + ---------- + entity_slug: str or EntityModel + The entity slug or EntityModel used for filtering the QuerySet. + user_model + The request UserModel to check for privileges. + + Returns + ------- + ItemModelQuerySet + A Filtered ItemModelQuerySet. + """ qs = self.get_queryset() + if isinstance(entity_slug, lazy_loader.get_entity_model()): + return qs.filter( + Q(entity=entity_slug) & + ( + Q(entity__managers__in=[user_model]) | + Q(entity__admin=user_model) + ) + ).select_related('uom') return qs.filter( Q(entity__slug__exact=entity_slug) & ( @@ -99,11 +205,43 @@ def for_entity(self, entity_slug: str, user_model): ) ).select_related('uom') - def for_entity_active(self, entity_slug: str, user_model): + def for_entity_active(self, entity_slug, user_model): + """ + Returns a QuerySet of Active ItemModel associated with a specific EntityModel & UserModel. + May pass an instance of EntityModel or a String representing the EntityModel slug. + + Parameters + ---------- + entity_slug: str or EntityModel + The entity slug or EntityModel used for filtering the QuerySet. + user_model + The request UserModel to check for privileges. + + Returns + ------- + ItemModelQuerySet + A Filtered ItemModelQuerySet. + """ qs = self.for_entity(entity_slug=entity_slug, user_model=user_model) return qs.filter(is_active=True) - def products(self, entity_slug: str, user_model): + def products(self, entity_slug, user_model): + """ + Returns a QuerySet of ItemModels that only qualify as products for a specific EntityModel & UserModel. + May pass an instance of EntityModel or a String representing the EntityModel slug. + + Parameters + ---------- + entity_slug: str or EntityModel + The entity slug or EntityModel used for filtering the QuerySet. + user_model + The request UserModel to check for privileges. + + Returns + ------- + ItemModelQuerySet + A Filtered ItemModelQuerySet. + """ qs = self.for_entity_active(entity_slug=entity_slug, user_model=user_model) return qs.filter( ( @@ -113,7 +251,23 @@ def products(self, entity_slug: str, user_model): Q(item_role=ItemModel.ITEM_ROLE_PRODUCT) ) - def services(self, entity_slug: str, user_model): + def services(self, entity_slug, user_model): + """ + Returns a QuerySet of ItemModels that only qualify as active services for a specific EntityModel & UserModel. + May pass an instance of EntityModel or a String representing the EntityModel slug. + + Parameters + ---------- + entity_slug: str or EntityModel + The entity slug or EntityModel used for filtering the QuerySet. + user_model + The request UserModel to check for privileges. + + Returns + ------- + ItemModelQuerySet + A Filtered ItemModelQuerySet. + """ qs = self.for_entity_active(entity_slug=entity_slug, user_model=user_model) return qs.filter( ( @@ -123,7 +277,23 @@ def services(self, entity_slug: str, user_model): Q(item_role=ItemModel.ITEM_ROLE_SERVICE) ) - def expenses(self, entity_slug: str, user_model): + def expenses(self, entity_slug, user_model): + """ + Returns a QuerySet of ItemModels that only qualify as active products for a specific EntityModel & UserModel. + May pass an instance of EntityModel or a String representing the EntityModel slug. + + Parameters + ---------- + entity_slug: str or EntityModel + The entity slug or EntityModel used for filtering the QuerySet. + user_model + The request UserModel to check for privileges. + + Returns + ------- + ItemModelQuerySet + A Filtered ItemModelQuerySet. + """ qs = self.for_entity_active(entity_slug=entity_slug, user_model=user_model) return qs.filter( ( @@ -132,8 +302,25 @@ def expenses(self, entity_slug: str, user_model): ) | Q(item_role=ItemModel.ITEM_ROLE_EXPENSE) ) - def inventory_wip(self, entity_slug: str, user_model): - qs = self.for_entity_active(entity_slug=entity_slug, user_model=user_model) + def inventory_wip(self, entity_slug, user_model): + """ + Returns a QuerySet of ItemModels that only qualify as inventory in progress for a specific EntityModel & + UserModel. These types of items cannot be sold as they are not considered a finished product. + May pass an instance of EntityModel or a String representing the EntityModel slug. + + Parameters + ---------- + entity_slug: str or EntityModel + The entity slug or EntityModel used for filtering the QuerySet. + user_model + The request UserModel to check for privileges. + + Returns + ------- + ItemModelQuerySet + A Filtered ItemModelQuerySet. + """ + qs = self.for_entity(entity_slug=entity_slug, user_model=user_model) return qs.filter( ( Q(is_product_or_service=False) & @@ -141,8 +328,25 @@ def inventory_wip(self, entity_slug: str, user_model): ) | Q(item_role=ItemModel.ITEM_ROLE_INVENTORY) ) - def inventory_all(self, entity_slug: str, user_model): - qs = self.for_entity_active(entity_slug=entity_slug, user_model=user_model) + def inventory_all(self, entity_slug, user_model): + """ + Returns a QuerySet of ItemModels that qualify as inventory for a specific EntityModel & + UserModel. These types of items may be finished or unfinished. + May pass an instance of EntityModel or a String representing the EntityModel slug. + + Parameters + ---------- + entity_slug: str or EntityModel + The entity slug or EntityModel used for filtering the QuerySet. + user_model + The request UserModel to check for privileges. + + Returns + ------- + ItemModelQuerySet + A Filtered ItemModelQuerySet. + """ + qs = self.for_entity(entity_slug=entity_slug, user_model=user_model) return qs.filter( ( ( @@ -160,7 +364,24 @@ def inventory_all(self, entity_slug: str, user_model): ) ) - def for_bill(self, entity_slug: str, user_model): + def for_bill(self, entity_slug, user_model): + """ + Returns a QuerySet of ItemModels that can only be used for BillModels for a specific EntityModel & + UserModel. These types of items qualify as expenses or inventory purchases. + May pass an instance of EntityModel or a String representing the EntityModel slug. + + Parameters + ---------- + entity_slug: str or EntityModel + The entity slug or EntityModel used for filtering the QuerySet. + user_model + The request UserModel to check for privileges. + + Returns + ------- + ItemModelQuerySet + A Filtered ItemModelQuerySet. + """ qs = self.for_entity_active(entity_slug=entity_slug, user_model=user_model) return qs.filter( ( @@ -170,22 +391,103 @@ def for_bill(self, entity_slug: str, user_model): Q(for_inventory=True) ) - def for_po(self, entity_slug: str, user_model): + def for_po(self, entity_slug, user_model): + """ + Returns a QuerySet of ItemModels that can only be used for PurchaseOrders for a specific EntityModel & + UserModel. These types of items qualify as inventory purchases. + May pass an instance of EntityModel or a String representing the EntityModel slug. + + Parameters + ---------- + entity_slug: str or EntityModel + The entity slug or EntityModel used for filtering the QuerySet. + user_model + The request UserModel to check for privileges. + + Returns + ------- + ItemModelQuerySet + A Filtered ItemModelQuerySet. + """ return self.inventory_all(entity_slug=entity_slug, user_model=user_model) def for_estimate(self, entity_slug: str, user_model): + """ + Returns a QuerySet of ItemModels that can only be used for EstimateModels for a specific EntityModel & + UserModel. These types of items qualify as products. + May pass an instance of EntityModel or a String representing the EntityModel slug. + + Parameters + ---------- + entity_slug: str or EntityModel + The entity slug or EntityModel used for filtering the QuerySet. + user_model + The request UserModel to check for privileges. + + Returns + ------- + ItemModelQuerySet + A Filtered ItemModelQuerySet. + """ return self.products(entity_slug=entity_slug, user_model=user_model) - def for_contract(self, entity_slug: str, user_model, ce_model_uuid): - qs = self.for_estimate( - entity_slug=entity_slug, - user_model=user_model - ) - qs = qs.filter(itemtransactionmodel__ce_model_id=ce_model_uuid) - return qs.distinct('uuid') - class ItemModelAbstract(CreateUpdateMixIn): + """ + Base implementation of the ItemModel. + + Attributes + ---------- + uuid: UUID + This is a unique primary key generated for the table. The default value of this field is uuid4(). + name: str + Human readable name of the ItemModel instance. Maximum of 100 characters. + item_role: str + A choice of ITEM_ROLE_CHOICES that determines whether the ItemModel should be treated as an expense, inventory, + service or product. + item_type: str + A choice of ITEM_TYPE_CHOICES that determines whether the ItemModel should be treated as labor, material, + equipment, lump sum or other. + uom: UnitOfMeasureModel + The assigned UnitOfMeasureModel of the ItemModel instance. Mandatory. + sku: str + The SKU number associated with the ItemModel instance. Maximum 50 characters. + upc: str + The UPC number associated with the ItemModel instance. Maximum 50 characters. + item_id: str + EntityModel specific id associated with the ItemModel instance. Maximum 50 characters. + item_number: str + Auto generated human-readable item number. + is_active: bool + Determines if the ItemModel instance is considered active. Defaults to True. + default_amount: Decimal + The default, prepopulated monetary amount of the ItemModel instance . + for_inventory: bool + Legacy field used to determine if the ItemModel instance is considered an inventory item. Mandatory. + Superseded by item_role field. Will be deprecated. + is_product_or_service: bool + Legacy field used to determine if the ItemModel instance is considered a product or service item. Mandatory. + Superseded by item_role field. Will be deprecated. + sold_as_unit: bool + Determines if only whole numbers can be used when specifying the quantity on ItemTransactionModels. + inventory_account: AccountModel + Inventory account associated with the ItemModel instance. Enforced if ItemModel instance is_inventory() is True. + inventory_received: Decimal + Holds the total quantity of the inventory received for the whole EntityModel instance. + inventory_received_value: Decimal + Holds the total monetary value of the inventory received for the whole EntityModel instance. + cogs_account: AccountModel + COGS account associated with the ItemModel instance. Enforced if ItemModel instance is_inventory() is True. + earnings_account: AccountModel + Earnings account associated with the ItemModel instance. Enforced if ItemModel instance is_product() or + is_service() is True. + expense_account: AccountModel + Expense account associated with the ItemModel instance. Enforced if ItemModel instance is_expense() is True. + additional_info: dict + Additional user defined information stored as JSON document in the Database. + entity: EntityModel + The EntityModel associated with the ItemModel instance. + """ REL_NAME_PREFIX = 'item' ITEM_TYPE_LABOR = 'L' @@ -224,6 +526,8 @@ class ItemModelAbstract(CreateUpdateMixIn): sku = models.CharField(max_length=50, blank=True, null=True, verbose_name=_('SKU Code')) upc = models.CharField(max_length=50, blank=True, null=True, verbose_name=_('UPC Code')) + + # todo: rename this and remove 'id' from it. item_id = models.CharField(max_length=50, blank=True, null=True, verbose_name=_('Internal ID')) item_number = models.CharField(max_length=30, editable=False, verbose_name=_('Item Number')) is_active = models.BooleanField(default=True, verbose_name=_('Is Active')) @@ -293,6 +597,8 @@ class ItemModelAbstract(CreateUpdateMixIn): null=True, default=dict, verbose_name=_('Item Additional Info')) + + # todo: rename to entity_model... entity = models.ForeignKey('django_ledger.EntityModel', editable=False, on_delete=models.CASCADE, @@ -843,47 +1149,97 @@ def __str__(self): return f'Estimate/Contract Model: {self.ce_model_id} | {self.ce_cost_estimate}' return f'Orphan {self.__class__.__name__}: {self.uuid}' - def is_received(self): + def is_received(self) -> bool: + """ + Determines if the ItemModel instance is received. + ItemModel status is only relevant for ItemModels associated with PurchaseOrderModels. + + Returns + ------- + bool + True if received, else False. + """ + return self.po_item_status == self.STATUS_RECEIVED + + def is_ordered(self) -> bool: + """ + Determines if the ItemModel instance is ordered. + ItemModel status is only relevant for ItemModels associated with PurchaseOrderModels. + + Returns + ------- + bool + True if received, else False. + """ return self.po_item_status == self.STATUS_RECEIVED def is_canceled(self): + """ + Determines if the ItemModel instance is canceled. + ItemModel status is only relevant for ItemModels associated with PurchaseOrderModels. + + Returns + ------- + bool + True if canceled, else False. + """ return self.po_item_status == self.STATUS_CANCELED # ItemTransactionModel Associations... - def for_estimate(self) -> bool: + def has_estimate(self) -> bool: """ - True if ItemTransactionModel is associated with an EstimateModel, else False. - @return: True/False + Determines if the ItemModel instance is associated with an EstimateModel. + + Returns + ------- + bool + True if associated with an EstimateModel, else False. """ return self.ce_model_id is not None - def for_po(self): + def has_po(self) -> bool: """ - True if ItemTransactionModel is associated with a PurchaseOrderModel, else False. - @return: True/False + Determines if the ItemModel instance is associated with a PurchaseOrderModel. + + Returns + ------- + bool + True if associated with an PurchaseOrderModel, else False. """ return self.po_model_id is not None - def for_invoice(self): + def has_invoice(self): """ - True if ItemTransactionModel is associated with an InvoiceModel, else False. - @return: True/False + Determines if the ItemModel instance is associated with a InvoiceModel. + + Returns + ------- + bool + True if associated with an InvoiceModel, else False. """ return self.invoice_model_id is not None - def for_bill(self): + def has_bill(self): """ - True if ItemTransactionModel is associated with a BillModel, else False. - @return: True/False + Determines if the ItemModel instance is associated with a BillModel. + + Returns + ------- + bool + True if associated with an BillModel, else False. """ return self.bill_model_id is not None # TRANSACTIONS... def update_total_amount(self): + """ + Hook that updates and checks the ItemModel instance fields according to its associations. + Calculates and updates total_amount accordingly. Called on every clean() call. + """ if any([ - self.for_bill(), - self.for_invoice(), - self.for_po() + self.has_bill(), + self.has_invoice(), + self.has_po() ]): if self.quantity is None: self.quantity = 0.0 @@ -891,11 +1247,9 @@ def update_total_amount(self): if self.unit_cost is None: self.unit_cost = 0.0 - self.total_amount = round( - Decimal.from_float(self.quantity * self.unit_cost), self.DECIMAL_PLACES - ) + self.total_amount = round(Decimal.from_float(self.quantity * self.unit_cost), self.DECIMAL_PLACES) - if self.for_po(): + if self.has_po(): if self.quantity > self.po_quantity: raise ValidationError(f'Billed quantity {self.quantity} cannot be greater than ' @@ -916,18 +1270,25 @@ def update_total_amount(self): # PURCHASE ORDER... def update_po_total_amount(self): - if self.for_po(): + """ + Hook that updates and checks the ItemModel instance purchase order fields according to its associations. + Calculates and updates po_total_amount accordingly. Called on every clean() call. + """ + if self.has_po(): if self.po_quantity is None: self.po_quantity = 0.0 if self.po_unit_cost is None: self.po_unit_cost = 0.0 - self.po_total_amount = round(Decimal.from_float(self.po_quantity * self.po_unit_cost), - self.DECIMAL_PLACES) + self.po_total_amount = round(Decimal.from_float(self.po_quantity * self.po_unit_cost), self.DECIMAL_PLACES) # ESTIMATE/CONTRACTS... def update_cost_estimate(self): - if self.for_estimate(): + """ + Hook that updates and checks the ItemModel instance cost estimate fields according to its associations. + Calculates and updates ce_cost_estimate accordingly. Called on every clean() call. + """ + if self.has_estimate(): if self.ce_quantity is None: self.ce_quantity = 0.00 if self.ce_unit_cost_estimate is None: @@ -936,7 +1297,11 @@ def update_cost_estimate(self): self.DECIMAL_PLACES) def update_revenue_estimate(self): - if self.for_estimate(): + """ + Hook that updates and checks the ItemModel instance revenue estimate fields according to its associations. + Calculates and updates ce_revenue_estimate accordingly. Called on every clean() call. + """ + if self.has_estimate(): if self.ce_quantity is None: self.ce_quantity = 0.00 if self.ce_unit_revenue_estimate is None: @@ -944,36 +1309,70 @@ def update_revenue_estimate(self): self.ce_revenue_estimate = Decimal.from_float(self.ce_quantity * self.ce_unit_revenue_estimate) # HTML TAGS... - def html_id(self): + def html_id(self) -> str: + """ + Unique ItemModel instance HTML ID. + + Returns + _______ + str + HTML ID as a String. + """ return f'djl-item-{self.uuid}' - def html_id_unit_cost(self): + def html_id_unit_cost(self) -> str: + """ + Unique ItemModel instance unit cost field HTML ID. + + Returns + _______ + str + HTML ID as a String. + """ return f'djl-item-unit-cost-id-{self.uuid}' - def html_id_quantity(self): - return f'djl-item-quantity-id-{self.uuid}' + def html_id_quantity(self) -> str: + """ + Unique ItemModel instance quantity field HTML ID. - def is_cancelled(self): - return self.po_item_status == self.STATUS_CANCELED + Returns + _______ + str + HTML ID as a String. + """ + return f'djl-item-quantity-id-{self.uuid}' - def can_create_bill(self): - # pylint: disable=no-member + def can_create_bill(self) -> bool: + """ + Determines if the ItemModel instance can be associated with a BillModel. + Returns + ------- + bool + True, if instance can be associated with a BillModel, else False. + """ return self.bill_model_id is None and self.po_item_status in [ - self.STATUS_ORDERED, self.STATUS_IN_TRANSIT, self.STATUS_RECEIVED + self.STATUS_ORDERED, + self.STATUS_IN_TRANSIT, + self.STATUS_RECEIVED ] - def get_status_css_class(self): - if self.po_item_status == self.STATUS_RECEIVED: + def get_status_css_class(self) -> str: + """ + Determines the CSS Class used to represent the ItemModel instance in the UI based on its status. + + Returns + ------- + str + The CSS class as a String. + """ + if self.is_received(): return ' is-success' - elif self.po_item_status == self.STATUS_CANCELED: + elif self.is_canceled(): return ' is-danger' - elif self.po_item_status == self.STATUS_ORDERED: + elif self.is_ordered(): return ' is-info' return ' is-warning' - def has_po(self): - return self.po_model_id is not None - def clean(self): if self.has_po() and not self.po_item_status: self.po_item_status = self.STATUS_NOT_ORDERED @@ -988,17 +1387,17 @@ def clean(self): class UnitOfMeasureModel(UnitOfMeasureModelAbstract): """ - Base Unit of Measure Model from Abstract. + Base UnitOfMeasureModel from Abstract. """ class ItemTransactionModel(ItemTransactionModelAbstract): """ - Base Item Transaction Model. + Base ItemTransactionModel from Abstract. """ class ItemModel(ItemModelAbstract): """ - Base Item Model from Abstract. + Base ItemModel from Abstract. """ diff --git a/django_ledger/models/journal_entry.py b/django_ledger/models/journal_entry.py index 7575c804..58579333 100644 --- a/django_ledger/models/journal_entry.py +++ b/django_ledger/models/journal_entry.py @@ -230,42 +230,33 @@ class JournalEntryModelAbstract(CreateUpdateMixIn): The base implementation of the JournalEntryModel. Attributes - __________ - uuid : UUID + ---------- + uuid: UUID This is a unique primary key generated for the table. The default value of this field is uuid4(). - je_number: str A unique, sequential, human-readable alphanumeric Journal Entry Number (a.k.a Voucher or Document Number in other commercial bookkeeping software). Contains the fiscal year under which the JE takes place within the EntityModel as a prefix. - timestamp: datetime The date of the JournalEntryModel. This date is applied to all TransactionModels contained within the JE, and drives the financial statements of the EntityModel. - description: str A user defined description for the JournalEntryModel. - entity_unit: EntityUnitModel A logical, self-contained, user defined class or structure defined withing the EntityModel. See EntityUnitModel documentation for more details. - activity: str Programmatically determined based on the JE transactions and must be a value from ACTIVITIES. Gives additional insight of the nature of the JournalEntryModel in order to produce the Statement of Cash Flows for the EntityModel. - origin: str A string giving additional information behind the origin or trigger of the JournalEntryModel. For example: reconciliations, migrations, auto-generated, etc. Any string value is valid. Max 30 characters. - posted: bool Determines if the JournalLedgerModel is posted, which means is affecting the books. Defaults to False. - locked: bool Determines if the JournalEntryModel is locked, which the creation or updates of new transactions are not allowed. - ledger: LedgerModel The LedgerModel associated with this JournalEntryModel. Cannot be null. """ @@ -900,8 +891,8 @@ def can_generate_je_number(self) -> bool: """ Checks if the JournalEntryModel instance can generate its own JE number. Conditions are: - * The JournalEntryModel must have a LedgerModel instance assigned. - * The JournalEntryModel instance must not have a pre-existing JE number. + * The JournalEntryModel must have a LedgerModel instance assigned. + * The JournalEntryModel instance must not have a pre-existing JE number. Returns ------- diff --git a/django_ledger/models/ledger.py b/django_ledger/models/ledger.py index 0fef33e3..d0689e35 100644 --- a/django_ledger/models/ledger.py +++ b/django_ledger/models/ledger.py @@ -3,7 +3,28 @@ Copyright© EDMA Group Inc licensed under the GPLv3 Agreement. Contributions to this module: -Miguel Sanda + * Miguel Sanda + +The LedgerModel is the heart of Django Ledger. It is a self-contained unit of accounting that implements a +double-entry accounting system capable of creating and managing transactions into the ledger and producing any financial +statements. In essence, an EntityModel is made of a collection of LedgerModels that drive the whole bookkeeping process. +Each LedgerModel is independent and they can operate as an individual or as a group. + +Each LedgerModel encapsulates a collection of JournalEntryModels, which in turn hold a collection of TransactionModels. +LedgerModels can be used to represent any part of the EntityModel and can be extended to add additional functionality +and custom logic that drives how transactions are recorded into the books. One example of this is the LedgerWrapperMixIn +(see django_ledger.models.mixins.LedgerWrapperMixIn), which is the foundation of LedgerModel abstractions such as the +BillModel, InvoiceModel, PurchaseOrderModel and EstimateModel. Extending the LedgerModel can add additional +functionality necessary to implement industry-specific functionality to almost anything you can think of. Examples: +Farming Equipment, Real Estate, Investment Portfolio, etc. + +Also, the LedgerModel inherits functionality from the all mighty IOMixIn (see django_ledger.io.io_mixin.IOMixIn), +which is the class responsible for making accounting queries to the Database in an efficient and performing way. +The digest() method executes all necessary aggregations and optimizations in order to push as much work to the Database +layer as possible in order to minimize the amount of data being pulled for analysis into the Python memory. + +The Django Ledger core model follows the following structure: \n +EntityModel -< LedgerModel -< JournalEntryModel -< TransactionModel """ from string import ascii_lowercase, digits @@ -31,10 +52,40 @@ class LedgerModelQuerySet(models.QuerySet): Custom defined LedgerModel QuerySet. """ + def posted(self): + """ + Filters the QuerySet to only posted LedgerModel. + + Returns + ------- + LedgerModelQuerySet + A QuerySet with applied filters. + """ + return self.filter(posted=True) + class LedgerModelManager(models.Manager): + """ + A custom-defined LedgerModelManager that implements custom QuerySet methods related to the LedgerModel. + """ def for_entity(self, entity_slug, user_model): + """ + Returns a QuerySet of LedgerModels associated with a specific EntityModel & UserModel. + May pass an instance of EntityModel or a String representing the EntityModel slug. + + Parameters + ---------- + entity_slug: str or EntityModel + The entity slug or EntityModel used for filtering the QuerySet. + user_model + The request UserModel to check for privileges. + + Returns + ------- + LedgerModelQuerySet + A Filtered LedgerModelQuerySet. + """ qs = self.get_queryset() if isinstance(entity_slug, lazy_loader.get_entity_model()): return qs.filter( @@ -52,13 +103,30 @@ def for_entity(self, entity_slug, user_model): ) ) - def posted(self): - return self.get_queryset().filter(posted=True) - class LedgerModelAbstract(CreateUpdateMixIn, IOMixIn): + """ + Base implmentation of the LedgerModel. + + Attributes + ---------- + uuid: UUID + This is a unique primary key generated for the table. The default value of this field is uuid4(). + name: str + Human-readable name of the LedgerModel. Maximum 150 characters. + entity: EntityModel + The EntityModel associated with the LedgerModel instance. + posted: bool + Determines if the LedgerModel is posted. Defaults to False. Mandatory. + locked: bool + Determines if the LedgerModel is locked. Defaults to False. Mandatory. + hidden: bool + Determines if the LedgerModel is hidden. Defaults to False. Mandatory. + """ uuid = models.UUIDField(default=uuid4, editable=False, primary_key=True) name = models.CharField(max_length=150, null=True, blank=True, verbose_name=_('Ledger Name')) + + # todo: rename to entity_model... entity = models.ForeignKey('django_ledger.EntityModel', editable=False, on_delete=models.CASCADE, @@ -83,33 +151,93 @@ class Meta: def __str__(self): return self.name - def is_posted(self): + def is_posted(self) -> bool: + """ + Determines if the LedgerModel instance is posted. + + Returns + ------- + bool + True if posted, else False. + """ return self.posted is True - def is_locked(self): + def is_locked(self) -> bool: + """ + Determines if the LedgerModel instance is locked. + + Returns + ------- + bool + True if locked, else False. + """ return self.locked is True - def is_hidden(self): + def is_hidden(self) -> bool: + """ + Determines if the LedgerModel instance is hidden. + + Returns + ------- + bool + True if hidden, else False. + """ return self.hidden is True - def get_absolute_url(self): - return reverse('django_ledger:ledger-detail', - kwargs={ - # pylint: disable=no-member - 'entity_slug': self.entity.slug, - 'ledger_pk': self.uuid - }) + def can_post(self) -> bool: + """ + Determines if the LedgerModel can be marked as posted. - def get_update_url(self): - return reverse('django_ledger:ledger-update', - kwargs={ - # pylint: disable=no-member - 'entity_slug': self.entity.slug, - 'ledger_pk': self.uuid - }) + Returns + ------- + bool + True if can be posted, else False. + """ + return self.posted is False + + def can_unpost(self) -> bool: + """ + Determines if the LedgerModel can be un-posted. + + Returns + ------- + bool + True if can be un-posted, else False. + """ + return self.posted is True + + def can_lock(self) -> bool: + """ + Determines if the LedgerModel can be locked. + + Returns + ------- + bool + True if can be locked, else False. + """ + return self.locked is False + + def can_unlock(self) -> bool: + """ + Determines if the LedgerModel can be un-locked. + + Returns + ------- + bool + True if can be un-locked, else False. + """ + return self.locked is True def post(self, commit: bool = False): - if not self.posted: + """ + Posts the LedgerModel. + + Parameters + ---------- + commit: bool + If True, saves the LedgerModel instance instantly. Defaults to False. + """ + if self.can_post(): self.posted = True if commit: self.save(update_fields=[ @@ -118,7 +246,15 @@ def post(self, commit: bool = False): ]) def unpost(self, commit: bool = False): - if self.posted: + """ + Un-posts the LedgerModel. + + Parameters + ---------- + commit: bool + If True, saves the LedgerModel instance instantly. Defaults to False. + """ + if self.can_unpost(): self.posted = False if commit: self.save(update_fields=[ @@ -127,23 +263,74 @@ def unpost(self, commit: bool = False): ]) def lock(self, commit: bool = False): - self.locked = True - if commit: - self.save(update_fields=[ - 'locked', - 'updated' - ]) + """ + Locks the LedgerModel. + + Parameters + ---------- + commit: bool + If True, saves the LedgerModel instance instantly. Defaults to False. + """ + if self.can_lock(): + self.locked = True + if commit: + self.save(update_fields=[ + 'locked', + 'updated' + ]) def unlock(self, commit: bool = False): - self.locked = False - if commit: - self.save(update_fields=[ - 'locked', - 'updated' - ]) + """ + Un-locks the LedgerModel. + + Parameters + ---------- + commit: bool + If True, saves the LedgerModel instance instantly. Defaults to False. + """ + if self.can_unlock(): + self.locked = False + if commit: + self.save(update_fields=[ + 'locked', + 'updated' + ]) + + def get_absolute_url(self) -> str: + """ + Determines the absolute URL of the LedgerModel instance. + Results in additional Database query if entity field is not selected in QuerySet. + + Returns + ------- + str + URL as a string. + """ + return reverse('django_ledger:ledger-detail', + kwargs={ + # pylint: disable=no-member + 'entity_slug': self.entity.slug, + 'ledger_pk': self.uuid + }) + + def get_update_url(self) -> str: + """ + Determines the update URL of the LedgerModel instance. + Results in additional Database query if entity field is not selected in QuerySet. + + Returns + ------- + str + URL as a string. + """ + return reverse('django_ledger:ledger-update', + kwargs={ + 'entity_slug': self.entity.slug, + 'ledger_pk': self.uuid + }) class LedgerModel(LedgerModelAbstract): """ - Ledger Model from Abstract + Base LedgerModel from Abstract. """ diff --git a/django_ledger/models/mixins.py b/django_ledger/models/mixins.py index 2ba67e2b..9f391d31 100644 --- a/django_ledger/models/mixins.py +++ b/django_ledger/models/mixins.py @@ -3,15 +3,20 @@ Copyright© EDMA Group Inc licensed under the GPLv3 Agreement. Contributions to this module: -Miguel Sanda + * Miguel Sanda + +This module implements the different model MixIns used on different Django Ledger Models to implement common +functionality. """ import logging from collections import defaultdict from datetime import timedelta, date, datetime from decimal import Decimal from itertools import groupby -from typing import Optional, Union +from typing import Optional, Union, Dict +from uuid import UUID +from django.conf import settings from django.core.exceptions import ValidationError from django.core.validators import MinValueValidator, MaxValueValidator, MinLengthValidator from django.core.validators import int_list_validator @@ -22,12 +27,24 @@ from django.utils.translation import gettext_lazy as _ from markdown import markdown -from django_ledger.io import balance_tx_data, ASSET_CA_CASH, ASSET_CA_PREPAID, LIABILITY_CL_DEFERRED_REVENUE, \ - validate_io_date +from django_ledger.io import (balance_tx_data, ASSET_CA_CASH, ASSET_CA_PREPAID, LIABILITY_CL_DEFERRED_REVENUE, + validate_io_date) from django_ledger.models.utils import lazy_loader +logging.basicConfig(format='%(asctime)s %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p') + class SlugNameMixIn(models.Model): + """ + Implements a slug field and a name field to a base Django Model. + + Attributes + ---------- + slug: str + A unique slug field to use as an index. Validates that the slug is at least 10 characters long. + name: str + A human-readable name for display purposes. Maximum 150 characters. + """ slug = models.SlugField(max_length=50, editable=False, unique=True, @@ -40,14 +57,17 @@ class SlugNameMixIn(models.Model): class Meta: abstract = True - def __str__(self): - # pylint: disable=invalid-str-returned - return self.slug - class CreateUpdateMixIn(models.Model): """ - The create and update mixin! + Implements a created and an updated field to a base Django Model. + + Attributes + ---------- + created: datetime + A created timestamp. Defaults to now(). + updated: str + An updated timestamp used to identify when models are updated. """ created = models.DateTimeField(auto_now_add=True) updated = models.DateTimeField(auto_now=True, null=True, blank=True) @@ -57,6 +77,30 @@ class Meta: class ContactInfoMixIn(models.Model): + """ + Implements a common set of fields used to document contact information. + + Attributes + ---------- + address_1: str + A string used to document the first line of an address. Mandatory. Max length is 70. + address_2: str + A string used to document the first line of an address. Optional. + city: str + A string used to document the city. Optional. + state: str + A string used to document the State of Province. Optional. + zip_code: str + A string used to document the ZIP code. Optional + country: str + A string used to document the country. Optional. + email: str + A string used to document the contact email. Uses django's EmailField for validation. + website: str + A string used to document the contact website. Uses django's URLField for validation. + phone: str + A string used to document the contact phone. + """ address_1 = models.CharField(max_length=70, verbose_name=_('Address Line 1')) address_2 = models.CharField(null=True, blank=True, max_length=70, verbose_name=_('Address Line 2')) city = models.CharField(null=True, blank=True, max_length=70, verbose_name=_('City')) @@ -80,10 +124,38 @@ def get_cszc(self): return f'{self.city}, {self.state}. {self.zip_code}. {self.country}' -class LedgerWrapperMixIn(models.Model): +class AccrualMixIn(models.Model): """ - The Ledger wrapper! - + Implements functionality used to track accruable financial instruments to a base Django Model. + Examples of this include bills and invoices expenses/income, that depending on the Entity's accrual method, may + be recognized on the Income Statement differently. + + Attributes + ---------- + amount_due: Decimal + The total amount due of the financial instrument. + amount_paid: Decimal + The total amount paid or settled. + amount_receivable: Decimal + The total amount allocated to Accounts Receivable based on the progress. + amount_unearned: Decimal + The total amount allocated to Accounts Payable based on the progress. + amount_earned: + The total amount that is recognized on the earnings based on progress. + accrue: bool + If True, the financial instrument will follow the Accrual Method of Accounting, otherwise it will follow the + Cash Method of Accounting. Defaults to the EntityModel preferred method of accounting. + progress: Decimal + A decimal number representing the amount of progress of the financial instrument. Value is between 0.00 and 1.00. + ledger: LedgerModel + The LedgerModel associated with the Accruable financial instrument. + cash_account: AccountModel + The AccountModel used to track cash payments to the financial instrument. Must be of role ASSET_CA_CASH. + prepaid_account: AccountModel + The AccountModel used to track receivables to the financial instrument. Must be of role ASSET_CA_PREPAID. + unearned_account: AccountModel + The AccountModel used to track receivables to the financial instrument. Must be of role + LIABILITY_CL_DEFERRED_REVENUE. """ IS_DEBIT_BALANCE = None REL_NAME_PREFIX = None @@ -124,7 +196,7 @@ class LedgerWrapperMixIn(models.Model): accrue = models.BooleanField(default=False, verbose_name=_('Accrue')) - # todo: change progress method from percent to currency amount... + # todo: change progress method from percent to currency amount and FloatField??... progress = models.DecimalField(default=0, verbose_name=_('Progress Amount'), decimal_places=2, @@ -134,6 +206,7 @@ class LedgerWrapperMixIn(models.Model): MaxValueValidator(limit_value=1) ]) + # todo: rename to ledger_model... ledger = models.OneToOneField('django_ledger.LedgerModel', editable=False, verbose_name=_('Ledger'), @@ -161,7 +234,15 @@ class Meta: abstract = True # STATES.. - def is_configured(self): + def is_configured(self) -> bool: + """ + Determines if the accruable financial instrument is properly configured. + + Returns + ------- + bool + True if configured, else False. + """ return all([ self.ledger_id is not None, self.cash_account_id is not None, @@ -170,77 +251,152 @@ def is_configured(self): ]) def is_posted(self): + """ + Determines if the accruable financial instrument is posted. + Results in additional Database query if 'ledger' field is not pre-fetch on QuerySet. + + Returns + ------- + bool + True if posted, else False. + """ return self.ledger.posted # OTHERS... - def get_progress(self): + def get_progress(self) -> Union[Decimal, float]: + """ + Determines the progress amount based on amount due, amount paid and accrue field. + + Returns + ------- + Decimal + Financial instrument progress as a Decimal. + """ if self.accrue: return self.progress if not self.amount_due: - return 0 - return (self.amount_paid or 0) / self.amount_due + return Decimal.from_float(0.00) + return (self.amount_paid or Decimal.from_float(0.00)) / self.amount_due - def get_progress_percent(self): + def get_progress_percent(self) -> float: + """ + Determines the progress amount as percent based on amount due, amount paid and accrue field. + + Returns + ------- + float + Financial instrument progress as a percent. + """ return round(self.get_progress() * 100, 2) - def get_amount_cash(self): + def get_amount_cash(self) -> Union[Decimal, float]: + """ + Determines the impact to the EntityModel cash balance based on the financial instrument debit or credit + configuration. i.e, Invoices are debit financial instrument because payments to invoices increase cash. + + Returns + ------- + float + Financial instrument progress as a percent. + """ if self.IS_DEBIT_BALANCE: return self.amount_paid elif not self.IS_DEBIT_BALANCE: return -self.amount_paid - def get_amount_earned(self): + def get_amount_earned(self) -> Union[Decimal, float]: + """ + Determines the impact to the EntityModel earnings based on financial instrument progress. + + Returns + ------- + float or Decimal + Financial instrument amount earned. + """ if self.accrue: - amount_due = self.amount_due or 0 + amount_due = self.amount_due or Decimal.from_float(0.00) return self.get_progress() * amount_due else: - return self.amount_paid or 0 + return self.amount_paid or Decimal.from_float(0.00) + + def get_amount_prepaid(self) -> Union[Decimal, float]: + """ + Determines the impact to the EntityModel Accounts Receivable based on financial instrument progress. - def get_amount_prepaid(self): - payments = self.amount_paid or 0 + Returns + ------- + float or Decimal + Financial instrument amount prepaid. + """ + payments = self.amount_paid or Decimal.from_float(0.00) if self.accrue: amt_earned = self.get_amount_earned() - if all([self.IS_DEBIT_BALANCE, - amt_earned >= payments]): + if all([ + self.IS_DEBIT_BALANCE, + amt_earned >= payments + ]): return self.get_amount_earned() - payments - elif all([not self.IS_DEBIT_BALANCE, - amt_earned <= payments]): + elif all([ + not self.IS_DEBIT_BALANCE, + amt_earned <= payments + ]): return payments - self.get_amount_earned() - return 0 + return Decimal.from_float(0.00) - def get_amount_unearned(self): + def get_amount_unearned(self) -> Union[Decimal, float]: + """ + Determines the impact to the EntityModel Accounts Payable based on financial instrument progress. + + Returns + ------- + float or Decimal + Financial instrument amount unearned. + """ if self.accrue: amt_earned = self.get_amount_earned() - if all([self.IS_DEBIT_BALANCE, - amt_earned <= self.amount_paid]): + if all([ + self.IS_DEBIT_BALANCE, + amt_earned <= self.amount_paid + ]): return self.amount_paid - amt_earned - elif all([not self.IS_DEBIT_BALANCE, - amt_earned >= self.amount_paid]): + elif all([ + not self.IS_DEBIT_BALANCE, + amt_earned >= self.amount_paid + ]): return amt_earned - self.amount_paid - return 0 + return Decimal.from_float(0.00) + + def get_amount_open(self) -> Union[Decimal, float]: + """ + Determines the open amount left to be progressed. - def get_amount_open(self): + Returns + ------- + float or Decimal + Financial instrument amount open. + """ if self.accrue: - amount_due = self.amount_due or 0 + amount_due = self.amount_due or 0.00 return amount_due - self.get_amount_earned() - else: - amount_due = self.amount_due or 0 - payments = self.amount_paid or 0 - return amount_due - payments + amount_due = self.amount_due or 0.00 + payments = self.amount_paid or 0.00 + return amount_due - payments - def get_migration_data(self, queryset=None): - raise NotImplementedError('Must implement get_account_balance_data method.') + def get_migration_data(self, queryset: QuerySet = None): + raise NotImplementedError('Must implement get_migration_data method.') def get_migrate_state_desc(self, *args, **kwargs): - """ - Must be implemented. - :return: - """ + raise NotImplementedError('Must implement get_migrate_state_desc method.') def can_migrate(self) -> bool: """ - Function returning if model state can be migrated to related accounts. - :return: + Determines if the Accruable financial instrument can be migrated to the books. + Results in additional Database query if 'ledger' field is not pre-fetch on QuerySet. + + Returns + ------- + bool + True if can migrate, else False. """ if not self.ledger_id: return False @@ -249,14 +405,51 @@ def can_migrate(self) -> bool: def get_tx_type(self, acc_bal_type: dict, adjustment_amount: Decimal): - - if adjustment_amount: - acc_bal_type = acc_bal_type[0] - d_or_i = 'd' if adjustment_amount < 0 else 'i' - return self.TX_TYPE_MAPPING[acc_bal_type + d_or_i] - return 'debit' - - def split_amount(self, amount: float, unit_split: dict, account_uuid, account_balance_type) -> dict: + """ + Determines the transaction type associated with an increase/decrease of an account balance of the financial + instrument. + + Parameters + ---------- + acc_bal_type: + The balance type of the account to be adjusted. + adjustment_amount: Decimal + The adjustment, whether positive or negative. + + Returns + ------- + str + The transaction type of the account adjustment. + """ + acc_bal_type = acc_bal_type[0] + d_or_i = 'd' if adjustment_amount < 0.00 else 'i' + return self.TX_TYPE_MAPPING[acc_bal_type + d_or_i] + + @classmethod + def split_amount(cls, amount: Union[Decimal, float], + unit_split: Dict, + account_uuid: UUID, + account_balance_type: str) -> Dict: + """ + Splits an amount into different proportions representing the unit splits. + Makes sure that 100% of the amount is numerically allocated taking into consideration decimal points. + + Parameters + ---------- + amount: Decimal or float + The amount to be split. + unit_split: dict + A dictionary with information related to each unit split and proportions. + account_uuid: UUID + The AccountModel UUID associated with the splits. + account_balance_type: str + The AccountModel balance type to determine whether to perform a credit or a debit. + + Returns + ------- + dict + A dictionary with the split information. + """ running_alloc = 0 SPLIT_LEN = len(unit_split) - 1 split_results = dict() @@ -271,6 +464,16 @@ def split_amount(self, amount: float, unit_split: dict, account_uuid, account_ba # LOCK/UNLOCK Ledger... def lock_ledger(self, commit: bool = False, raise_exception: bool = True, **kwargs): + """ + Convenience method to lock the LedgerModel associated with the Accruable financial instrument. + + Parameters + ---------- + commit: bool + Commits the transaction in the database. Defaults to False. + raise_exception: bool + If True, raises ValidationError if LedgerModel already locked. + """ ledger_model = self.ledger if ledger_model.locked: if raise_exception: @@ -278,6 +481,16 @@ def lock_ledger(self, commit: bool = False, raise_exception: bool = True, **kwar ledger_model.lock(commit) def unlock_ledger(self, commit: bool = False, raise_exception: bool = True, **kwargs): + """ + Convenience method to un-lock the LedgerModel associated with the Accruable financial instrument. + + Parameters + ---------- + commit: bool + Commits the transaction in the database. Defaults to False. + raise_exception: bool + If True, raises ValidationError if LedgerModel already locked. + """ ledger_model = self.ledger if not ledger_model.locked: if raise_exception: @@ -286,6 +499,16 @@ def unlock_ledger(self, commit: bool = False, raise_exception: bool = True, **kw # POST/UNPOST Ledger... def post_ledger(self, commit: bool = False, raise_exception: bool = True, **kwargs): + """ + Convenience method to post the LedgerModel associated with the Accruable financial instrument. + + Parameters + ---------- + commit: bool + Commits the transaction in the database. Defaults to False. + raise_exception: bool + If True, raises ValidationError if LedgerModel already locked. + """ ledger_model = self.ledger if ledger_model.posted: if raise_exception: @@ -293,6 +516,16 @@ def post_ledger(self, commit: bool = False, raise_exception: bool = True, **kwar ledger_model.post(commit) def unpost_ledger(self, commit: bool = False, raise_exception: bool = True, **kwargs): + """ + Convenience method to un-lock the LedgerModel associated with the Accruable financial instrument. + + Parameters + ---------- + commit: bool + Commits the transaction in the database. Defaults to False. + raise_exception: bool + If True, raises ValidationError if LedgerModel already locked. + """ ledger_model = self.ledger if not ledger_model.posted: if raise_exception: @@ -302,15 +535,45 @@ def unpost_ledger(self, commit: bool = False, raise_exception: bool = True, **kw def migrate_state(self, user_model, entity_slug: str, - itemtxs_qs: QuerySet = None, + itemtxs_qs: Optional[QuerySet] = None, force_migrate: bool = False, commit: bool = True, void: bool = False, - je_date: Union[str, date, datetime] = None, - verify_journal_entries: bool = True, + je_date: Optional[Union[str, date, datetime]] = None, raise_exception: bool = True, **kwargs): + """ + Migrates the current Accruable financial instrument into the books. The main objective of the migrate_state + method is to determine the JournalEntry and TransactionModels necessary to accurately reflect the financial + instrument state in the books. + + Parameters + ---------- + user_model + The Django User Model. + entity_slug: str + The EntityModel slug. + itemtxs_qs: ItemTransactionModelQuerySet + The pre-fetched ItemTransactionModelQuerySet containing the item information associated with the financial + element migration. If provided, will avoid additional database query. + force_migrate: bool + Forces migration of the financial instrument bypassing the can_migrate() check. + commit: bool + If True the migration will be committed in the database. Defaults to True. + void: bool + If True, the migration will perform a VOID actions of the financial instrument. + je_date: date + The JournalEntryModel date to be used for this migration. + raise_exception: bool + Raises ValidationError if migration is not allowed. Defaults to True. + + Returns + ------- + tuple + A tuple of the ItemTransactionModel and the Digest Result from IOMixIn. + """ + if self.can_migrate() or force_migrate: # getting current ledger state @@ -510,38 +773,63 @@ def migrate_state(self, balance_tx_data(tx_data=txs, perform_correction=True) TransactionModel.objects.bulk_create(txs) - if verify_journal_entries: - for _, je in je_list.items(): - # will independently verify and populate appropriate activity for JE. - je.clean(verify=True) - if je.is_verified(): - je.mark_as_posted(commit=False, verify=False, raise_exception=True) - je.mark_as_locked(commit=False, raise_exception=True) - - if all([je.is_verified() for _, je in je_list.items()]): - # only if all JEs have been verified will be posted and locked... - JournalEntryModel.objects.bulk_update( - objs=[je for _, je in je_list.items()], - fields=['posted', 'locked', 'activity'] - ) - - return item_data, digest_data + for _, je in je_list.items(): + # will independently verify and populate appropriate activity for JE. + je.clean(verify=True) + if je.is_verified(): + je.mark_as_posted(commit=False, verify=False, raise_exception=True) + je.mark_as_locked(commit=False, raise_exception=True) + + if all([je.is_verified() for _, je in je_list.items()]): + # only if all JEs have been verified will be posted and locked... + JournalEntryModel.objects.bulk_update( + objs=[je for _, je in je_list.items()], + fields=['posted', 'locked', 'activity'] + ) + + return item_data, txs_digest else: if raise_exception: raise ValidationError(f'{self.REL_NAME_PREFIX.upper()} state migration not allowed') - def void_state(self, commit: bool = False): + def void_state(self, commit: bool = False) -> Dict: + """ + Determines the VOID state of the financial instrument. + + Parameters + ---------- + commit: bool + Commits the new financial instrument state into the model. + + Returns + ------- + dict + A dictionary with new amount_paid, amount_receivable, amount_unearned and amount_earned as keys. + """ void_state = { - 'amount_paid': Decimal.from_float(0.0), - 'amount_receivable': Decimal.from_float(0.0), - 'amount_unearned': Decimal.from_float(0.0), - 'amount_earned': Decimal.from_float(0.0), + 'amount_paid': Decimal.from_float(0.00), + 'amount_receivable': Decimal.from_float(0.00), + 'amount_unearned': Decimal.from_float(0.00), + 'amount_earned': Decimal.from_float(0.00), } if commit: self.update_state(void_state) return void_state def new_state(self, commit: bool = False): + """ + Determines the new state of the financial instrument based on progress. + + Parameters + ---------- + commit: bool + Commits the new financial instrument state into the model. + + Returns + ------- + dict + A dictionary with new amount_paid, amount_receivable, amount_unearned and amount_earned as keys. + """ new_state = { 'amount_paid': self.get_amount_cash(), 'amount_receivable': self.get_amount_prepaid(), @@ -552,7 +840,15 @@ def new_state(self, commit: bool = False): self.update_state(new_state) return new_state - def update_state(self, state: dict = None): + def update_state(self, state: Optional[Dict] = None): + """ + Updates the state on the financial instrument. + + Parameters + ---------- + state: dict + Optional user provided state to use. + """ if not state: state = self.new_state() self.amount_paid = abs(state['amount_paid']) @@ -596,13 +892,13 @@ def clean(self): raise ValidationError(f'Unearned account must be of role {LIABILITY_CL_DEFERRED_REVENUE}.') if self.accrue and self.progress is None: - self.progress = 0 + self.progress = Decimal.from_float(0.00) if self.amount_paid > self.amount_due: raise ValidationError(f'Amount paid {self.amount_paid} cannot exceed amount due {self.amount_due}') if self.is_paid(): - self.progress = Decimal(1.0) + self.progress = Decimal.from_float(1.0) self.amount_paid = self.amount_due today = localdate() @@ -622,7 +918,7 @@ def clean(self): ]): raise ValidationError('Voided element cannot have any balance.') - self.progress = 0 + self.progress = Decimal.from_float(0.00) if self.can_migrate(): self.update_state() @@ -630,7 +926,15 @@ def clean(self): class PaymentTermsMixIn(models.Model): """ - Payment Terms MixIn! + Implements functionality used to track dates relate to various payment terms. + Examples of this include tracking bills and invoices that are due on receipt, 30, 60 or 90 days after they are + approved. + + Attributes + ---------- + terms: str + A choice of TERM_CHOICES that determines the payment terms. + """ TERMS_ON_RECEIPT = 'on_receipt' TERMS_NET_30 = 'net_30' @@ -638,35 +942,103 @@ class PaymentTermsMixIn(models.Model): TERMS_NET_90 = 'net_90' TERMS_NET_90_PLUS = 'net_90+' - TERMS = [ + TERM_CHOICES = [ (TERMS_ON_RECEIPT, 'Due On Receipt'), (TERMS_NET_30, 'Net 30 Days'), (TERMS_NET_60, 'Net 60 Days'), (TERMS_NET_90, 'Net 90 Days'), ] + TERM_DAYS_MAPPING = { + TERMS_ON_RECEIPT: 0, + TERMS_NET_30: 30, + TERMS_NET_60: 60, + TERMS_NET_90: 90, + TERMS_NET_90_PLUS: 120 + } + terms = models.CharField(max_length=10, default='on_receipt', - choices=TERMS, + choices=TERM_CHOICES, verbose_name=_('Terms')) date_due = models.DateField(verbose_name=_('Due Date'), null=True, blank=True) class Meta: abstract = True - def get_terms_start_date(self) -> Optional[date]: + def get_terms_start_date(self) -> date: + """ + Determines the start date for the terms of payment. + + Returns + ------- + date + The date when terms of payment starts. + """ raise NotImplementedError( f'Must implement get_terms_start_date() for {self.__class__.__name__}' ) + def get_terms_net_90_plus(self) -> int: + """ + Determines the number of days for 90+ days terms of payment. + + Returns + ------- + date + The date when terms of payment starts. + """ + return 120 + + def get_terms_timedelta_days(self) -> int: + """ + Determines the number of days from the terms start date. + + Returns + ------- + int + The number of days as integer. + """ + if self.terms == self.TERMS_NET_90_PLUS: + return self.get_terms_net_90_plus() + return self.TERM_DAYS_MAPPING[self.terms] + + def get_terms_timedelta(self) -> timedelta: + """ + Calculates a timedelta relative to the terms start date. + + Returns + ------- + timedelta + Timedelta relative to terms start date. + """ + return timedelta(days=self.get_terms_timedelta_days()) + def due_in_days(self) -> Optional[int]: + """ + Determines how many days until the due date. + + Returns + ------- + int + Days as integer. + """ if self.date_due: td = self.date_due - localdate() if td.days < 0: return 0 return td.days + # todo: is this necessary?... def net_due_group(self): + """ + Determines the group where the financial instrument falls based on the number of days until the due date. + + Returns + ------- + str + The terms group as a string. + """ due_in = self.due_in_days() if due_in == 0: return self.TERMS_ON_RECEIPT @@ -682,15 +1054,19 @@ def clean(self): terms_start_date = self.get_terms_start_date() if terms_start_date: if self.terms != self.TERMS_ON_RECEIPT: - # pylint: disable=no-member - self.date_due = terms_start_date + timedelta(days=int(self.terms.split('_')[-1])) + self.date_due = terms_start_date + self.get_terms_timedelta() else: self.date_due = terms_start_date class MarkdownNotesMixIn(models.Model): """ - MarkDown Notes MixIn! + Implements functionality used to add a Mark-Down notes to a base Django Model. + + Attributes + ---------- + markdown_notes: str + A string of text representing the mark-down document. """ markdown_notes = models.TextField(blank=True, null=True, verbose_name=_('Markdown Notes')) @@ -698,6 +1074,14 @@ class Meta: abstract = True def notes_html(self): + """ + Compiles the markdown_notes field into html. + + Returns + ------- + str + Compiled HTML document as a string. + """ if not self.markdown_notes: return '' return markdown(force_str(self.markdown_notes)) @@ -705,13 +1089,20 @@ def notes_html(self): class BankAccountInfoMixIn(models.Model): """ - MixIn to add universal bank routing information to DjangoLedger Models. - - @account_number: This is the Bank Account number . Only Digits are allowed. - @routing_number: User defined routing number for the concerned bank account. Also called as 'Routing Transit Number (RTN)' - @aba_number: The American Bankers Association Number assigned to each bank. - @account_type: Each account will have to select from the available choices Checking, Savings or Money Market. - @swift_number: SWIFT electronic communications network number of the bank institution. + Implements functionality used to add bank account details to base Django Models. + + Attributes + ---------- + account_number: str + The Bank Account number. Only Digits are allowed. Max 30 digists. + routing_number: str + Routing number for the concerned bank account. Also called as 'Routing Transit Number (RTN)'. Max 30 digists. + aba_number: str + The American Bankers Association Number assigned to each bank. + account_type: str + A choice of ACCOUNT_TYPES. Each account will have to select from the available choices Checking, Savings. + swift_number: str + SWIFT electronic communications network number of the bank institution. """ ACCOUNT_CHECKING = 'checking' @@ -751,6 +1142,15 @@ class Meta: class TaxCollectionMixIn(models.Model): + """ + Implements functionality used to add tax collection rates and or withholding to a base Django Model. + This field may be used to set a pre-defined withholding rate to a financial instrument, customer, vendor, etc. + + Attributes + ---------- + sales_tax_rate: float + The tax rate as a float. A Number between 0.00 and 1.00. + """ sales_tax_rate = models.FloatField(default=0.00000, verbose_name=_('Sales Tax Rate'), null=True, @@ -765,7 +1165,12 @@ class Meta: class LoggingMixIn: + """ + Implements functionality used to add logging capabilities to any python class. + Useful for production and or testing environments. + """ LOGGER_NAME_ATTRIBUTE = None + LOGGER_BYPASS_DEBUG = False def get_logger_name(self): if self.LOGGER_NAME_ATTRIBUTE is None: @@ -776,3 +1181,8 @@ def get_logger_name(self): def get_logger(self) -> logging.Logger: name = self.get_logger_name() return logging.getLogger(name) + + def send_log(self, msg, level, force): + if self.LOGGER_BYPASS_DEBUG or settings.DEBUG or force: + logger = self.get_logger() + logger.log(msg=msg, level=level) diff --git a/django_ledger/models/purchase_order.py b/django_ledger/models/purchase_order.py index e8815356..803dc881 100644 --- a/django_ledger/models/purchase_order.py +++ b/django_ledger/models/purchase_order.py @@ -5,24 +5,37 @@ Contributions to this module: * Miguel Sanda * Pranav P Tulshyan + +A purchase order is a commercial source document that is issued by a business purchasing department when placing an +order with its vendors or suppliers. The document indicates the details on the items that are to be purchased, such as +the types of goods, quantity, and price. In simple terms, it is the contract drafted by the buyer when purchasing goods +from the seller. + +The PurchaseOrderModel is designed to track the status of a Purchase Order and all its items. The PurchaseOrderModel +starts in draft model by default and goes through different states including InReview, Approved, Fulfilled, Canceled and +Void. The PurchaseOrderModel also keeps track of when these states take place. + """ from datetime import date from string import ascii_uppercase, digits -from typing import Tuple, List, Union +from typing import Tuple, List, Union, Optional from uuid import uuid4 from django.core.exceptions import ValidationError, ObjectDoesNotExist from django.core.validators import MinLengthValidator from django.db import models, transaction, IntegrityError -from django.db.models import Q, Sum, Count, QuerySet, F +from django.db.models import Q, Sum, Count, F from django.db.models.functions import Coalesce from django.shortcuts import get_object_or_404 from django.urls import reverse from django.utils.timezone import localdate from django.utils.translation import gettext_lazy as _ -from django_ledger.models import EntityModel, ItemTransactionModel, lazy_loader, BillModel +from django_ledger.models.bill import BillModel, BillModelQuerySet +from django_ledger.models.entity import EntityModel +from django_ledger.models.items import ItemTransactionModel, ItemTransactionModelQuerySet from django_ledger.models.mixins import CreateUpdateMixIn, MarkdownNotesMixIn +from django_ledger.models.utils import lazy_loader from django_ledger.settings import DJANGO_LEDGER_DOCUMENT_NUMBER_PADDING, DJANGO_LEDGER_PO_NUMBER_PREFIX PO_NUMBER_CHARS = ascii_uppercase + digits @@ -33,14 +46,43 @@ class PurchaseOrderModelValidationError(ValidationError): class PurchaseOrderModelQuerySet(models.QuerySet): + """ + A custom defined PurchaseOrderModel QuerySet. + """ def approved(self): + """ + Filters the QuerySet to include Approved PurchaseOrderModels only. + + Returns + ------- + PurchaseOrderModelQuerySet + A PurchaseOrderModelQuerySet with applied filters. + """ return self.filter(po_status__exact=PurchaseOrderModel.PO_STATUS_APPROVED) def fulfilled(self): + """ + Filters the QuerySet to include Fulfilled PurchaseOrderModels only. + + Returns + ------- + PurchaseOrderModelQuerySet + A PurchaseOrderModelQuerySet with applied filters. + """ return self.filter(po_status__exact=PurchaseOrderModel.PO_STATUS_FULFILLED) def active(self): + """ + Filters the QuerySet to include Active PurchaseOrderModels only. + Active PurchaseOrderModels are either approved or fulfilled, which are those that may contain associated + transactions on the Ledger. + + Returns + ------- + PurchaseOrderModelQuerySet + A PurchaseOrderModelQuerySet with applied filters. + """ return self.filter( Q(po_status__exact=PurchaseOrderModel.PO_STATUS_APPROVED) | Q(po_status__exact=PurchaseOrderModel.PO_STATUS_FULFILLED) @@ -48,8 +90,20 @@ def active(self): class PurchaseOrderModelManager(models.Manager): + """ + A custom defined PurchaseOrderModel Manager. + """ + + def for_entity(self, entity_slug, user_model) -> PurchaseOrderModelQuerySet: + """ + Fetches a QuerySet of PurchaseOrderModel associated with a specific EntityModel & UserModel. + May pass an instance of EntityModel or a String representing the EntityModel slug. - def for_entity(self, entity_slug, user_model): + Returns + ------- + PurchaseOrderModelQuerySet + A PurchaseOrderModelQuerySet with applied filters. + """ qs = self.get_queryset() if isinstance(entity_slug, EntityModel): qs = qs.filter(entity=entity_slug) @@ -62,6 +116,53 @@ def for_entity(self, entity_slug, user_model): class PurchaseOrderModelAbstract(CreateUpdateMixIn, MarkdownNotesMixIn): + """ + The base implementation of the PurchaseOrderModel. + + Attributes + ---------- + uuid: UUID + This is a unique primary key generated for the table. The default value of this field is uuid4(). + po_number: str + A unique human-readable and sequential PO Number identifier. Automatically generated. + po_title: str + The PurchaseOrderModel instance title. + po_status: str + One of PO_STATUS values representing the current status of the PurchaseOrderModel instance. + po_amount: Decimal + The total value of the PurchaseOrderModel instance. + po_amount_received: Decimal + The PurchaseOrderModel instance total value received to date. Cannot be greater than PO amount. + entity: EntityModel + The EntityModel associated with the PurchaseOrderModel instance. + date_draft: date + The draft date represents the date when the PurchaseOrderModel was first created. Defaults to + :func:`localdate `. + date_in_review: date + The in review date represents the date when the PurchaseOrderModel was marked as In Review status. + Will be null if PurchaseOrderModel is canceled during draft status. Defaults to + :func:`localdate `. + date_approved: date + The approved date represents the date when the PurchaseOrderModel was approved. Will be null if + PurchaseOrderModel is canceled. + Defaults to :func:`localdate `. + date_fulfilled: date + The paid date represents the date when the PurchaseOrderModel was fulfilled and po_amount_received equals + po_amount. Will be null if PurchaseOrderModel is canceled. + Defaults to :func:`localdate `. + date_void: date + The void date represents the date when the PurchaseOrderModel was void, if applicable. + Will be null unless PurchaseOrderModel is void. + Defaults to :func:`localdate `. + date_canceled: date + The canceled date represents the date when the PurchaseOrderModel was canceled, if applicable. + Will be null unless PurchaseOrderModel is canceled. + Defaults to :func:`localdate `. + po_items: + A foreign key reference to the list of ItemTransactionModel that make the PurchaseOrderModel amount. + ce_model: EstimateModel + A foreign key reference to the EstimateModel associated with the PurchaseOrderModel, if any. + """ PO_STATUS_DRAFT = 'draft' PO_STATUS_REVIEW = 'in_review' PO_STATUS_APPROVED = 'approved' @@ -69,6 +170,7 @@ class PurchaseOrderModelAbstract(CreateUpdateMixIn, MarkdownNotesMixIn): PO_STATUS_VOID = 'void' PO_STATUS_CANCELED = 'canceled' + """The different valid PO Status and their representation in the Database""" PO_STATUS = [ (PO_STATUS_DRAFT, _('Draft')), (PO_STATUS_REVIEW, _('In Review')), @@ -141,115 +243,329 @@ def __str__(self): return f'PO Model: {self.po_number} | {self.get_po_status_display()}' # Configuration... + + def is_configured(self) -> bool: + return all([ + self.entity_id is not None, + self.date_draft, + self.po_status + ]) + def configure(self, entity_slug: str or EntityModel, user_model, draft_date: date = None, commit: bool = False): + """ + A configuration hook which executes all initial PurchaseOrderModel setup on to the EntityModel and all initial + values of the EntityModel. Can only call this method once in the lifetime of a PurchaseOrderModel. - if isinstance(entity_slug, str): - entity_qs = EntityModel.objects.for_user( - user_model=user_model) - entity_model: EntityModel = get_object_or_404(entity_qs, slug__exact=entity_slug) - elif isinstance(entity_slug, EntityModel): - entity_model = entity_slug - else: - raise PurchaseOrderModelValidationError('entity_slug must be an instance of str or EntityModel') - - if draft_date: - self.date_draft = draft_date - if not self.date_draft: - self.date_draft = localdate() - self.po_status = PurchaseOrderModel.PO_STATUS_DRAFT - self.entity = entity_model - self.clean() - if commit: - self.save() + Parameters + __________ + + entity_slug: str or EntityModel + The entity slug or EntityModel to associate the Bill with. + + user_model: + The UserModel making the request to check for QuerySet permissions. + + ledger_posted: + An option to mark the BillModel Ledger as posted at the time of configuration. Defaults to False. + + bill_desc: str + An optional description appended to the LedgerModel name. + + commit: bool + Saves the current BillModel after being configured. + + Returns + ------- + PurchaseOrderModel + The configured PurchaseOrderModel instance. + """ + if not self.is_configured(): + if isinstance(entity_slug, str): + entity_qs = EntityModel.objects.for_user( + user_model=user_model) + entity_model: EntityModel = get_object_or_404(entity_qs, slug__exact=entity_slug) + elif isinstance(entity_slug, EntityModel): + entity_model = entity_slug + else: + raise PurchaseOrderModelValidationError('entity_slug must be an instance of str or EntityModel') + + if draft_date: + self.date_draft = draft_date + if not self.date_draft: + self.date_draft = localdate() + self.po_status = PurchaseOrderModel.PO_STATUS_DRAFT + self.entity = entity_model + self.clean() + if commit: + self.save() return self + def validate_item_transaction_qs(self, queryset: Union[ItemTransactionModelQuerySet, List[ItemTransactionModel]]): + """ + Validates that the entire ItemTransactionModelQuerySet is bound to the PurchaseOrderModel. + + Parameters + ---------- + queryset: ItemTransactionModelQuerySet or list of ItemTransactionModel. + ItemTransactionModelQuerySet to validate. + """ + valid = all([ + i.po_model_id == self.uuid for i in queryset + ]) + if not valid: + raise PurchaseOrderModelValidationError(f'Invalid queryset. All items must be assigned to PO {self.uuid}') + # State Update... - def get_itemtxs_data(self, queryset: QuerySet = None) -> Tuple: + def get_itemtxs_data(self, + queryset: Optional[Union[ItemTransactionModelQuerySet, List[ItemTransactionModel]]] = None, + aggregate_on_db: bool = False) -> Tuple: + """ + Fetches the PurchaseOrderModel Items and aggregates the QuerySet. + + Parameters + ---------- + queryset: ItemTransactionModelQuerySet + Optional pre-fetched ItemModelQueryset to use. Avoids additional DB query if provided. + Validated if provided. + aggregate_on_db: bool + If True, performs aggregation of ItemsTransactions in the DB resulting in one additional DB query. + + Returns + ------- + A tuple: ItemTransactionModelQuerySet, dict + """ if not queryset: - # pylint: disable=no-member queryset = self.itemtransactionmodel_set.all().select_related('bill_model', 'item_model') - - return queryset, queryset.aggregate( - po_total_amount__sum=Coalesce(Sum('po_total_amount'), 0.0, output_field=models.FloatField()), - bill_amount_paid__sum=Coalesce(Sum('bill_model__amount_paid'), 0.0, output_field=models.FloatField()), - total_items=Count('uuid') - ) + else: + self.validate_item_transaction_qs(queryset) + + if aggregate_on_db and isinstance(queryset, ItemTransactionModelQuerySet): + return queryset, queryset.aggregate( + po_total_amount__sum=Coalesce(Sum('po_total_amount'), 0.0, output_field=models.FloatField()), + bill_amount_paid__sum=Coalesce(Sum('bill_model__amount_paid'), 0.0, output_field=models.FloatField()), + total_items=Count('uuid') + ) + return queryset, { + 'po_total_amount__sum': sum(i.total_amount for i in queryset), + 'bill_amount_paid__sum': sum(i.bill_model.amount_paid for i in queryset if i.bill_model_id), + 'total_items': len(queryset) + } def update_state(self, - itemtxs_qs: QuerySet = None, - itemtxs_list: List[ItemTransactionModel] = None) -> Union[Tuple, None]: - if itemtxs_qs and itemtxs_list: - raise PurchaseOrderModelValidationError('Either queryset or list can be used.') - - if itemtxs_list: - self.po_amount = round(sum(a.po_total_amount for a in itemtxs_list if not a.is_canceled()), 2) - self.po_amount_received = round(sum(a.po_total_amount for a in itemtxs_list if a.is_received()), 2) - else: - itemtxs_qs, itemtxs_agg = self.get_itemtxs_data(queryset=itemtxs_qs) + itemtxs_qs: Optional[Union[ItemTransactionModelQuerySet, List[ItemTransactionModel]]] = None + ) -> Tuple: + + """ + Updates the state of the PurchaseOrderModel. + + Parameters + ---------- + itemtxs_qs: ItemTransactionModelQuerySet or list of ItemTransactionModel + + Returns + ------- + tuple + A tuple of ItemTransactionModels and Aggregation + """ + itemtxs_qs, itemtxs_agg = self.get_itemtxs_data(queryset=itemtxs_qs) + + if isinstance(itemtxs_qs, list): + self.po_amount = round(sum(a.po_total_amount for a in itemtxs_qs if not a.is_canceled()), 2) + self.po_amount_received = round(sum(a.po_total_amount for a in itemtxs_qs if a.is_received()), 2) + elif isinstance(itemtxs_qs, ItemTransactionModelQuerySet): total_po_amount = round(sum(i.po_total_amount for i in itemtxs_qs if not i.is_canceled()), 2) total_received = round(sum(i.po_total_amount for i in itemtxs_qs if i.is_received()), 2) self.po_amount = total_po_amount self.po_amount_received = total_received - return itemtxs_qs, itemtxs_agg + + return itemtxs_qs, itemtxs_agg # State... def is_draft(self) -> bool: + """ + Checks if the PurchaseOrderModel is in Draft status. + + Returns + ------- + bool + True if PurchaseOrderModel is Draft, else False. + """ return self.po_status == self.PO_STATUS_DRAFT def is_review(self) -> bool: + """ + Checks if the PurchaseOrderModel is in Review status. + + Returns + ------- + bool + True if PurchaseOrderModel is Review, else False. + """ return self.po_status == self.PO_STATUS_REVIEW def is_approved(self) -> bool: + """ + Checks if the PurchaseOrderModel is in Approved status. + + Returns + ------- + bool + True if PurchaseOrderModel is Approved, else False. + """ return self.po_status == self.PO_STATUS_APPROVED def is_fulfilled(self) -> bool: + """ + Checks if the PurchaseOrderModel is in Fulfilled status. + + Returns + ------- + bool + True if PurchaseOrderModel is in Fulfilled status, else False. + """ return self.po_status == self.PO_STATUS_FULFILLED def is_canceled(self) -> bool: + """ + Checks if the PurchaseOrderModel is in Canceled status. + + Returns + ------- + bool + True if PurchaseOrderModel is in Canceled, else False. + """ return self.po_status == self.PO_STATUS_CANCELED def is_void(self) -> bool: + """ + Checks if the PurchaseOrderModel is in Void status. + + Returns + ------- + bool + True if PurchaseOrderModel is Void, else False. + """ return self.po_status == self.PO_STATUS_VOID # Permissions... def can_draft(self) -> bool: + """ + Checks if the PurchaseOrderModel can be marked as Draft. + + Returns + ------- + bool + True if PurchaseOrderModel can be marked as Draft, else False. + """ return self.is_review() def can_review(self) -> bool: + """ + Checks if the PurchaseOrderModel can be marked as In Review. + + Returns + ------- + bool + True if PurchaseOrderModel can be marked as In Review, else False. + """ return self.is_draft() def can_approve(self) -> bool: + """ + Checks if the PurchaseOrderModel can be marked as Approved. + + Returns + ------- + bool + True if PurchaseOrderModel can be marked as Approved, else False. + """ return self.is_review() def can_fulfill(self) -> bool: + """ + Checks if the PurchaseOrderModel can be marked as Fulfilled. + + Returns + ------- + bool + True if PurchaseOrderModel can be marked as Fulfilled, else False. + """ return self.is_approved() def can_cancel(self) -> bool: + """ + Checks if the PurchaseOrderModel can be marked as Canceled. + + Returns + ------- + bool + True if PurchaseOrderModel can be marked as Canceled, else False. + """ return any([ self.is_draft(), self.is_review() ]) def can_void(self) -> bool: + """ + Checks if the PurchaseOrderModel can be marked as Void. + + Returns + ------- + bool + True if PurchaseOrderModel can be marked as Void, else False. + """ return self.is_approved() def can_delete(self) -> bool: + """ + Checks if the PurchaseOrderModel can be deleted. + + Returns + ------- + bool + True if PurchaseOrderModel can be deleted, else False. + """ return any([ self.is_draft(), self.is_review() ]) def can_edit_items(self) -> bool: + """ + Checks if the PurchaseOrderModel items can be edited. + + Returns + ------- + bool + True if PurchaseOrderModel items can be edited, else False. + """ return self.is_draft() def is_contract_bound(self): + """ + Checks if the PurchaseOrderModel is bound to an EstimateModel. + + Returns + ------- + bool + True if PurchaseOrderModel is bound to an EstimateModel, else False. + """ return self.ce_model_id is not None def can_bind_estimate(self, estimate_model, raise_exception: bool = False) -> bool: + """ + Checks if the PurchaseOrderModel ican be bound to an EstimateModel. + + Returns + ------- + bool + True if PurchaseOrderModel can be bound to an EstimateModel, else False. + """ if self.is_contract_bound(): if raise_exception: raise PurchaseOrderModelValidationError( @@ -263,14 +579,17 @@ def can_bind_estimate(self, estimate_model, raise_exception: bool = False) -> bo is_approved ]) - def can_generate_po_number(self): - return all([ - self.date_draft, - not self.po_number - ]) - - # Actions... def action_bind_estimate(self, estimate_model, commit: bool = False): + """ + Binds a specific EstimateModel to the PurchaseOrderModel instance. + + Parameters + ---------- + estimate_model: EstimateModel + The EstimateModel to bind. + commit: bool + Commits the changes in the Database, if True. Defaults to False. + """ try: self.can_bind_estimate(estimate_model, raise_exception=True) except ValueError as e: @@ -283,12 +602,39 @@ def action_bind_estimate(self, estimate_model, commit: bool = False): 'updated' ]) + def can_generate_po_number(self): + """ + Checks if PurchaseOrderModel can generate its Document Number. + + Returns + ------- + bool + True if PurchaseOrderModel can generate its po_number, else False. + """ + return all([ + self.date_draft, + not self.po_number + ]) + + # Actions... + # DRAFT... - def mark_as_draft(self, commit: bool = False, **kwargs): + def mark_as_draft(self, date_draft: Optional[date] = None, commit: bool = False, **kwargs): + """ + Marks PurchaseOrderModel as Draft. + + Parameters + ---------- + date_draft: date + Draft date. If None, defaults to localdate(). + commit: bool + Commits transaction into the Database. Defaults to False. + """ if not self.can_draft(): raise PurchaseOrderModelValidationError( message=f'Purchase Order {self.po_number} cannot be marked as draft.') self.po_status = self.PO_STATUS_DRAFT + self.date_draft = localdate() if not date_draft else date_draft self.clean() if commit: self.save(update_fields=[ @@ -297,9 +643,25 @@ def mark_as_draft(self, commit: bool = False, **kwargs): ]) def get_mark_as_draft_html_id(self): + """ + PurchaseOrderModel Mark as Draft HTML ID Tag. + + Returns + ------- + str + HTML ID as a String. + """ return f'djl-{self.uuid}-po-mark-as-draft' def get_mark_as_draft_url(self): + """ + PurchaseOrderModel Mark as Draft URL ID Tag. + + Returns + ------- + str + HTML ID as a String. + """ return reverse('django_ledger:po-action-mark-as-draft', kwargs={ 'entity_slug': self.entity.slug, @@ -307,20 +669,39 @@ def get_mark_as_draft_url(self): }) def get_mark_as_draft_message(self): + """ + PurchaseOrderModel Mark as Draft Message. + + Returns + ------- + str + Message as a String. + """ return _('Do you want to mark Purchase Order %s as Draft?') % self.po_number # REVIEW... - def mark_as_review(self, date_review: date = None, commit: bool = False, **kwargs): + def mark_as_review(self, date_in_review: Optional[date] = None, commit: bool = False, **kwargs): + """ + Marks PurchaseOrderModel as In Review. + + Parameters + ---------- + date_in_review: date + Draft date. If None, defaults to localdate(). + commit: bool + Commits transaction into the Database. Defaults to False. + """ if not self.can_review(): raise PurchaseOrderModelValidationError( message=f'Purchase Order {self.po_number} cannot be marked as in review.') - itemthrough_qs = self.itemtransactionmodel_set.all() - if not itemthrough_qs.count(): + + itemtxs_qs, itemtxs_agg = self.get_itemtxs_data() + if not itemtxs_qs.count(): raise PurchaseOrderModelValidationError(message='Cannot review a PO without items...') if not self.po_amount: raise PurchaseOrderModelValidationError(message='PO amount is zero.') - self.date_in_review = localdate() if not date_review else date_review + self.date_in_review = localdate() if not date_in_review else date_in_review self.po_status = self.PO_STATUS_REVIEW self.clean() if commit: @@ -331,9 +712,25 @@ def mark_as_review(self, date_review: date = None, commit: bool = False, **kwarg ]) def get_mark_as_review_html_id(self): + """ + PurchaseOrderModel Mark as In Review HTML ID Tag. + + Returns + ------- + str + HTML ID as a String. + """ return f'djl-{self.uuid}-po-mark-as-review' def get_mark_as_review_url(self): + """ + PurchaseOrderModel Mark as In Review URL ID Tag. + + Returns + ------- + str + HTML ID as a String. + """ return reverse('django_ledger:po-action-mark-as-review', kwargs={ 'entity_slug': self.entity.slug, @@ -341,10 +738,28 @@ def get_mark_as_review_url(self): }) def get_mark_as_review_message(self): + """ + PurchaseOrderModel Mark as Review Message. + + Returns + ------- + str + Message as a String. + """ return _('Do you want to mark Purchase Order %s as In Review?') % self.po_number # APPROVED... - def mark_as_approved(self, commit: bool = False, date_approved: date = None, **kwargs): + def mark_as_approved(self, date_approved: Optional[date] = None, commit: bool = False, **kwargs): + """ + Marks PurchaseOrderModel as Approved. + + Parameters + ---------- + date_approved: date + Approved date. If None, defaults to localdate(). + commit: bool + Commits transaction into the Database. Defaults to False. + """ if not self.can_approve(): raise PurchaseOrderModelValidationError( message=f'Purchase Order {self.po_number} cannot be marked as approved.') @@ -360,9 +775,25 @@ def mark_as_approved(self, commit: bool = False, date_approved: date = None, **k ]) def get_mark_as_approved_html_id(self): + """ + PurchaseOrderModel Mark as Approved HTML ID Tag. + + Returns + ------- + str + HTML ID as a String. + """ return f'djl-{self.uuid}-po-mark-as-approved' def get_mark_as_approved_url(self): + """ + PurchaseOrderModel Mark as Approved URL ID Tag. + + Returns + ------- + str + HTML ID as a String. + """ return reverse('django_ledger:po-action-mark-as-approved', kwargs={ 'entity_slug': self.entity.slug, @@ -370,10 +801,28 @@ def get_mark_as_approved_url(self): }) def get_mark_as_approved_message(self): + """ + PurchaseOrderModel Mark as Approved Message. + + Returns + ------- + str + Message as a String. + """ return _('Do you want to mark Purchase Order %s as Approved?') % self.po_number # CANCEL... - def mark_as_canceled(self, commit: bool = False, date_canceled: date = None, **kwargs): + def mark_as_canceled(self, date_canceled: Optional[date] = None, commit: bool = False, **kwargs): + """ + Marks PurchaseOrderModel as Canceled. + + Parameters + ---------- + date_canceled: date + Canceled date. If None, defaults to localdate(). + commit: bool + Commits transaction into the Database. Defaults to False. + """ if not self.can_cancel(): raise PurchaseOrderModelValidationError( message=f'Purchase Order {self.po_number} cannot be marked as canceled.') @@ -388,9 +837,25 @@ def mark_as_canceled(self, commit: bool = False, date_canceled: date = None, **k ]) def get_mark_as_canceled_html_id(self): + """ + PurchaseOrderModel Mark as Canceled HTML ID Tag. + + Returns + ------- + str + HTML ID as a String. + """ return f'djl-{self.uuid}-po-mark-as-canceled' def get_mark_as_canceled_url(self): + """ + PurchaseOrderModel Mark as Canceled URL ID Tag. + + Returns + ------- + str + HTML ID as a String. + """ return reverse('django_ledger:po-action-mark-as-canceled', kwargs={ 'entity_slug': self.entity.slug, @@ -398,22 +863,45 @@ def get_mark_as_canceled_url(self): }) def get_mark_as_canceled_message(self): + """ + PurchaseOrderModel Mark as Canceled Message. + + Returns + ------- + str + Message as a String. + """ return _('Do you want to mark Purchase Order %s as Canceled?') % self.po_number # FULFILL... def mark_as_fulfilled(self, date_fulfilled: date = None, - po_items: Union[QuerySet, List[ItemTransactionModel]] = None, - commit=False, + po_items: Union[ItemTransactionModelQuerySet, List[ItemTransactionModel]] = None, + commit: bool = False, **kwargs): + """ + Marks PurchaseOrderModel as Fulfilled. + + Parameters + ---------- + date_fulfilled: date + Fulfilled date. If None, defaults to localdate(). + po_items: ItemTransactionModelQuerySet or list of ItemTransactionModel. + Pre-fetched ItemTransactionModelQuerySet or list of ItemTransactionModel. + Validated if provided. + commit: bool + Commits transaction into the Database. Defaults to False. + """ + if not self.can_fulfill(): raise PurchaseOrderModelValidationError( message=f'Purchase Order {self.po_number} cannot be marked as fulfilled.') - self.date_fulfilled = localdate() if not date_fulfilled else date_fulfilled - self.po_amount_received = self.po_amount if not po_items: - po_items = self.itemtransactionmodel_set.all().select_related('bill_model') + po_items, po_items_agg = self.get_itemtxs_data(queryset=po_items) + + self.date_fulfilled = localdate() if not date_fulfilled else date_fulfilled + self.po_amount_received = self.po_amount bill_models = [i.bill_model for i in po_items] all_items_billed = all(bill_models) @@ -433,6 +921,7 @@ def mark_as_fulfilled(self, self.clean() if commit: + # todo: what if PO items is list???... po_items.update(po_item_status=ItemTransactionModel.STATUS_RECEIVED) self.save(update_fields=[ 'date_fulfilled', @@ -441,9 +930,25 @@ def mark_as_fulfilled(self, ]) def get_mark_as_fulfilled_html_id(self): + """ + PurchaseOrderModel Mark as Fulfilled HTML ID Tag. + + Returns + ------- + str + HTML ID as a String. + """ return f'djl-{self.uuid}-po-mark-as-fulfilled' def get_mark_as_fulfilled_url(self): + """ + PurchaseOrderModel Mark as Fulfilled URL ID Tag. + + Returns + ------- + str + URL as a String. + """ return reverse('django_ledger:po-action-mark-as-fulfilled', kwargs={ 'entity_slug': self.entity.slug, @@ -451,24 +956,37 @@ def get_mark_as_fulfilled_url(self): }) def get_mark_as_fulfilled_message(self): + """ + PurchaseOrderModel Mark as Fulfilled Message. + + Returns + ------- + str + Message as a String. + """ return _('Do you want to mark Purchase Order %s as Fulfilled?') % self.po_number # VOID... def mark_as_void(self, - entity_slug: str, - user_model, - void_date: date = None, - commit=False, + void_date: Optional[date] = None, + commit: bool = False, **kwargs): + """ + Marks PurchaseOrderModel as Fulfilled. + + Parameters + ---------- + void_date: date + Void date. If None, defaults to localdate(). + commit: bool + Commits transaction into the Database. Defaults to False. + """ if not self.can_void(): raise PurchaseOrderModelValidationError( message=f'Purchase Order {self.po_number} cannot be marked as void.') # all bills associated with this PO... - bill_model_qs = self.get_po_bill_queryset( - entity_slug=entity_slug, - user_model=user_model - ) + bill_model_qs = self.get_po_bill_queryset() bill_model_qs = bill_model_qs.only('bill_status') if not all(b.is_void() for b in bill_model_qs): @@ -486,9 +1004,25 @@ def mark_as_void(self, ]) def get_mark_as_void_html_id(self): + """ + PurchaseOrderModel Mark as Void HTML ID Tag. + + Returns + ------- + str + HTML ID as a String. + """ return f'djl-{self.uuid}-po-mark-as-void' def get_mark_as_void_url(self): + """ + PurchaseOrderModel Mark as Void URL ID Tag. + + Returns + ------- + str + HTML ID as a String. + """ return reverse('django_ledger:po-action-mark-as-void', kwargs={ 'entity_slug': self.entity.slug, @@ -496,22 +1030,51 @@ def get_mark_as_void_url(self): }) def get_mark_as_void_message(self): + """ + PurchaseOrderModel Mark as Void Message. + + Returns + ------- + str + Message as a String. + """ return _('Do you want to mark Purchase Order %s as Void?') % self.po_number - # Conevience Methods... + def get_po_bill_queryset(self) -> BillModelQuerySet: + """ + Fetches a BillModelQuerySet of all BillModels associated with the PurchaseOrderModel instance. - def get_po_bill_queryset(self, user_model, entity_slug): - return BillModel.objects.for_entity( - user_model=user_model, - entity_slug=entity_slug - ).filter(bill_items__purchaseordermodel__uuid__exact=self.uuid) + Returns + ------- + BillModelQuerySet + """ + return BillModel.objects.filter(bill_items__purchaseordermodel__uuid__exact=self.uuid) def get_status_action_date(self): - if self.is_fulfilled(): - return self.date_fulfilled + """ + Current status action date. + + Returns + ------- + date + A date. i.e. If status is Approved, return date_approved. If In Review, return date_in_review. + """ return getattr(self, f'date_{self.po_status}') def _get_next_state_model(self, raise_exception: bool = True): + """ + Fetches the next sequenced state model associated with the PurchaseOrderModel number. + + Parameters + ---------- + raise_exception: bool + Raises IntegrityError if unable to secure transaction from DB. + + Returns + ------- + EntityStateModel + An instance of EntityStateModel + """ EntityStateModel = lazy_loader.get_entity_state_model() EntityModel = lazy_loader.get_entity_model() entity_model = EntityModel.objects.get(uuid__exact=self.entity_id) @@ -552,8 +1115,16 @@ def _get_next_state_model(self, raise_exception: bool = True): def generate_po_number(self, commit: bool = False) -> str: """ Atomic Transaction. Generates the next PurchaseOrder document number available. - @param commit: Commit transaction into InvoiceModel. - @return: A String, representing the current InvoiceModel instance Document Number. + + Parameters + ---------- + commit: bool + Commits transaction into PurchaseOrderModel. + + Returns + ------- + str + A String, representing the generated or current PurchaseOrderModel instance Document Number. """ if self.can_generate_po_number(): with transaction.atomic(durable=True): diff --git a/django_ledger/models/transactions.py b/django_ledger/models/transactions.py index 46b348da..728a39dc 100644 --- a/django_ledger/models/transactions.py +++ b/django_ledger/models/transactions.py @@ -4,6 +4,16 @@ Contributions to this module: * Miguel Sanda + +The TransactionModel is the lowest accounting level where the financial information is recorded on the books. +Every transaction which has an financial implication must be recorded as part of a JournalEntryModel, which in turn +encapsulates a collection of TransactionModels. Transaction models cannot exist without being part of a validated +JournalEntryModel. Orphan TransactionModels are not allowed, and this is enforced by the database. + +A transaction by definition must perform a CREDIT or a DEBIT to the underlying AccountModel. The IOMixIn plays a crucial +role in the production of financial statements and sets its foundation in the TransactionModel API to effective query +amd aggregate transactions at the Database layer without the need of pulling all TransactionModels into memory for the +production of financial statements. """ from datetime import datetime, date from typing import List, Union @@ -24,18 +34,6 @@ from django_ledger.models.mixins import CreateUpdateMixIn from django_ledger.models.unit import EntityUnitModel -""" -The TransactionModel is the lowest accounting level where the financial information is recorded on the books. -Every transaction which has an financial implication must be recorded as part of a JournalEntryModel, which in turn -encapsulates a collection of TransactionModels. Transaction models cannot exist without being part of a validated -JournalEntryModel. Orphan TransactionModels are not allowed, and this is enforced by the database. - -A transaction by definition must perform a CREDIT or a DEBIT to the underlying AccountModel. The IOMixIn plays a crucial -role in the production of financial statements and sets its foundation in the TransactionModel API to effective query -amd aggregate transactions at the Database layer without the need of pulling all TransactionModels into memory for the -production of financial statements. -""" - class TransactionModelValidationError(ValidationError): pass @@ -43,7 +41,7 @@ class TransactionModelValidationError(ValidationError): class TransactionModelQuerySet(QuerySet): """ - A custom defined QuerySet for the TransactionModel. + A custom defined EntityUnitModel Queryset. """ def posted(self) -> QuerySet: diff --git a/django_ledger/models/utils.py b/django_ledger/models/utils.py index 3d667796..9a41f1d1 100644 --- a/django_ledger/models/utils.py +++ b/django_ledger/models/utils.py @@ -29,6 +29,7 @@ class LazyLoader: TRANSACTION_MODEL = None ENTITY_UNIT_MODEL = None PURCHASE_ORDER_MODEL = None + ESTIMATE_MODEL = None def get_entity_model(self): if not self.ENTITY_MODEL: @@ -138,5 +139,11 @@ def get_entity_unit_model(self): self.ENTITY_UNIT_MODEL = EntityUnitModel return self.ENTITY_UNIT_MODEL + def get_estimate_model(self): + if not self.ESTIMATE_MODEL: + from django_ledger.models import EstimateModel + self.ESTIMATE_MODEL = EstimateModel + return self.ESTIMATE_MODEL + lazy_loader = LazyLoader() diff --git a/django_ledger/settings.py b/django_ledger/settings.py index 8fddbe6e..95d9a2f3 100644 --- a/django_ledger/settings.py +++ b/django_ledger/settings.py @@ -66,6 +66,8 @@ 'DJANGO_LEDGER_INVOICE_MODEL_ABSTRACT_CLASS', 'django_ledger.models.invoice.InvoiceModelAbstract') +DJANGO_LEDGER_DEFAULT_COA = getattr(settings, 'DJANGO_LEDGER_DEFAULT_COA', None) + DJANGO_LEDGER_FINANCIAL_ANALYSIS = { 'ratios': { 'current_ratio': { diff --git a/docs/source/assets/img/ModelDependency.png b/docs/source/assets/img/ModelDependency.png index 3e565af3..88d97306 100644 Binary files a/docs/source/assets/img/ModelDependency.png and b/docs/source/assets/img/ModelDependency.png differ diff --git a/docs/source/assets/img/ModelDependencyDetail.png b/docs/source/assets/img/ModelDependencyDetail.png index 382d8493..90934c3c 100644 Binary files a/docs/source/assets/img/ModelDependencyDetail.png and b/docs/source/assets/img/ModelDependencyDetail.png differ diff --git a/docs/source/models.rst b/docs/source/models.rst index 568d4483..8f8d7f25 100644 --- a/docs/source/models.rst +++ b/docs/source/models.rst @@ -5,7 +5,6 @@ Model Dependency Diagram ------------------------- .. image:: ./assets/img/ModelDependency.png - Database Fields --------------- .. image:: ./assets/img/ModelDependencyDetail.png @@ -25,6 +24,16 @@ Account Model .. automodule:: django_ledger.models.accounts :members: +Ledger Model +------------ +.. automodule:: django_ledger.models.ledger + :members: + +Transaction Model +----------------- +.. automodule:: django_ledger.models.ledger + :members: + Journal Entry Model ------------------- .. automodule:: django_ledger.models.journal_entry @@ -40,33 +49,48 @@ Chart of Accounts Model .. automodule:: django_ledger.models.coa :members: +Default Chart of Accounts +------------------------- +.. automodule:: django_ledger.models.coa_default + :members: + +Item Model +----------- +.. automodule:: django_ledger.models.items + :members: + Bill Model ------------------ +---------- .. automodule:: django_ledger.models.bill :members: Estimate Model ------------------ +-------------- .. automodule:: django_ledger.models.estimate :members: +Purchase Order Model +-------------------- +.. automodule:: django_ledger.models.purchase_order + :members: + Invoice Model ------------------ +------------- .. automodule:: django_ledger.models.invoice :members: Customer Model ------------------ +-------------- .. automodule:: django_ledger.models.customer :members: Vendor Model ------------------ +------------ .. automodule:: django_ledger.models.vendor :members: MixIns ------------------ +------ .. automodule:: django_ledger.models.mixins :members: diff --git a/pyproject.toml b/pyproject.toml index f58c22ac..2aceab16 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "django-ledger" -version = "0.5.2.17" +version = "0.5.2.18" readme = "README.md" requires-python = ">=3.7" description = "Bookkeeping & Financial analysis backend for Django. Balance Sheet, Income Statements, Chart of Accounts, Entities" @@ -12,18 +12,18 @@ maintainers = [ { name = "Miguel Sanda", email = "msanda@arrobalytics.com" } ] dependencies = [ - "asgiref==3.5.2; python_version >= '3.7'", - "django==4.1.3", - "django-treebeard==4.5.1", - "faker==15.3.3", - "markdown==3.4.1", - "ofxtools==0.9.5", - "pillow==9.3.0", - "python-dateutil==2.8.2; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "six==1.16.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "sqlparse==0.4.3; python_version >= '3.5'", - "text-unidecode==1.3", - "tzdata==2022.2; sys_platform == 'win32'", + "asgiref>=3.5.2; python_version >= '3.7'", + "django>=2.2", + "django-treebeard>=4.5.1", + "faker>=15.3.3", + "markdown>=3.4.1", + "ofxtools>=0.9.5", + "pillow>=9.3.0", + "python-dateutil>=2.8.2; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "six>=1.16.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "sqlparse>=0.4.3; python_version >= '3.5'", + "text-unidecode>=1.3", + "tzdata>=2022.2; sys_platform == 'win32'", ] classifiers = [ "Programming Language :: Python :: 3", diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 2fd57eca..00000000 --- a/requirements.txt +++ /dev/null @@ -1,11 +0,0 @@ --i https://pypi.org/simple -asgiref==3.6.0 ; python_version >= '3.7' -django==4.1.4 -django-treebeard==4.5.1 -faker==15.3.4 -markdown==3.4.1 -ofxtools==0.9.5 -pillow==9.3.0 -python-dateutil==2.8.2 ; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' -six==1.16.0 ; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' -sqlparse==0.4.3 ; python_version >= '3.5'