From 7b7bdb8a999c7961570d3239552700fe8c05a9c0 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Wed, 7 May 2025 09:34:50 +0000 Subject: [PATCH 01/11] chore(release): Bumped to Version 1.0.0 # 1.0.0 (2025-05-07) ### Bug Fixes * 401 from parallel logins ([c5136fd](https://github.com/aerele/ecommerce_integrations/commit/c5136fd4494a300adb064acbcab5ce9864a1aa04)) * add channel id field on invoices ([54bb519](https://github.com/aerele/ecommerce_integrations/commit/54bb51920d2278e224495a5535e95b833a260d1a)) * add defaults if mfg/expirty dates are missing ([ceb72e6](https://github.com/aerele/ecommerce_integrations/commit/ceb72e637fc8c41a5f94755302ff8fddd8d4bba5)) * add filters for `Warehouse` and `Account Group` ([b7b9f4d](https://github.com/aerele/ecommerce_integrations/commit/b7b9f4da3c62e14cb8ba6783f9a215b8b694abe6)) * add filters on various field ([092be51](https://github.com/aerele/ecommerce_integrations/commit/092be51a4cdb5963ca42fdff0a5cd7341e2bb9ad)) * add js to warn about use of old settings ([30178be](https://github.com/aerele/ecommerce_integrations/commit/30178be2aa49aa5927782fa24bf42e0617ae2fb5)) * add memberships to items ([fbc10e2](https://github.com/aerele/ecommerce_integrations/commit/fbc10e2e42c55f5611ca94d845ca9e601e340655)) * add param self to remaining methods ([42eee93](https://github.com/aerele/ecommerce_integrations/commit/42eee935eda8b318471932dbbc86adb992bc4a65)) * add proper filters for warehouse map ([302b5d8](https://github.com/aerele/ecommerce_integrations/commit/302b5d855add7b8f6980db71eabfc4437273432d)) * add template name in variant item name ([#170](https://github.com/aerele/ecommerce_integrations/issues/170)) ([339cfe3](https://github.com/aerele/ecommerce_integrations/commit/339cfe385fcf33437e6a40edf262164781c9a75e)) * add validation for `Amazon Fields Map` ([bab7702](https://github.com/aerele/ecommerce_integrations/commit/bab7702a1aaf70f48cf024d70fcf999798e23d4d)) * added points as payments ([b998332](https://github.com/aerele/ecommerce_integrations/commit/b99833271b9e6d95bc809e97c8e5ccf547227a5f)) * added therapist to the list of employees ([0a75dc2](https://github.com/aerele/ecommerce_integrations/commit/0a75dc26c8f058a587c07800794ef393a6b78808)) * allow updating is_stock_item after creaetion ([433a3d6](https://github.com/aerele/ecommerce_integrations/commit/433a3d6de373a78b5bc1055f2f48c2915256547d)) * apply taxes at actuals ([#36](https://github.com/aerele/ecommerce_integrations/issues/36)) ([624e144](https://github.com/aerele/ecommerce_integrations/commit/624e1441be0038e1fef186b78c541ab644d8ea96)) * assign attribute instead of item ([4c017b0](https://github.com/aerele/ecommerce_integrations/commit/4c017b0af4ccd354353029f884b5d900cab0bcde)) * attach documents before submitting ([2e1e2ee](https://github.com/aerele/ecommerce_integrations/commit/2e1e2ee28c872957d36cc81fe3553a52d4d965b8)) * AttributeSets error ([389080e](https://github.com/aerele/ecommerce_integrations/commit/389080ecef1e392b73c8da9a9f7974442b2a9615)) * avoid 0 values payment entry and mark items as sales items by default ([#163](https://github.com/aerele/ecommerce_integrations/issues/163)) ([54a4105](https://github.com/aerele/ecommerce_integrations/commit/54a41054de19171ad98e01c1342dba608aa63749)) * avoid duplicate logs for old order sync ([9f90628](https://github.com/aerele/ecommerce_integrations/commit/9f9062885440d00edde522e46fae96d280ae3508)) * avoid duplication of product on shopify ([341625c](https://github.com/aerele/ecommerce_integrations/commit/341625ce60096c935d11b732155d660e982a02f3)) * avoid possible infinite loop with save triggers ([bc8e46d](https://github.com/aerele/ecommerce_integrations/commit/bc8e46dbeaa4f07e0b0d6d4bbb41d033b9e1398f)) * avoid triggering custom field creation ([01a9c26](https://github.com/aerele/ecommerce_integrations/commit/01a9c26e9471798e14b79dd072427f120348bc4e)) * bad logging ([cd210c2](https://github.com/aerele/ecommerce_integrations/commit/cd210c2afad0c59598638e953c29d54783bdc882)) * better error message in case of HTTPError ([e70e586](https://github.com/aerele/ecommerce_integrations/commit/e70e586c6526251fefbd58bc3e83e41f9d98b2b9)) * better message for WH registration failure ([60ed710](https://github.com/aerele/ecommerce_integrations/commit/60ed710c272fa253170b8ef08ac9c8929a3127b6)) * bump python version to 3.8 ([a31155c](https://github.com/aerele/ecommerce_integrations/commit/a31155c90a0ee706ca9c8703e6664e4047e1ee02)) * bump shopify api version ([203df24](https://github.com/aerele/ecommerce_integrations/commit/203df246f4484cb151ece793b86725eeb045b794)) * bump shopify API version ([#254](https://github.com/aerele/ecommerce_integrations/issues/254)) ([d0fb890](https://github.com/aerele/ecommerce_integrations/commit/d0fb890fba4d9d8902e813196d2ea38b133ba404)) * Bump shopify version ([#310](https://github.com/aerele/ecommerce_integrations/issues/310)) ([0eeef5e](https://github.com/aerele/ecommerce_integrations/commit/0eeef5edbb49e506f63efb033de295928818fd87)) * can't save new item ([efe512a](https://github.com/aerele/ecommerce_integrations/commit/efe512ac22c6a03869e7850ac0c937f1179531b6)) * can't save new item ([5da0ea8](https://github.com/aerele/ecommerce_integrations/commit/5da0ea8bb3b2845f8d83240f47fcf7e7a16b2017)) * capture curency code from order data ([5023dc0](https://github.com/aerele/ecommerce_integrations/commit/5023dc0ca247f66ce32622cf22988039586acbb9)) * cast to string before concatenating ([3cf612b](https://github.com/aerele/ecommerce_integrations/commit/3cf612bd468cec39fc7fe722c3826226245002cf)) * change fieldtype to reduce row size ([4142837](https://github.com/aerele/ecommerce_integrations/commit/4142837187167dfdd45d13bc7f88a89190c8962c)) * change tax fields to invoice field names ([d325041](https://github.com/aerele/ecommerce_integrations/commit/d325041f8b530abfe0f21158160e0bcfc045fa9d)) * changing datatypes to small text ([81cf062](https://github.com/aerele/ecommerce_integrations/commit/81cf0628594bb0bce1d3f49168e7fef349928b8b)) * check enabled status before uploading item ([48aed46](https://github.com/aerele/ecommerce_integrations/commit/48aed4620c900a75f39fce466b7ac1dbf239b02f)) * check existence of custom field "amazon_item_code" ([a817cf9](https://github.com/aerele/ecommerce_integrations/commit/a817cf9e674c31c5fe5d10ce80a56ce93c67ecda)) * check for current webhooks ([00879cb](https://github.com/aerele/ecommerce_integrations/commit/00879cb549502939df02893cd167d7f45afd3f9d)) * check for emp on line item level ([2752336](https://github.com/aerele/ecommerce_integrations/commit/275233653dd447dec5c15ac39db3678cfcb94eb3)) * check item_code for unicommerce requirements ([d4ccea3](https://github.com/aerele/ecommerce_integrations/commit/d4ccea364ab74b5dd7db7a3b45bfde985c8b4713)) * check order status right before invoicing ([04bf9fc](https://github.com/aerele/ecommerce_integrations/commit/04bf9fcea1b9cbdc3f9b0f88eb7b97673d00697f)) * CI ([83713e4](https://github.com/aerele/ecommerce_integrations/commit/83713e43003c23e56ea7e15a2cf782c51b7012a0)) * CI ([4d451e2](https://github.com/aerele/ecommerce_integrations/commit/4d451e29cc6e866e3aa3a092b9dc4700d8b6dd39)) * ci commits ([23e2234](https://github.com/aerele/ecommerce_integrations/commit/23e2234268547bdd05e21cc5edaba73c78e3b5a4)) * clear all stale webhooks when registering ([4dbdc15](https://github.com/aerele/ecommerce_integrations/commit/4dbdc157e38fd1599060b5efa08864d38751fbc1)) * clear old integration logs automatically ([4452fea](https://github.com/aerele/ecommerce_integrations/commit/4452feae5250ce66c2d0247f6ba850399d1e4944)) * code to add employee ([5149d4d](https://github.com/aerele/ecommerce_integrations/commit/5149d4da2dbd3213eb60a5664cddca4ea6f15ecc)) * configurable client id ([cd0033f](https://github.com/aerele/ecommerce_integrations/commit/cd0033f61c71849a7fa2f5fc9a58d363b746e789)) * consider all warehouse for non-inventory ops ([1966dc2](https://github.com/aerele/ecommerce_integrations/commit/1966dc283c70fee52308429bd4aa53f140b837aa)) * consider reserved qty when updating inventory ([c462841](https://github.com/aerele/ecommerce_integrations/commit/c4628414d7fc74c524674f7c4fd2195d92bf3c77)) * consider variant id while creating new item ([0a2021b](https://github.com/aerele/ecommerce_integrations/commit/0a2021bda0a3e4d57fe457a76a9be4d6d796108e)) * convert HTML to text while syncing description ([#235](https://github.com/aerele/ecommerce_integrations/issues/235)) ([3546eca](https://github.com/aerele/ecommerce_integrations/commit/3546eca62b1c45301c9a9130c8a4b522bca1e3b6)) * convert tax title to string ([a6765a0](https://github.com/aerele/ecommerce_integrations/commit/a6765a0e325f5fd8f5c77d410918f7e8dc889ab3)) * convert tax title to string ([723dca6](https://github.com/aerele/ecommerce_integrations/commit/723dca6b36cafe70dd664b2659cb769f015608a7)) * correct endpoint for sales invoice generation ([868c4d9](https://github.com/aerele/ecommerce_integrations/commit/868c4d96face3341e28f31435e73f10f7c7992ad)) * correct invoice label API ([#88](https://github.com/aerele/ecommerce_integrations/issues/88)) ([f4bd860](https://github.com/aerele/ecommerce_integrations/commit/f4bd8609af63fb99ad6d0c830ef2d0e483f0e02a)) * correct method for defaults ([d9b38ba](https://github.com/aerele/ecommerce_integrations/commit/d9b38ba969d5dc4925395abdc5d5b4360d83e6b4)) * correct shipping provider code in invoice ([b814753](https://github.com/aerele/ecommerce_integrations/commit/b814753c293723f53248fdc2109d7e24e570d0b0)) * Correctly filter existing return order ([2fe3d07](https://github.com/aerele/ecommerce_integrations/commit/2fe3d07cd4dd4d2b7eda733910a28b13ba5c50b3)) * Correctly wrap function ([b1dc35c](https://github.com/aerele/ecommerce_integrations/commit/b1dc35cfd7cfd73e38dcdade7450e8ac05bc277d)) * create_brand() ([ddf31d2](https://github.com/aerele/ecommerce_integrations/commit/ddf31d2989a112a757446a3f2cb6c246f15feb76)) * create_manufacturer() ([230104d](https://github.com/aerele/ecommerce_integrations/commit/230104d9027b38eebb09e04eca1a8f96389931ad)) * custom fields for sales invoice items ([3edfffc](https://github.com/aerele/ecommerce_integrations/commit/3edfffc1922a2b960b2a731f02be80862d0ddb27)) * customer name concatenation ([#175](https://github.com/aerele/ecommerce_integrations/issues/175)) ([f8ac70b](https://github.com/aerele/ecommerce_integrations/commit/f8ac70b93b279008b8cf2f40a5da77bc4c87e1f0)) * deduplicate cusomters based on address ([37d4944](https://github.com/aerele/ecommerce_integrations/commit/37d494486b48866adbfe31169aa2cc1b7df8da63)) * delete custom field amazon_item_code after migration ([1eab7b7](https://github.com/aerele/ecommerce_integrations/commit/1eab7b71f33c4812c9ddf99ce2e534be61ee9e16)) * delete logs in single query before uninstall ([8b8251a](https://github.com/aerele/ecommerce_integrations/commit/8b8251a5a7b120adfbbe749a6e0e60604424b95c)) * disable uploading of items in data import ([b72717b](https://github.com/aerele/ecommerce_integrations/commit/b72717b42b6535ff3c0fbd826a0425dd700ef363)) * document modified in two separate calls ([9ead99c](https://github.com/aerele/ecommerce_integrations/commit/9ead99c1c2a5084f48ae0bc1196c0d72032ceb66)) * don't add unchanged items ([8889a86](https://github.com/aerele/ecommerce_integrations/commit/8889a8658cd68c02f609c5fcfce93bfb7608f85a)) * don't add unchanged items ([#66](https://github.com/aerele/ecommerce_integrations/issues/66)) ([948301e](https://github.com/aerele/ecommerce_integrations/commit/948301e1358fcb9516ac50543839815b2e7a0558)) * don't lock while updating tokens ([5910636](https://github.com/aerele/ecommerce_integrations/commit/5910636da0afa395577c86292ae02194c53f0311)) * don't match templates by skus ([#34](https://github.com/aerele/ecommerce_integrations/issues/34)) ([d6dec82](https://github.com/aerele/ecommerce_integrations/commit/d6dec82749a03f729e870a7cdefb770e3f59a6d2)) * don't try to create session if not configured ([#10](https://github.com/aerele/ecommerce_integrations/issues/10)) ([58b0eab](https://github.com/aerele/ecommerce_integrations/commit/58b0eab27adc4ca7c65a73ff14322b18fe86c453)) * don't try to create session if not configured ([#10](https://github.com/aerele/ecommerce_integrations/issues/10)) ([a671070](https://github.com/aerele/ecommerce_integrations/commit/a67107025b643eab84141b149890bb86e773cf05)) * don't use stored value while unregistering ([be220f7](https://github.com/aerele/ecommerce_integrations/commit/be220f7462a3ee0c24aeb7ccf9f5992730b76877)) * dont check for old settings on v14 ([#48](https://github.com/aerele/ecommerce_integrations/issues/48)) ([cd6e09d](https://github.com/aerele/ecommerce_integrations/commit/cd6e09dc933e2661e6c38e4e9d2bf108981aa472)) * dont log error for item query ([2b3fa0c](https://github.com/aerele/ecommerce_integrations/commit/2b3fa0ca4dcb29219ff3394a09b00e40b9e433a2)) * dont run invoice hooks if unicommerce isn't enabled ([cb5853f](https://github.com/aerele/ecommerce_integrations/commit/cb5853fd49bfc189e288f10fd839170605da54a5)) * dont update existing custom fields ([a688635](https://github.com/aerele/ecommerce_integrations/commit/a688635e7d25bdc3053332a410f74a658848d55b)) * dont upload price on unicommerce ([6a526e1](https://github.com/aerele/ecommerce_integrations/commit/6a526e1502b73dc91d1c649b07947fff017d2f37)) * employee filters and added db.commit ([#147](https://github.com/aerele/ecommerce_integrations/issues/147)) ([92b2340](https://github.com/aerele/ecommerce_integrations/commit/92b234052bed035732b0d2f481d61b16abf5b6de)) * error case ([a125c29](https://github.com/aerele/ecommerce_integrations/commit/a125c2999ea996c906ce18eafce18f24a418a910)) * error when there is no billing address is shopify order ([#283](https://github.com/aerele/ecommerce_integrations/issues/283)) ([d1e7354](https://github.com/aerele/ecommerce_integrations/commit/d1e735480e379263e2e4ed6b86e3535093adfb4e)) * error when there is no billing address is shopify order ([#283](https://github.com/aerele/ecommerce_integrations/issues/283)) ([f9a9dd1](https://github.com/aerele/ecommerce_integrations/commit/f9a9dd15e153794d8c94d48c820c99d3c2fd9d59)) * error while getting asin or product-id from report_document ([2114f56](https://github.com/aerele/ecommerce_integrations/commit/2114f56ddbce0f092719d45ef8efd7479b97cd46)) * false error about old settings ([c63bcd8](https://github.com/aerele/ecommerce_integrations/commit/c63bcd8e8144809cacd21b4a3103780f4e29815f)) * file save fails if filename contains `/` ([3ca7541](https://github.com/aerele/ecommerce_integrations/commit/3ca7541073fb01b68e73dba5b0f586068f19cc97)) * first inventory sync failing ([#54](https://github.com/aerele/ecommerce_integrations/issues/54)) ([c2acda4](https://github.com/aerele/ecommerce_integrations/commit/c2acda4214a41b4cf57a7e3b86b9dd0a61771e74)) * first inventory sync failing ([#54](https://github.com/aerele/ecommerce_integrations/issues/54)) ([c8ce50e](https://github.com/aerele/ecommerce_integrations/commit/c8ce50eb7994f21e46af39c0db071634a9ec7ee9)) * for demo ([220d51c](https://github.com/aerele/ecommerce_integrations/commit/220d51c546290c96888a7b0a226e094d77fd9a07)) * generate manifest before submit ([e99e64e](https://github.com/aerele/ecommerce_integrations/commit/e99e64e4aea6cada77f7746486c863460fdca380)) * get ecommerce item ([1bc0f53](https://github.com/aerele/ecommerce_integrations/commit/1bc0f53ae2d21528f03308298be63326d5a521ef)) * get sales invoice API requires facility code ([2bd845a](https://github.com/aerele/ecommerce_integrations/commit/2bd845a784a698a06618e4eaa239c1bad65691e5)) * get_erpnext_item not considering template ([aa08e1b](https://github.com/aerele/ecommerce_integrations/commit/aa08e1beca1cf92fd661af87b4abab7f5b41f4f0)) * gift card adding ([6725104](https://github.com/aerele/ecommerce_integrations/commit/6725104c75c4ee236a3bfda52a72cf6f487b6d56)) * Give low priority to SKU ([#287](https://github.com/aerele/ecommerce_integrations/issues/287)) ([042c88a](https://github.com/aerele/ecommerce_integrations/commit/042c88a8837749728d68d5e67bbff26decaf014c)) * handle case when error message is missing ([1a86ece](https://github.com/aerele/ecommerce_integrations/commit/1a86ece246da6b079036fdb434b010bbeb0a09c6)) * handle case where shipping address is same ([4618a93](https://github.com/aerele/ecommerce_integrations/commit/4618a93631ba2c4c59dffdc89c55ca2934a07777)) * handle inconsistency in state naming in Unicommerce ([50d3f1a](https://github.com/aerele/ecommerce_integrations/commit/50d3f1aff704da2080ca1e476f3f2f4463205cbd)) * handle refresh token expiry ([5c3115b](https://github.com/aerele/ecommerce_integrations/commit/5c3115b195879d8fd8e741a2e59ba4f923f77968)) * handle success/failure properly ([fecfcb0](https://github.com/aerele/ecommerce_integrations/commit/fecfcb05f40aa96181ef65b7e2d9066619523df1)) * handle tax inclusive shipping ([a4b2fbb](https://github.com/aerele/ecommerce_integrations/commit/a4b2fbb7a065a31f87f5101c78a1678a200ec1f0)) * handle tax inclusive shipping ([870c239](https://github.com/aerele/ecommerce_integrations/commit/870c2395827a192ad57f497398ac9c1126829da0)) * handle wrong credentials gracefully ([17c392c](https://github.com/aerele/ecommerce_integrations/commit/17c392c98e6bc2b0ea53f30e1d27766ab788d1e5)) * hide default SI generation button ([5010bd9](https://github.com/aerele/ecommerce_integrations/commit/5010bd95a16e79f18acb064961fbc86b1239487f)) * hide irrelevant manifest fields in print ([1126680](https://github.com/aerele/ecommerce_integrations/commit/112668057467551da9251a3a8ca84b15614fbc24)) * if warehouse map is available, use it in DN ([cb0cd72](https://github.com/aerele/ecommerce_integrations/commit/cb0cd72556bf716e9fe9cd2c714f392b7d026e8e)) * ignore 0 tax amount ([ee164d5](https://github.com/aerele/ecommerce_integrations/commit/ee164d5dfdb7a2290d15abb756e4eca2e86075a3)) * ignore any possible exception from old deleted doctype ([32a62fc](https://github.com/aerele/ecommerce_integrations/commit/32a62fc199699af55077903eee14b09d935dff5a)) * ignore deleted variants from sync ([27c6907](https://github.com/aerele/ecommerce_integrations/commit/27c6907f092f013855262ba718beb36802fd5d1e)) * ignore incomplete items while migrating ([fdaeb14](https://github.com/aerele/ecommerce_integrations/commit/fdaeb141c1ef7d1c3894a8c2a4d70360c428ea18)) * ignore mandatory fields while syncing address ([#28](https://github.com/aerele/ecommerce_integrations/issues/28)) ([06c3de0](https://github.com/aerele/ecommerce_integrations/commit/06c3de07afc2c4ade0461115aa1d26dff86df339)) * ignore non batched items in auto grn ([8e9f673](https://github.com/aerele/ecommerce_integrations/commit/8e9f6735fc3ca021ce7977264856c092db79cd57)) * ignore permissions while updating token ([fa461c8](https://github.com/aerele/ecommerce_integrations/commit/fa461c8e0223988c668c5ffdeb4736ac213715a1)) * ignore picklist validation if not enabled ([1fee343](https://github.com/aerele/ecommerce_integrations/commit/1fee343b2ac5d3e3d5bde9dc548f28ba28e6efa8)) * improve logging for item sync ([40001f3](https://github.com/aerele/ecommerce_integrations/commit/40001f3777d371f8854b21b62f4f26a4fe5794ce)) * improve scanning of packages ([d1bbbe1](https://github.com/aerele/ecommerce_integrations/commit/d1bbbe16a0da5ba0f03016bbd7be1bf9cc62b1c0)) * incorrect patch file ([2a6d5e5](https://github.com/aerele/ecommerce_integrations/commit/2a6d5e5ebf5856b20d8d6814ecd8ff745468bf03)) * increase migration timeout and other fixes ([d7b1f67](https://github.com/aerele/ecommerce_integrations/commit/d7b1f6739fe2e42fe47f14158613be36e2e0052d)) * increase timespan for searching new orders ([fdfbdf0](https://github.com/aerele/ecommerce_integrations/commit/fdfbdf05e25fae28cc2a9071d2b8a0a356091d6a)) * initiate manifest items if not existing ([e12db27](https://github.com/aerele/ecommerce_integrations/commit/e12db27931dc553ff14ccfd5622fb40014ddb369)) * integration_item_code for inventory sync ([#7](https://github.com/aerele/ecommerce_integrations/issues/7)) ([64d6146](https://github.com/aerele/ecommerce_integrations/commit/64d6146cb18721ab4c34240d75f9504dbc8dfd17)) * inventory sync failure when last_run not present ([88eaefe](https://github.com/aerele/ecommerce_integrations/commit/88eaefe5066a8cea174b08d3170d90ad02754cbc)) * invoice not submitting with WH allocation ([6fa6561](https://github.com/aerele/ecommerce_integrations/commit/6fa6561c62f7791447dbc1573d3f585834414403)) * item level discount calculations ([#56](https://github.com/aerele/ecommerce_integrations/issues/56)) ([1eaf360](https://github.com/aerele/ecommerce_integrations/commit/1eaf360521b53e66751357b8f7f8951a3847910d)) * item level discount calculations ([#56](https://github.com/aerele/ecommerce_integrations/issues/56)) ([5c58d82](https://github.com/aerele/ecommerce_integrations/commit/5c58d82ecff7e6223ccc8c2d7aa3fc03aa4a36ba)) * item wise tax detail missing on shipping lines ([#232](https://github.com/aerele/ecommerce_integrations/issues/232)) ([08e41d2](https://github.com/aerele/ecommerce_integrations/commit/08e41d26a543be54473c22cc4d959710d927e370)) * item_group_name ([9583e44](https://github.com/aerele/ecommerce_integrations/commit/9583e4410815eef635c9db72437f12be883581c2)) * item-wise tax distribution ([92b5eb7](https://github.com/aerele/ecommerce_integrations/commit/92b5eb79e7b982d0bf6c773c7092bdbbfc99a59e)) * iterate and fetch all locations ([af4c57c](https://github.com/aerele/ecommerce_integrations/commit/af4c57cc61cd21bbdf740e530b2c7ba3fc1b6b32)) * keep all items for fully cancelled order ([fa89ba9](https://github.com/aerele/ecommerce_integrations/commit/fa89ba9306f5720458b2afff39f1d19f160ef35e)) * keep return warehouse on location mapping ([a45a7cd](https://github.com/aerele/ecommerce_integrations/commit/a45a7cd5a2b785491e62c37d0f3cb099815d16c8)) * keep shopify api ops in decorated function ([a81df87](https://github.com/aerele/ecommerce_integrations/commit/a81df8778ecd9edbc438032f91183a0c6d7bc97a)) * keyError(processingStatus) ([6cd99e1](https://github.com/aerele/ecommerce_integrations/commit/6cd99e14d26fc0ffc266436c794b802755d37bfb)) * lack of variant_id should not mean template ([910dd61](https://github.com/aerele/ecommerce_integrations/commit/910dd61c52dad42e46e205ed44776719592bf951)) * limit inventory updates in single request ([f050044](https://github.com/aerele/ecommerce_integrations/commit/f0500441097016827f767c1e157a3822bb4b28fc)) * limit maximum number of days to sync to 14 ([e836e8f](https://github.com/aerele/ecommerce_integrations/commit/e836e8f976124db7ad65ec8633cd3790f0ae3386)) * link created SI with SO ([3e2c67d](https://github.com/aerele/ecommerce_integrations/commit/3e2c67d2ac5fb856238313b7a2d0355781f6fe7d)) * linter ([65c232e](https://github.com/aerele/ecommerce_integrations/commit/65c232ec804f69a8008b799272dec9e0179c6bdb)) * linter issue ([1ed822c](https://github.com/aerele/ecommerce_integrations/commit/1ed822c455c124b0b8c57f3fd68c956be2b9888f)) * linter issue ([a791d44](https://github.com/aerele/ecommerce_integrations/commit/a791d44014010dd72ef0793c0236ee330baaab73)) * linter issue ([ee80db5](https://github.com/aerele/ecommerce_integrations/commit/ee80db57ed026f9235ffe4bd840e1297827dcff4)) * linter issue ([e8e1916](https://github.com/aerele/ecommerce_integrations/commit/e8e191600767d925eb76bf58984cc2c82f3643cb)) * linter issue: ([fbe8f86](https://github.com/aerele/ecommerce_integrations/commit/fbe8f868f559e6363bd777ec1135530d6d1c58ed)) * linting issues ([f676b2c](https://github.com/aerele/ecommerce_integrations/commit/f676b2c3a45f5cadcbcd1f42208ce22447d1675a)) * linting issues ([369fcda](https://github.com/aerele/ecommerce_integrations/commit/369fcdaab2811ba658b181c1ff06128c8951ff6c)) * linting issues ([e1b2f3d](https://github.com/aerele/ecommerce_integrations/commit/e1b2f3d64dd100cdb1b7c8d0c915ba81bdeb2618)) * log item level failures during inventory sync ([592bf79](https://github.com/aerele/ecommerce_integrations/commit/592bf79134ac3afff62ced75f765f5d4c27b315b)) * logging zenoti error message, status code and title ([97d7d0c](https://github.com/aerele/ecommerce_integrations/commit/97d7d0ce49608d739c2b58e8f174c674820c3ea1)) * make `state` title case ([d0b2da0](https://github.com/aerele/ecommerce_integrations/commit/d0b2da00ac7b5ba636548198e0ef317bbf67320a)) * make fields mandatory based on status ([a6677db](https://github.com/aerele/ecommerce_integrations/commit/a6677db0b7682426f0187207a1bc7ef9ae8611ba)) * make tax description upper case ([ca1a39a](https://github.com/aerele/ecommerce_integrations/commit/ca1a39a2dae72172b29248662efda0014320c894)) * map shopify weight unit to ERPNext ([3e2f5c3](https://github.com/aerele/ecommerce_integrations/commit/3e2f5c30d0d558b69baf444b13283cb1a3aa06f9)) * misc unicommerce fixes ([#89](https://github.com/aerele/ecommerce_integrations/issues/89)) ([710d0db](https://github.com/aerele/ecommerce_integrations/commit/710d0db7eae9de8841350359eec0f091ad696f8e)) * misleading "success" message for DN when SO doesn't exist ([7ea6d15](https://github.com/aerele/ecommerce_integrations/commit/7ea6d15c4347b0e84ae9bce030acb12d72f8febb)) * module def for tax account child table ([7170974](https://github.com/aerele/ecommerce_integrations/commit/717097423c0c7c7ac96a685f237bb1ef4a4e7488)) * move custom field creation ([9a2d4d0](https://github.com/aerele/ecommerce_integrations/commit/9a2d4d057eb10a9326d448181e1ec39af2f3a232)) * move status update job to from 30 to 60 min ([0b7e922](https://github.com/aerele/ecommerce_integrations/commit/0b7e92235c1ce68e862568d764b3042b9d7fe523)) * old data migration checks not stopping ([70c0688](https://github.com/aerele/ecommerce_integrations/commit/70c06887202a15960c48ec8bc18998f55582f07f)) * only add shipping line if >0 ([fc354ef](https://github.com/aerele/ecommerce_integrations/commit/fc354efaf1d9486162689fcc5d9929baa606a61d)) * only log inventory sync failure in single log ([5e8928e](https://github.com/aerele/ecommerce_integrations/commit/5e8928e8dee542065a6d571d7e1eb952071c85dc)) * only set inventory tracking if is_stock_item ([cac20c1](https://github.com/aerele/ecommerce_integrations/commit/cac20c184135a0957b04c8e56b95ed6a33967ad3)) * order -> payload for new function definition ([6a38497](https://github.com/aerele/ecommerce_integrations/commit/6a3849792677fa1bc505cf40b4ecb0f375696f14)) * pass on product_id in case of variant ([aea67a7](https://github.com/aerele/ecommerce_integrations/commit/aea67a794c221d76432deeafa3da9fcdfb368dc2)) * patch shopify methods during test ([1dff9f6](https://github.com/aerele/ecommerce_integrations/commit/1dff9f65be8a85d6779aa0ede5f881d1db04088d)) * patch to update custom fields ([a2824a9](https://github.com/aerele/ecommerce_integrations/commit/a2824a9804da3f257e0f83e7c00d209296c9f42c)) * **patch:** `set_default_amazon_item_fields_map` ([#263](https://github.com/aerele/ecommerce_integrations/issues/263)) ([27f777b](https://github.com/aerele/ecommerce_integrations/commit/27f777bb15e0762b4893f6bcbcb4f27ceb9282fd)) * phone number mapping in address ([#198](https://github.com/aerele/ecommerce_integrations/issues/198)) ([66a3782](https://github.com/aerele/ecommerce_integrations/commit/66a37821a31e17985990145de9018b20938d279b)) * populate correct rate for item-tax reports ([ac5cff5](https://github.com/aerele/ecommerce_integrations/commit/ac5cff5a3c9cb84eb9e7ed6f47f464a5631cceea)) * pos related issues ([07d4d5f](https://github.com/aerele/ecommerce_integrations/commit/07d4d5fca7190f2d711ad8e826907a01f56bc43a)) * possible deadlock while deleting dummy prices ([#177](https://github.com/aerele/ecommerce_integrations/issues/177)) ([c22dd49](https://github.com/aerele/ecommerce_integrations/commit/c22dd497a39e7910635723a07cf99b6e40c66f4c)) * possible missing updates during time of sync ([01fafe0](https://github.com/aerele/ecommerce_integrations/commit/01fafe018a2494b609d57dd512abdba3057cc93c)) * prioritize user input in tax description ([83c5299](https://github.com/aerele/ecommerce_integrations/commit/83c52994a2c1720a7b9bce3101dbb003dd7856ce)) * proceed for center code only when response available ([36b567a](https://github.com/aerele/ecommerce_integrations/commit/36b567a67592e657461b96b7419b792d155fd0b2)) * proceed for center code only when response available ([2ab0fa4](https://github.com/aerele/ecommerce_integrations/commit/2ab0fa4af34d3c9a5c64e98d6352c8f6fb4804f7)) * proceed for center code only when response available ([#68](https://github.com/aerele/ecommerce_integrations/issues/68)) ([b397228](https://github.com/aerele/ecommerce_integrations/commit/b3972281b8fc64d90fc009a6714e129e593a9361)), closes [#56](https://github.com/aerele/ecommerce_integrations/issues/56) * proceed only if response from api received ([d0fb8dd](https://github.com/aerele/ecommerce_integrations/commit/d0fb8dd1f143af2fd243e527c3ae7f58297eb03a)) * proceed with stock reconciliation only when response available ([#69](https://github.com/aerele/ecommerce_integrations/issues/69)) ([feb0c8b](https://github.com/aerele/ecommerce_integrations/commit/feb0c8b043c17a6907342c96a22ab889f8ee6874)), closes [#56](https://github.com/aerele/ecommerce_integrations/issues/56) * product sync ([3acebaa](https://github.com/aerele/ecommerce_integrations/commit/3acebaa1254e964d72639774f2c18497840e3602)) * re-introduce consolidation ([36e3a39](https://github.com/aerele/ecommerce_integrations/commit/36e3a393d1b0293773d541998739f939aa6daeb1)) * reload SO page after invoice is generated ([9591e05](https://github.com/aerele/ecommerce_integrations/commit/9591e05923b3633bfceea10e49e7f283d169ff00)) * remove cancelled items from WH allocations ([52958c2](https://github.com/aerele/ecommerce_integrations/commit/52958c27cf0904178b79099676d16fc425914195)) * remove explicit commit ([462253d](https://github.com/aerele/ecommerce_integrations/commit/462253d3e1aeb432e7f209f5d0dbd0c68d72a3f1)) * remove explicit commits ([#31](https://github.com/aerele/ecommerce_integrations/issues/31)) ([1f117b8](https://github.com/aerele/ecommerce_integrations/commit/1f117b81e2b041c13ded7a564c31b3b42d327447)) * remove image upload ([9dcec08](https://github.com/aerele/ecommerce_integrations/commit/9dcec084b6ed76d85134d7fa784ac84d9a3e84be)) * remove the trailing comma after the last SKU ([#201](https://github.com/aerele/ecommerce_integrations/issues/201)) ([2894160](https://github.com/aerele/ecommerce_integrations/commit/2894160cb9fcd617c8523820fef0434f144d5970)) * removed print statement ([d6b65d9](https://github.com/aerele/ecommerce_integrations/commit/d6b65d93805b2181bd53005e6faa21606452807e)) * removed Tax Mapping doctype ([fa7196c](https://github.com/aerele/ecommerce_integrations/commit/fa7196c70e778c797b2cfc2ac3ea81293d4f53e7)) * removed unwnated file ([23261aa](https://github.com/aerele/ecommerce_integrations/commit/23261aa07f59dbef6c6d3527afe0d0b136ecc309)) * repeat items in sales invoice ([eae1b18](https://github.com/aerele/ecommerce_integrations/commit/eae1b18daaf923cb798dcdd33c79a0058023108a)) * restrict retry button to system managers ([b67c15e](https://github.com/aerele/ecommerce_integrations/commit/b67c15eeb8c678f649b40531af35e30fc84add5a)) * reverting error_message field type to text editor ([96a039a](https://github.com/aerele/ecommerce_integrations/commit/96a039aae6f36cd85ad7eb6defeddba5d16ce658)) * rollback failed order syncs ([#40](https://github.com/aerele/ecommerce_integrations/issues/40)) ([67198b8](https://github.com/aerele/ecommerce_integrations/commit/67198b87689e24815e6bb332695ab9233d1b98e1)) * rollback on errors ([1db68e9](https://github.com/aerele/ecommerce_integrations/commit/1db68e961d6fd7969c8601de8b34d7ba0b3d7bad)) * Round tax in description and add to shipping calculations ([#21](https://github.com/aerele/ecommerce_integrations/issues/21)) ([#22](https://github.com/aerele/ecommerce_integrations/issues/22)) ([0e6318d](https://github.com/aerele/ecommerce_integrations/commit/0e6318df8aa1a9dc8c8e5d734e1360e4d014cecf)) * RTO return credit note extra paramter ([8daa8f6](https://github.com/aerele/ecommerce_integrations/commit/8daa8f6f927a0c722ccbc456b004baa791f79b2a)) * run uniqueness check only on insert ([7e34e87](https://github.com/aerele/ecommerce_integrations/commit/7e34e870fd1ca8cb08c1b871cfe48057b828659a)) * sales invoice syncing issue ([#143](https://github.com/aerele/ecommerce_integrations/issues/143)) ([6baa4c6](https://github.com/aerele/ecommerce_integrations/commit/6baa4c620502a6640d0f8f9dbe6b12f98389ba28)) * sales order status not updated on cancel ([99382a2](https://github.com/aerele/ecommerce_integrations/commit/99382a29b4009a16906a8e5eec8c3c2f52a3cf51)) * sales transaction issues ([#138](https://github.com/aerele/ecommerce_integrations/issues/138)) ([5817c1b](https://github.com/aerele/ecommerce_integrations/commit/5817c1b0674f8d28cee34cf1198e5809b013fbf5)) * same items inventory sync across multiple WH ([71675fb](https://github.com/aerele/ecommerce_integrations/commit/71675fbf381beac1a5f3e97795656171424128e0)) * save after renewing tokens ([1c0f51e](https://github.com/aerele/ecommerce_integrations/commit/1c0f51ead86ea9fc23a86e8b8e670caa4b01f73a)) * scan duplicate and manifested packages ([6b4a624](https://github.com/aerele/ecommerce_integrations/commit/6b4a6246053b5a15acf435e529e85e49c759cba8)) * send full image url path ([f0d4aee](https://github.com/aerele/ecommerce_integrations/commit/f0d4aee54f90d5afa465e7ae186efd53879f25c9)) * set default field-map onload ([60c936a](https://github.com/aerele/ecommerce_integrations/commit/60c936ab608b324d208a77273b64541b42ca73a8)) * setting for updating changes to existing item ([9c6919b](https://github.com/aerele/ecommerce_integrations/commit/9c6919bb7c67dfbaabeb00c3e052395024659490)) * setting last sync ([8b6d604](https://github.com/aerele/ecommerce_integrations/commit/8b6d60441b4395156f4390b83db7632aa7a57728)) * shopify default customer ([#270](https://github.com/aerele/ecommerce_integrations/issues/270)) ([187ffdb](https://github.com/aerele/ecommerce_integrations/commit/187ffdb65e21abb8b796a786b9f46f4e05724a66)) * shopify default customer ([#270](https://github.com/aerele/ecommerce_integrations/issues/270)) ([882207f](https://github.com/aerele/ecommerce_integrations/commit/882207f931fedfea0ff43d012c63c2c005b62b3f)) * shopify sync issue without customer ([8d41768](https://github.com/aerele/ecommerce_integrations/commit/8d4176852bd7fb622179caaea62bd4f1606786d1)) * shopify sync issue without customer ([3763ab5](https://github.com/aerele/ecommerce_integrations/commit/3763ab5d113ca29d477f6d12f1672c302958672d)) * **shopify:** correct module name ([b217265](https://github.com/aerele/ecommerce_integrations/commit/b2172659c936fd3598096aa7c27efe08072cbd64)) * **shopify:** don't run migration before enabling ([3ea1e3e](https://github.com/aerele/ecommerce_integrations/commit/3ea1e3efd781878aaf2a4aac7744e6b38bdb735e)) * **shopify:** handle multiple instance of same item in delivery ([7d663c1](https://github.com/aerele/ecommerce_integrations/commit/7d663c1a95d49b7d7b433c10152eabdd479fa996)) * **shopify:** ignore invalid dummy phone numbers ([61da9fa](https://github.com/aerele/ecommerce_integrations/commit/61da9faedc458e3cca4d2bbc95d582ba1fd05b4f)) * **shopify:** ignore unsupported methods in resync ([77d41d5](https://github.com/aerele/ecommerce_integrations/commit/77d41d503d547f364a179639315608c228390f28)) * show update button only if upload is enabled ([95e8f4a](https://github.com/aerele/ecommerce_integrations/commit/95e8f4a00363ae3ebd3fa827304fad5ca69d5193)) * SI falsely stuck in queued state ([bc3558b](https://github.com/aerele/ecommerce_integrations/commit/bc3558b30448a5bb432e59cfc23605fb163656f7)) * SI line item quantity functionality ([4676774](https://github.com/aerele/ecommerce_integrations/commit/467677445400feff2ad1543276b24722a424ee6f)) * skip cancel_order if order is not found ([dfb851e](https://github.com/aerele/ecommerce_integrations/commit/dfb851efe054e672a77ef770275f2699e9fde5e1)) * sku only allowed for non-template items ([df5d6eb](https://github.com/aerele/ecommerce_integrations/commit/df5d6eb5eeb6579090be4c2085f3d1f14109d819)) * start and end date ([09a91cc](https://github.com/aerele/ecommerce_integrations/commit/09a91ccfe3877b8ddd6cb269ad828648a869f3eb)) * State mapping ([f158a4a](https://github.com/aerele/ecommerce_integrations/commit/f158a4ab4245e18132a948ad71e3dcb87ab7bc9a)) * status not chaning from queued on retry ([0060e29](https://github.com/aerele/ecommerce_integrations/commit/0060e29eee62706cbbeb1b0e1254015efd9ba610)) * store invoice data and logs ([a92a019](https://github.com/aerele/ecommerce_integrations/commit/a92a01914a128216895852af16c45fae2004609e)) * syncing therapists creates duplication records ([#150](https://github.com/aerele/ecommerce_integrations/issues/150)) ([2d06962](https://github.com/aerele/ecommerce_integrations/commit/2d069624c5022a73caeaf67b6211b5b334b89106)) * tax category to avoid tax templates ([#32](https://github.com/aerele/ecommerce_integrations/issues/32)) ([cfd4132](https://github.com/aerele/ecommerce_integrations/commit/cfd413272e1c11cb85bdd8b432955687bdb765c9)) * tests ([212127b](https://github.com/aerele/ecommerce_integrations/commit/212127b1346c406b700c6cbc7d0db044570e8cfc)) * tests for mocking webhook response ([62d9876](https://github.com/aerele/ecommerce_integrations/commit/62d9876425fb3976a2ee43a204aa85b0346b812f)) * timeout issue in item and stock reconcilation ([#135](https://github.com/aerele/ecommerce_integrations/issues/135)) ([cf61bae](https://github.com/aerele/ecommerce_integrations/commit/cf61bae7c6d4632577b0a021c7fb40c4efb2b63b)) * tips related issue ([df50aa2](https://github.com/aerele/ecommerce_integrations/commit/df50aa27e08a444638064d5ea1dc0838fdf04ded)) * try both sku and product id for getting item code ([10366b8](https://github.com/aerele/ecommerce_integrations/commit/10366b8b6745a2bb0f0abb6cba9b7d0048114e4c)) * type case ([db948ec](https://github.com/aerele/ecommerce_integrations/commit/db948ecd222613b14b50692c3882e1f53eb8a5a7)) * typo ([fea8bec](https://github.com/aerele/ecommerce_integrations/commit/fea8bec5e86c7cbcc28bee967a71f688eaf9046d)) * typo in addres field ([92b853b](https://github.com/aerele/ecommerce_integrations/commit/92b853bc27de7837b8b5b6ddaeb0bcc31ba3ba14)) * typos ([7e7c288](https://github.com/aerele/ecommerce_integrations/commit/7e7c288babfe63ca35d157b6de9d363124fe17b8)) * unable to save `Amazon SP API Settings` ([#262](https://github.com/aerele/ecommerce_integrations/issues/262)) ([ffb7c97](https://github.com/aerele/ecommerce_integrations/commit/ffb7c97ad79d5888b0f9358c212356932b6f2db6)) * undo consolidation of items in SO ([f6b8800](https://github.com/aerele/ecommerce_integrations/commit/f6b8800c0c9deb52a8d8449224649f3a8fea9815)) * unicommerce date format ([18ea0e5](https://github.com/aerele/ecommerce_integrations/commit/18ea0e532df69f5d6cd9a3ad46f0a35f8223fd63)) * **unicommerce:** set `name` also when syncing new item ([6b2199f](https://github.com/aerele/ecommerce_integrations/commit/6b2199f33450dd6dfa5383cccbb98bfef40b8ba4)) * **unicommerce:** use updated since instead of from_date ([91b2ee9](https://github.com/aerele/ecommerce_integrations/commit/91b2ee9a1530ad67f67d151370edf6f885f79e58)) * update filters for buying and selling list ([b965af5](https://github.com/aerele/ecommerce_integrations/commit/b965af54b2bee0f3c40742380a31875ff78070bc)) * update labels for custom app ([4041b2c](https://github.com/aerele/ecommerce_integrations/commit/4041b2cd2fe538dab00cc1a202ee65f4fa9a5c15)) * update package dimension failing ([cab8b37](https://github.com/aerele/ecommerce_integrations/commit/cab8b37b98141563e3e3ca657d3aa6cb5617c283)) * updated woocommerce connection test ([896a9d5](https://github.com/aerele/ecommerce_integrations/commit/896a9d505804a1a6f8bc65afc7e7e0dcd6d18525)) * updated woocommerce connection test ([acf48b8](https://github.com/aerele/ecommerce_integrations/commit/acf48b80d1382339c8293752b1f64b6c4eaf7ef6)) * updating webhooks for first time ([e0f7de5](https://github.com/aerele/ecommerce_integrations/commit/e0f7de5064c9403d9509b75de0d9551a0a77ed51)) * use center code insted of center name ([9abd316](https://github.com/aerele/ecommerce_integrations/commit/9abd316a4de2ebb71279ece36e4148c694659e28)) * use correct invoice series while invoicing ([#121](https://github.com/aerele/ecommerce_integrations/issues/121)) ([b342b9e](https://github.com/aerele/ecommerce_integrations/commit/b342b9ed67f6d2b4c5b0b6d3fb43aa890ccd59f5)) * use correct target for mapped doc ([a88ae28](https://github.com/aerele/ecommerce_integrations/commit/a88ae28448d9161ed6e94d224b278537f56bd44a)) * use db column existence instead of meta for check ([e65ad9e](https://github.com/aerele/ecommerce_integrations/commit/e65ad9e5d6939c87d2b727daecaa0461dce83421)) * use default shopify customer is none provided ([afb7543](https://github.com/aerele/ecommerce_integrations/commit/afb754371831b00da5ce1980cac3f541dfaa6776)) * use doc local flags for locking behaviour ([d58b7de](https://github.com/aerele/ecommerce_integrations/commit/d58b7de645e6fb74ae5163c49d767fcb4cdd4b79)) * use dummy price list to avoid clashes ([#168](https://github.com/aerele/ecommerce_integrations/issues/168)) ([c01e0b1](https://github.com/aerele/ecommerce_integrations/commit/c01e0b190f7383db6e1c45c67e4ef3d92388144c)) * use get_password() to get value for password fields ([651ac22](https://github.com/aerele/ecommerce_integrations/commit/651ac2245dc32efc96a484ba2f5911401763f2ba)) * use invoice reponse for label link ([9301c01](https://github.com/aerele/ecommerce_integrations/commit/9301c0149bbb1c1e745da1a11c1bd5cefbe49773)) * use net_total for total discount ([845e962](https://github.com/aerele/ecommerce_integrations/commit/845e962b093de14d011baa04655734fc55fc950e)) * use RQ job to query active jobs ([f7100d2](https://github.com/aerele/ecommerce_integrations/commit/f7100d2060678e384adadf18881be455fe80c94a)) * use separate endpoint if item exists ([29944f3](https://github.com/aerele/ecommerce_integrations/commit/29944f3805c0009712c07c79b4537a2e917ff6e2)) * use set instead of setattr ([f68f924](https://github.com/aerele/ecommerce_integrations/commit/f68f924d84676436daad8eaaa88bb5ed73919f54)) * use shopify order date during old order sync ([3848f19](https://github.com/aerele/ecommerce_integrations/commit/3848f196a01731b29a399d5b921af898a2516bcc)) * use simpler endpoint for invoice generation ([b6d41d0](https://github.com/aerele/ecommerce_integrations/commit/b6d41d0d3156f3c7d2d0a923f4c758252a393ff3)) * use SO data in absense of invoice response ([db09591](https://github.com/aerele/ecommerce_integrations/commit/db09591cba9a4bdca2fcb920d2c2b5130b845580)) * use tax category to ignore item tax templates ([502026f](https://github.com/aerele/ecommerce_integrations/commit/502026f40f4900a8f9c1ea296d07a193b2b338ae)) * Use TZ aware ISO 8601 date format ([9613b5c](https://github.com/aerele/ecommerce_integrations/commit/9613b5cfe38a68b897da29290840f606d5070f6c)) * Use UTC timestamp for filtering recent orders ([6ab22a0](https://github.com/aerele/ecommerce_integrations/commit/6ab22a04a3db197dabece0c1fbbb6eb14b6f57bc)) * use zulu time in search sale order ([b0ff1de](https://github.com/aerele/ecommerce_integrations/commit/b0ff1de3e3ffe0af5d1f2279a02e20797fdb118a)) * **ux:** add appropriate query filters ([cd807d2](https://github.com/aerele/ecommerce_integrations/commit/cd807d2b3e9a458dc8dcc17f9bb49325fc8e6691)) * **ux:** allow stock manager/user to use manifest ([#95](https://github.com/aerele/ecommerce_integrations/issues/95)) ([90517c1](https://github.com/aerele/ecommerce_integrations/commit/90517c132ffca44c375cce71966444ea4adc4dad)) * **ux:** better title for logs ([347564e](https://github.com/aerele/ecommerce_integrations/commit/347564e7b81c08d30ad67be1eedfc5e50e3c8ff3)) * **ux:** clean up shopify setting page ([#26](https://github.com/aerele/ecommerce_integrations/issues/26)) ([58e8a6e](https://github.com/aerele/ecommerce_integrations/commit/58e8a6e0adf4f51cd6a48febb482a8547107aa6a)) * **ux:** cleanup ecommerce item views ([dbc7a72](https://github.com/aerele/ecommerce_integrations/commit/dbc7a72e82ffd78406f7413101538d14639fa615)) * **ux:** cleanup log list view ([137b571](https://github.com/aerele/ecommerce_integrations/commit/137b571abeca30529a105de603d087e895c2587c)) * **UX:** Correct URL in shopify webhooks ([71d5a3f](https://github.com/aerele/ecommerce_integrations/commit/71d5a3fc0214bb60bd42c609f6379204109d4f3c)) * **ux:** custom field order of insertion ([0e45b9f](https://github.com/aerele/ecommerce_integrations/commit/0e45b9f1a35ce7dc86932cbd6ed6c4facb6919bd)) * **ux:** don't show Sync buttons in local doc ([e525f9e](https://github.com/aerele/ecommerce_integrations/commit/e525f9ec5e9945dab7d5a186918f4c7bab252997)) * **ux:** don't validate unless required ([bbdb2a4](https://github.com/aerele/ecommerce_integrations/commit/bbdb2a4ad1607e56dd9c49b301aacf5e4f305a1b)) * **ux:** hide old doctype to avoid confusion ([#3](https://github.com/aerele/ecommerce_integrations/issues/3)) ([7d682fe](https://github.com/aerele/ecommerce_integrations/commit/7d682fe3afb0c7deeec282ea7739e5a287f6d284)) * **ux:** improve form view for uni manifest ([e8a76c0](https://github.com/aerele/ecommerce_integrations/commit/e8a76c0874b392f3fe0d697915285c80f9de0011)) * **ux:** redo channel config form layour ([3ab26ff](https://github.com/aerele/ecommerce_integrations/commit/3ab26ffcd6f7f5eb24db3cc17f776f7fb29d45d3)) * **ux:** remove tokens on disabling integration ([f9ea9d2](https://github.com/aerele/ecommerce_integrations/commit/f9ea9d24b0a736b9d5a70615cff3cb860bf69785)) * **UX:** show logs button on shopify setting ([9d131f5](https://github.com/aerele/ecommerce_integrations/commit/9d131f58469529145a4bd1a4ca70b24281798c54)) * validate address ([e96c206](https://github.com/aerele/ecommerce_integrations/commit/e96c20635e8b5558671d7f1452a7f7995c7dbdb2)) * validation for company links in channel conf ([9380677](https://github.com/aerele/ecommerce_integrations/commit/93806770eed549fb2c148f9194c3f9519a640788)) * verify hmac unconditionally ([7f22c45](https://github.com/aerele/ecommerce_integrations/commit/7f22c4592481a795f8f45b90125b409845dd51bf)) * Verify if enable perpetual inventory is unchecked and set unchecked if checked ([45b1184](https://github.com/aerele/ecommerce_integrations/commit/45b118466f3aaba4c8bc4af006bead3e9967e049)) * Verify if enable perpetual inventory is unchecked and set unchecked if checked ([#67](https://github.com/aerele/ecommerce_integrations/issues/67)) ([60a136b](https://github.com/aerele/ecommerce_integrations/commit/60a136bf827a2fc1e9da396ddc8392e843fd52ea)), closes [#56](https://github.com/aerele/ecommerce_integrations/issues/56) * Warning about recomputed taxes ([1bb9198](https://github.com/aerele/ecommerce_integrations/commit/1bb9198e92a39df741c1a376ed0556c6c70e5a3d)) * wh mappings should be unique ([8b9aef8](https://github.com/aerele/ecommerce_integrations/commit/8b9aef816fbec5f5b0fd5afa8468541637399a81)) * wrap resync in savepoint ([fcd7680](https://github.com/aerele/ecommerce_integrations/commit/fcd7680a656522324bd775d4a0e6ec0f233c079f)) * zenoti category (syntax issue) ([#142](https://github.com/aerele/ecommerce_integrations/issues/142)) ([acdc2a3](https://github.com/aerele/ecommerce_integrations/commit/acdc2a337b34d9e911f6dca3aa8fbd0621ef3a24)) * zenoti category api url issue ([#144](https://github.com/aerele/ecommerce_integrations/issues/144)) ([591c781](https://github.com/aerele/ecommerce_integrations/commit/591c7812edca30d55cc92b08d471b3c78444defd)) * zenoti employee syncing issue ([#148](https://github.com/aerele/ecommerce_integrations/issues/148)) ([af0d8cc](https://github.com/aerele/ecommerce_integrations/commit/af0d8cc739c7ecce43e7b3616c8134c9f79a060c)) * zenoti handling api rate limits ([#146](https://github.com/aerele/ecommerce_integrations/issues/146)) ([9920ba4](https://github.com/aerele/ecommerce_integrations/commit/9920ba41a2300ecd88663b5ae54ff0200976c2d3)) * zenoti item tax template ([#156](https://github.com/aerele/ecommerce_integrations/issues/156)) ([e6d7216](https://github.com/aerele/ecommerce_integrations/commit/e6d721691dfe1711b428408a3ace2f1b2cc4d58c)) * zenoti item_to_search dict key ([#145](https://github.com/aerele/ecommerce_integrations/issues/145)) ([36a3a6c](https://github.com/aerele/ecommerce_integrations/commit/36a3a6c3a16651ad840f5beb6f4263f8db4fd638)) * zenoti posting date time issue ([#149](https://github.com/aerele/ecommerce_integrations/issues/149)) ([651bf3c](https://github.com/aerele/ecommerce_integrations/commit/651bf3c851b8c383835113960f7f1ab4c49f17c9)) * zenoti removed syncing of item/category on syncing of Stock Reconi. ([#155](https://github.com/aerele/ecommerce_integrations/issues/155)) ([89b5bff](https://github.com/aerele/ecommerce_integrations/commit/89b5bffc5c1afb98e380952b63af9fd259b92e81)) * zenoti settings shouldn't trigger unless enabled ([e79f701](https://github.com/aerele/ecommerce_integrations/commit/e79f701a60ec148c5dc4591f6a454200d648d658)) ### Features * (Uni-commerce) generate Delivery Note and sync item fields ([#239](https://github.com/aerele/ecommerce_integrations/issues/239)) ([f474301](https://github.com/aerele/ecommerce_integrations/commit/f47430133d1341d810f66c8e50c9d27e5f9c5ec5)) * Add field "Enable Amazon" ([b04a92d](https://github.com/aerele/ecommerce_integrations/commit/b04a92dc1e234fe40c09264379b595b71c61316d)) * add field "is_old_data_migrated" ([12e4004](https://github.com/aerele/ecommerce_integrations/commit/12e4004a2d4f5cc4b82f51936d07eb0a92899321)) * add func to migrate old user data ([99db302](https://github.com/aerele/ecommerce_integrations/commit/99db302441eb1a70eb3283d4530a64afa1818739)) * add hourly job for syncing inventory ([a5d8851](https://github.com/aerele/ecommerce_integrations/commit/a5d8851b68c441c71d9e991a857aea0594599403)) * add inventory_synced_on field ([9e33e71](https://github.com/aerele/ecommerce_integrations/commit/9e33e713d5481230ce1163f05b10ec0d89c1c486)) * add invoice generation APIs ([caf00fb](https://github.com/aerele/ecommerce_integrations/commit/caf00fbe2ce1b5aa87a1101414dd2eba387812cd)) * add PDF in sales invoice ([da06f9f](https://github.com/aerele/ecommerce_integrations/commit/da06f9f5bd66f30710de654877642be26f2d9d40)) * add price, sku on creating item on shopify ([084b9b5](https://github.com/aerele/ecommerce_integrations/commit/084b9b5e6b1eaf9683c11ab61795b47239925c98)) * add required sections in unicommerce setting ([ea412d4](https://github.com/aerele/ecommerce_integrations/commit/ea412d4cebe2db9943d2071e2816ebfcaae5c17a)) * add table `Amazon Fields Map` in `Amazon SP API Settings` ([45d48ac](https://github.com/aerele/ecommerce_integrations/commit/45d48acc45af7ec6df06374d3bacc6bf2bed1252)) * Added masters for Zenoti Center, Category and some fixes ([#134](https://github.com/aerele/ecommerce_integrations/issues/134)) ([6c7b901](https://github.com/aerele/ecommerce_integrations/commit/6c7b901684deeebdd2de5242f2ee6a55dd552108)) * allow selecting group warehouses in mapping ([23d180f](https://github.com/aerele/ecommerce_integrations/commit/23d180f250e4b14e249b27052be82a133dab9851)) * Amazon SP-API Integration ([#161](https://github.com/aerele/ecommerce_integrations/issues/161)) ([16ccae4](https://github.com/aerele/ecommerce_integrations/commit/16ccae44c835838f99e6b0313623b7670610ebc7)) * amazon_methods.py ([09ac586](https://github.com/aerele/ecommerce_integrations/commit/09ac5866912480dcda1fd269652ac19ddcb1bdfc)) * amazon_methods.py ([84da303](https://github.com/aerele/ecommerce_integrations/commit/84da303c0d1351dd7de0eadb0cff578eeb4ae1b0)) * amazon_methods.py ([6cd0fbc](https://github.com/aerele/ecommerce_integrations/commit/6cd0fbcfc68e4148e2bbb20fef72340d408d239d)) * amazon_methods.py ([233e88b](https://github.com/aerele/ecommerce_integrations/commit/233e88b7537b73d7fdb774e8670145e6f0c4dcb2)) * amazon_methods.py ([71c414e](https://github.com/aerele/ecommerce_integrations/commit/71c414ea9a6ee560b206dce6a7d9c2ef86154a7c)) * amazon_sp_api_settings.get_products_details() ([f8e91d0](https://github.com/aerele/ecommerce_integrations/commit/f8e91d0735724a02cab63e6fde858c7629970588)) * amazon_sp_api_settings.py ([47822a3](https://github.com/aerele/ecommerce_integrations/commit/47822a399c5bf5516af5184d0a50b43ab3701509)) * amazon_sp_api.py ([45e2222](https://github.com/aerele/ecommerce_integrations/commit/45e22229cd1acb59443c6eeac040dc4d89c0d8a6)) * **amazon-sp:** validate credentials on save ([3257794](https://github.com/aerele/ecommerce_integrations/commit/3257794450b3556f51656b0d21ba49b020ce4e5f)) * AmazonSPAPISettings.get_order_details() ([fcec461](https://github.com/aerele/ecommerce_integrations/commit/fcec461cc8f40e4849bb8911d6ef1ed4f1db6123)) * AmazonSPAPISettings.schedule_get_order_details() ([66ed5da](https://github.com/aerele/ecommerce_integrations/commit/66ed5da1ff3471915c9ab3595e3ce6893ffd64ca)) * api client method for sales order data ([fa01cef](https://github.com/aerele/ecommerce_integrations/commit/fa01cef0bdf009385842f35e88c807f2e4acb64c)) * api method for getting inventory snapshot ([20ec7ff](https://github.com/aerele/ecommerce_integrations/commit/20ec7ff7c935ab7488b3342b1dc2f9cf91a3fe77)) * api methods for create/get shipping manifest ([56854d9](https://github.com/aerele/ecommerce_integrations/commit/56854d98f2268a66d76bfa6bb4266a01f5491010)) * Auto GRN settings and stock entry type ([caeb249](https://github.com/aerele/ecommerce_integrations/commit/caeb249208f0f1030f0b79b3c9e3d762fa7b1fc6)) * background item syncing ([e857245](https://github.com/aerele/ecommerce_integrations/commit/e8572450fbeea3e4bc57734005e1b385fc7d8cf8)) * barcode in manifest item lines ([a316b9c](https://github.com/aerele/ecommerce_integrations/commit/a316b9c5979cb95cff29d8c5c35355c921a4a8ce)) * basic doctypes reqd for shipping manifest ([6455a8f](https://github.com/aerele/ecommerce_integrations/commit/6455a8f69ea50ab87142f33a71e800bbf2763644)) * basic SO syncing ([4638d62](https://github.com/aerele/ecommerce_integrations/commit/4638d622dc2ea47acce39ef5c982ef064d67dae1)) * bulk import API and auto GRN ([#125](https://github.com/aerele/ecommerce_integrations/issues/125)) ([408d324](https://github.com/aerele/ecommerce_integrations/commit/408d324e8f615837341fbe35ed6d86812c6f6e32)) * Bulk import products from Shopify ([#133](https://github.com/aerele/ecommerce_integrations/issues/133)) ([40d7f92](https://github.com/aerele/ecommerce_integrations/commit/40d7f9220242b9265ff321bbf50dee6a3041a8d4)) * bulk retry failed jobs ([2f9bb71](https://github.com/aerele/ecommerce_integrations/commit/2f9bb7137adfe3d9f41a4773d1897821dffb234a)) * cancel fully cancelled orders ([5d1229b](https://github.com/aerele/ecommerce_integrations/commit/5d1229bb45e4de8c0fb8b343723960ca6625b94c)) * capture batch no on SO item ([2914e60](https://github.com/aerele/ecommerce_integrations/commit/2914e60307dd82f0daf374ea7354b41f80818fbd)) * capture COD charges ([7451a16](https://github.com/aerele/ecommerce_integrations/commit/7451a1622d79731f12b7138943269d2064e4bc50)) * capture COD flag and shipping method ([d4b330d](https://github.com/aerele/ecommerce_integrations/commit/d4b330d0ae2413d0bad0b75a4bd0b5345ea5caba)) * capture facility code at order creation ([7d2cb1f](https://github.com/aerele/ecommerce_integrations/commit/7d2cb1fddb0b47da107cc9d2ab73ff9143b25872)) * capture gift wrap charges ([838e13a](https://github.com/aerele/ecommerce_integrations/commit/838e13a082008414299308e7808740f01f16914a)) * capture return code on credit notes ([bf7b525](https://github.com/aerele/ecommerce_integrations/commit/bf7b525a3608f48fe6bc0386f130e9443cf7fa9b)) * capture shipping costs in tax lines ([04be1c7](https://github.com/aerele/ecommerce_integrations/commit/04be1c7e93c1b491940aa7d54c2aee69eb913edd)) * change product status on disabling item ([e81d7ad](https://github.com/aerele/ecommerce_integrations/commit/e81d7ad478600fa7958dfdf114838be1dda02c26)) * check order cancellation status at sync ([a165ddf](https://github.com/aerele/ecommerce_integrations/commit/a165ddf07d570a4abd9576eab1372c4261a3a835)) * class AmazonRepository ([56f3ac1](https://github.com/aerele/ecommerce_integrations/commit/56f3ac10a6e54c887bb325bfcbb1b37ddbe381eb)) * client method for creating invoice using shipping package ([789831d](https://github.com/aerele/ecommerce_integrations/commit/789831d455d40f1351e824109298177a3bd2b3c6)) * config for shipping per channel ([fabca88](https://github.com/aerele/ecommerce_integrations/commit/fabca88f176a0a19ce2ea203cc8128879cbaf7da)) * configurable interval for inventory sync ([#19](https://github.com/aerele/ecommerce_integrations/issues/19)) ([1f40638](https://github.com/aerele/ecommerce_integrations/commit/1f40638c827bbbee2a23d82dc47590cee84ab59b)) * connect with shopify and setup webhooks ([f979eb3](https://github.com/aerele/ecommerce_integrations/commit/f979eb3dd9e74c722ed0fb7ea3c85a776c662149)) * consider SKU in Ecommerce item ([a4d087f](https://github.com/aerele/ecommerce_integrations/commit/a4d087fd592231765ba2cbf1d2280164cacc45ce)) * consolidate quantity by wh, sku and price ([2f52956](https://github.com/aerele/ecommerce_integrations/commit/2f52956e995fe5fd287f50dbc11e17013746f607)) * cost center config in unicommerce_channel ([296a206](https://github.com/aerele/ecommerce_integrations/commit/296a20667f8a9e703515396447a1cbf2d2e41331)) * create and close manifest on unicommerce ([48a0d9a](https://github.com/aerele/ecommerce_integrations/commit/48a0d9a088c08120e81277aa9c28dcd6b1678a26)) * create credit note for fully returned orders ([ce5ce24](https://github.com/aerele/ecommerce_integrations/commit/ce5ce240a84792e81924a14e35b6022d4684f73d)) * create custom fields on enabling shopify ([b2ef575](https://github.com/aerele/ecommerce_integrations/commit/b2ef5753c4229fe11c479bef33c8483458609c0b)) * create delivery notes ([188a399](https://github.com/aerele/ecommerce_integrations/commit/188a39976aa495c8e87a98c3e860bcdabf2876c7)) * Create DocType "Amazon SP API Settings" ([5b0afe9](https://github.com/aerele/ecommerce_integrations/commit/5b0afe9db08e2f9de6b1e2083e4f4f7f8fcd3279)) * create draft credit notes for returns (RTO) ([1ed1f96](https://github.com/aerele/ecommerce_integrations/commit/1ed1f9652a687c097ba6648e8fe2654299a8b608)) * Create module "Amazon SP-API" ([c889c1d](https://github.com/aerele/ecommerce_integrations/commit/c889c1d0b6f8c5c4bc47538dd00dc0ee2d37ca17)) * create payment entry from Sales Invoice ([fb28230](https://github.com/aerele/ecommerce_integrations/commit/fb28230e4a7d8c6e88702439ffead12d5d43f84b)) * customerCode to deduplicate when available ([09c6b88](https://github.com/aerele/ecommerce_integrations/commit/09c6b8840aa9fdae848cf1739f837b2214cf68e2)) * default item group configuration ([ece0ab9](https://github.com/aerele/ecommerce_integrations/commit/ece0ab97399a5ab4f0199e8c058d059683763edd)) * Default sales tax account in shopify ([7c18889](https://github.com/aerele/ecommerce_integrations/commit/7c18889986d54b2bc39d4b47c3f4c8ff43f7b886)) * default warehouse per unicommerce channel ([435e6dc](https://github.com/aerele/ecommerce_integrations/commit/435e6dc9d4d31b4d087579404810a53427d14e48)) * DocType "Amazon SP API Settings" ([0a32750](https://github.com/aerele/ecommerce_integrations/commit/0a32750c504a3d8de397ddd5c974ca3cb457dddc)) * dynamically link SO and SI row items ([188b830](https://github.com/aerele/ecommerce_integrations/commit/188b830db6ee85e1f396044e56092825071cbf7a)) * ecommerce integration logging ([91370eb](https://github.com/aerele/ecommerce_integrations/commit/91370eb0ed8ed0d5a73cf433a3b8d65205056220)) * Ecommerce item DocType ([2e1d6f7](https://github.com/aerele/ecommerce_integrations/commit/2e1d6f73a0e9d1ab080617f1a6673de4456efd88)) * ecommerce item doctype to link erpnext items ([979d9ce](https://github.com/aerele/ecommerce_integrations/commit/979d9cec105223b638e42e0a7def66555a872d7c)) * Enable a description text for the final invoice on each tax line setting ([#15](https://github.com/aerele/ecommerce_integrations/issues/15)) ([e1099e6](https://github.com/aerele/ecommerce_integrations/commit/e1099e6c63f5bb557968d4a6a345dfaa40d851c7)) * facility code in package list on manifest ([918f42e](https://github.com/aerele/ecommerce_integrations/commit/918f42e76ca9f87f6f53a9ca6aed5609a57714d9)) * fetch orders to be synced from unicommerce ([5468f23](https://github.com/aerele/ecommerce_integrations/commit/5468f23ba75e1a77d2ebc728511d673aea568c2f)) * fetch packages from open invoices ([913a83e](https://github.com/aerele/ecommerce_integrations/commit/913a83e6520444d4192e53d7febf5a13d3489561)) * field to track manifest status on invoice ([77f0208](https://github.com/aerele/ecommerce_integrations/commit/77f02080bba16256d702af9b4f9c3eb24d371736)) * field validations and fetching for manifest ([4032474](https://github.com/aerele/ecommerce_integrations/commit/40324740c4a29943632b8026c07f6c1f89a4d3f7)) * flag to ignore status for fetching WH ([03573f1](https://github.com/aerele/ecommerce_integrations/commit/03573f1a4a5548dfdf029d68cafe256ff65ed66a)) * generate invoice from order ([324abcf](https://github.com/aerele/ecommerce_integrations/commit/324abcf2be4872aa8f44acc100ced43adda5af8a)) * get_catalog_items_instance() ([ae167e6](https://github.com/aerele/ecommerce_integrations/commit/ae167e6b2bef70a520232f2ac6288abba76ba627)) * get_reports_instance() ([dda707e](https://github.com/aerele/ecommerce_integrations/commit/dda707e09ea6bce15c069028687c06556a7f4e92)) * get/create sales invoice using api client ([a743a7a](https://github.com/aerele/ecommerce_integrations/commit/a743a7af1e319bf71491a4aa2fa4391c16706e6f)) * Handle HTTPError in "get_access_token" ([994d901](https://github.com/aerele/ecommerce_integrations/commit/994d901feb3284bc4ef8c84c4d975d26c1f1318e)) * indicator for log status in list view ([d584a15](https://github.com/aerele/ecommerce_integrations/commit/d584a15c93c13ccf428120de60fd37a24ba99071)) * Initialize App ([e6116ef](https://github.com/aerele/ecommerce_integrations/commit/e6116ef3633cebcb0fb0edf493315d5af99e8965)) * inventory sync with Unicommerce ([1278328](https://github.com/aerele/ecommerce_integrations/commit/127832865fe6184f231cdfd37c001b4bc40ff58c)) * invoice and shipping package code fields ([fe74990](https://github.com/aerele/ecommerce_integrations/commit/fe74990456244e1ae3a3aa1f13352146ce48e197)) * invoice status field on sales order ([fe7f28e](https://github.com/aerele/ecommerce_integrations/commit/fe7f28e2e32562f656f53b17541588584fc8828a)) * item variant sync ([#212](https://github.com/aerele/ecommerce_integrations/issues/212)) ([d1c6b22](https://github.com/aerele/ecommerce_integrations/commit/d1c6b22c5b2f09d69661036a3b5fa009c079e7db)) * item-product category mapping ([0340bfa](https://github.com/aerele/ecommerce_integrations/commit/0340bfaf745b788e01cd2f3f87d503ddcee691f3)) * leave comment if totals dont match ([7e10335](https://github.com/aerele/ecommerce_integrations/commit/7e10335e8d8dc37363489ef361c2f3b605c6898d)) * log clearing support ([0d8d5b5](https://github.com/aerele/ecommerce_integrations/commit/0d8d5b522c70a015b8ddf99874894d282fc2fe12)) * log generation status based on existence of invoice ([326dfa6](https://github.com/aerele/ecommerce_integrations/commit/326dfa67810821c83654ef3ea9c900e3d0239b57)) * logging inventory update status ([150d9ec](https://github.com/aerele/ecommerce_integrations/commit/150d9ecf94d185c0d29462ab245ea1c8ccc086cf)) * make invoice submission optional ([f9ee7d3](https://github.com/aerele/ecommerce_integrations/commit/f9ee7d3426b21c2b53804756b31f731733f07953)) * manual sync from UI ([0c6c667](https://github.com/aerele/ecommerce_integrations/commit/0c6c667eb878509c7226d9df10db45da1f7e317b)) * map shopify locations to ERPNext Warehouse ([f5d9f46](https://github.com/aerele/ecommerce_integrations/commit/f5d9f4672d001c20b33905d11876be33b5dcd2ea)) * match SKU to reduce duplication ([a6543a2](https://github.com/aerele/ecommerce_integrations/commit/a6543a2bedb64dbdf563965f248d02eba0afa274)) * method to update shipping package details ([2e63c98](https://github.com/aerele/ecommerce_integrations/commit/2e63c98b0ff3f12497d255591d24b53cca50cee8)) * migrate old item data ([eb4a285](https://github.com/aerele/ecommerce_integrations/commit/eb4a28532b1ccc6dd074d37e14f8a3c886c73d3c)) * migrate sync old orders feature ([c5f90e6](https://github.com/aerele/ecommerce_integrations/commit/c5f90e6cbcca259776a45388c12cca056184fb55)) * **minor:** add customer note as comment on doc ([#27](https://github.com/aerele/ecommerce_integrations/issues/27)) ([954a05d](https://github.com/aerele/ecommerce_integrations/commit/954a05dc94a78e96adb9b0cc3b4f4a11db18bfc2)) * **minor:** add raw data to doc to allow scripting ([26e2903](https://github.com/aerele/ecommerce_integrations/commit/26e290338cc63f9db7429e4026a4bcaa51af8065)) * **minor:** open logs from setting page ([c24110a](https://github.com/aerele/ecommerce_integrations/commit/c24110a2220c882f7671dbcc064d10c1fa3cd8ce)) * move multi-invoice generation to background ([f5aee54](https://github.com/aerele/ecommerce_integrations/commit/f5aee5472c265e5b9abebafd18cccf8fbdf6c074)) * new child doctype `Amazon Fields Map` ([e3d1bf2](https://github.com/aerele/ecommerce_integrations/commit/e3d1bf202aad3b8c726147923aa7cdd3054ff1b8)) * open sales order from invoice page ([341458d](https://github.com/aerele/ecommerce_integrations/commit/341458df6c1762b12e76089e7132ab72001954af)) * open unicommerce item from item form ([78ef0b4](https://github.com/aerele/ecommerce_integrations/commit/78ef0b4d1c9096872fe7d9d4a6c3cf905ab38944)) * open unicommerce manifest ([de06e6c](https://github.com/aerele/ecommerce_integrations/commit/de06e6cbf64dee102b4c2aeb74d0b91306923d86)) * open Unicommerce order URL from SO ([519f219](https://github.com/aerele/ecommerce_integrations/commit/519f219e0f06af11e82ba6dbde05c8cc60471502)) * option to auto-make payment entries ([8c1385c](https://github.com/aerele/ecommerce_integrations/commit/8c1385c4508a3294dbe120d81b7e7b90cf994148)) * option to make draft payment entries ([431a7f1](https://github.com/aerele/ecommerce_integrations/commit/431a7f1242001df1c5612c988b82431f59568b79)) * option to only sync complete orders ([e57fd90](https://github.com/aerele/ecommerce_integrations/commit/e57fd90b0e63e46f6fc89227c00f021dc143cd53)) * partial CIR returns ([ac1c9db](https://github.com/aerele/ecommerce_integrations/commit/ac1c9dbaa28361287588594ea4d232c803622e23)) * pass WH details when generating invoice ([66b80e0](https://github.com/aerele/ecommerce_integrations/commit/66b80e01172fdcb4474c2cc70abcb8b232950391)) * pick custom naming series for documents ([0c52761](https://github.com/aerele/ecommerce_integrations/commit/0c52761ac03212064aa3d2306d7f182e19010a7d)) * populate naming series fields with defaults ([2a2f03c](https://github.com/aerele/ecommerce_integrations/commit/2a2f03cbf73279ece682e8f4ed55931fa3842ab9)) * prevent GRN stock entry cancellation ([b36f5f0](https://github.com/aerele/ecommerce_integrations/commit/b36f5f0ab2d7687dbab017e9385c0735c9e6c75e)) * provision for linking variants ([4041828](https://github.com/aerele/ecommerce_integrations/commit/404182844c124864ee8f8a7f791b6eaaecd74d37)) * Reports.create_report() ([ddfd7cd](https://github.com/aerele/ecommerce_integrations/commit/ddfd7cdc615e9c14c0928cb1373fa7770a13cace)) * return created sales orders list from AmazonRepository.get_orders() ([990b804](https://github.com/aerele/ecommerce_integrations/commit/990b8045e7b6954e2f3851182944f20b26629d47)) * return products list from AmazonRepository.get_products_details() ([b24d0a8](https://github.com/aerele/ecommerce_integrations/commit/b24d0a8b9e544cc067b0adb684cf959240576b5b)) * reverse mapping for items ([0e2e806](https://github.com/aerele/ecommerce_integrations/commit/0e2e8068d258a2a5016166e62242f4460dd72c19)) * Sales Invoice sync ([b8d9acf](https://github.com/aerele/ecommerce_integrations/commit/b8d9acfcd6b2c2b6a3db7a51c3f05db407ce2032)) * scan AWB barcode to add package in manifest ([37b18e4](https://github.com/aerele/ecommerce_integrations/commit/37b18e421eec69dcfab10158e202af2d77669160)) * select a package type in Sales Order ([52921a9](https://github.com/aerele/ecommerce_integrations/commit/52921a945c7d5efa0fc9c62950cac4120f645f46)) * set scan identifier as first barcode ([bc74a90](https://github.com/aerele/ecommerce_integrations/commit/bc74a9082fce542f0c71f0ba01d587ca7ec242c0)) * set shopify_order_json for customizations ([d700e4c](https://github.com/aerele/ecommerce_integrations/commit/d700e4cbbd076c3ca55242efb35e4aa2107e721c)) * **shopify:** consolidate tax accounts in order ([4d60fb0](https://github.com/aerele/ecommerce_integrations/commit/4d60fb08db7643d525dc4b326e2a10be318359f6)) * **shopify:** resync item in bulk import ([bed3723](https://github.com/aerele/ecommerce_integrations/commit/bed37237b987fbbcb6f32c4c7ddcafa8e3597af9)) * **shopify:** sync shopify selling rate ([#221](https://github.com/aerele/ecommerce_integrations/issues/221)) ([3ae06a6](https://github.com/aerele/ecommerce_integrations/commit/3ae06a614387dd770b1ccf8c9eeff7a3c97fa71c)) * specify return warehouse for each channel ([ad2cb81](https://github.com/aerele/ecommerce_integrations/commit/ad2cb8106da00f2d82f7dbb461c482772568bae6)) * status sync for shipping packages ([c8d9d7a](https://github.com/aerele/ecommerce_integrations/commit/c8d9d7a442b81b2c3b5d5e3481676a67d6e7a670)) * stock entry hook submitting GRN ([a29ef6f](https://github.com/aerele/ecommerce_integrations/commit/a29ef6f16ed9937dd1a204eeb0d5b1dbf509ecba)) * store order item code in child table ([0228898](https://github.com/aerele/ecommerce_integrations/commit/02288985db2697e121fce03e4dbafa25b9101925)) * store tracking no. and shipping provider ([8e1dbf4](https://github.com/aerele/ecommerce_integrations/commit/8e1dbf463cee249dbe9aae8f9a682b86f2bb32a3)) * sync customer ([250d8df](https://github.com/aerele/ecommerce_integrations/commit/250d8df8afa4a9e9e68a3b737b4765e6313c8d20)) * sync customer ([8f5ecdd](https://github.com/aerele/ecommerce_integrations/commit/8f5ecddb3c7467abbbc00f9aa096d169282a0ed8)) * sync invoice labels ([a4d7054](https://github.com/aerele/ecommerce_integrations/commit/a4d7054dfbe2518b3da82289f24e0819198beae0)) * sync item from unicommerce to ERPNext ([3313bbd](https://github.com/aerele/ecommerce_integrations/commit/3313bbd3a72b89ace58b748714470e5dfea15052)) * sync item sizes ([349b999](https://github.com/aerele/ecommerce_integrations/commit/349b999a6e221eddf940d59505758b69cc8ba3d9)) * sync order status periodically ([14e5045](https://github.com/aerele/ecommerce_integrations/commit/14e5045843d73d1abab0488f79da0ef936e635d3)) * sync sales invoice ([d8f85c8](https://github.com/aerele/ecommerce_integrations/commit/d8f85c8b9083312854b109626caccbf28a46ab01)) * sync shopify sales order ([8341697](https://github.com/aerele/ecommerce_integrations/commit/8341697217e487b1b202cf29a67574b113f78528)) * sync transfer orders ([f428171](https://github.com/aerele/ecommerce_integrations/commit/f42817113711fe714fe921f0978cfd5f4b6f8606)) * sync warehouse qty ([2708917](https://github.com/aerele/ecommerce_integrations/commit/2708917079f669366f033f199e3bb4ebaf211b66)) * tax field mappings ([9200f52](https://github.com/aerele/ecommerce_integrations/commit/9200f52b15f395432c736f3732ea8ced75ca9912)) * tax fields in unicommerce channel ([bdb08bd](https://github.com/aerele/ecommerce_integrations/commit/bdb08bde8f090a5a19383d1654710567e6cffded)) * test_data.py ([3229355](https://github.com/aerele/ecommerce_integrations/commit/32293551eb0f7c16b0a44f289d205cb2648cecee)) * total discount on each item row ([bd3374a](https://github.com/aerele/ecommerce_integrations/commit/bd3374aa9ede3e06c72b381efc8df75caa1bd2f2)) * unicommerce package type doctype ([b6ba1c4](https://github.com/aerele/ecommerce_integrations/commit/b6ba1c41a5f110dd0635113bb763f655edc4e1c3)) * Unicommerce settings + authentication ([214a3fe](https://github.com/aerele/ecommerce_integrations/commit/214a3fed09f57828a8efef299c23f1c6dd367a4a)) * UnicommerceAPIClient and basic item wrapper ([9cc3c50](https://github.com/aerele/ecommerce_integrations/commit/9cc3c501760e128e5e6c87595415283eb4a0e109)) * **unicommerce:** capture batch group field ([#186](https://github.com/aerele/ecommerce_integrations/issues/186)) ([e8a932b](https://github.com/aerele/ecommerce_integrations/commit/e8a932ba3e495761b4b73ba4303db747a5853beb)) * **unicommerce:** channel config doctype ([8a5cdb7](https://github.com/aerele/ecommerce_integrations/commit/8a5cdb723e4ba44796353f74d1f47c14c9f2de67)) * **unicommerce:** func for uploading ERPNext item ([3aa554e](https://github.com/aerele/ecommerce_integrations/commit/3aa554e9ae67f0d472cf29dd9513670263ff9cce)) * **unicommerce:** Sales Invoice creation through picklist ([#240](https://github.com/aerele/ecommerce_integrations/issues/240)) ([8410c59](https://github.com/aerele/ecommerce_integrations/commit/8410c59efc89ee868033738a668853c4f7f9cf8b)) * **unicommerce:** TCS account ([#104](https://github.com/aerele/ecommerce_integrations/issues/104)) ([0e9595e](https://github.com/aerele/ecommerce_integrations/commit/0e9595e60d320126946a1b6f058e13b341a52824)) * **unicommerce:** warehouse mapping ([3170523](https://github.com/aerele/ecommerce_integrations/commit/3170523bc52ef5deee74de69e4b259c24c9fdc29)) * update fields in setting, add tax map table ([e160ae7](https://github.com/aerele/ecommerce_integrations/commit/e160ae77e6b4495cd0daaa0c0b389e61517beb6a)) * update manifest status on invoices ([5325eb6](https://github.com/aerele/ecommerce_integrations/commit/5325eb611f2522ace8524ac7601d7725422a8312)) * update package size in shipping package ([a995f77](https://github.com/aerele/ecommerce_integrations/commit/a995f774ba0e0138ad679d9a8104287e61466dea)) * update partially cancelled items ([5754ed2](https://github.com/aerele/ecommerce_integrations/commit/5754ed2a1c3a55d16c832f8fc759b86dfac13d98)) * update status of cancelled shopify orders ([b2b212c](https://github.com/aerele/ecommerce_integrations/commit/b2b212c308a0da412b1a7f26a02b15916a121a49)) * upload new single erpnext item to shopify ([8134526](https://github.com/aerele/ecommerce_integrations/commit/8134526d9db80c92f6aeb9710504790a10758e1e)) * use batch code from unicommerce ([7b44bf0](https://github.com/aerele/ecommerce_integrations/commit/7b44bf024258823c0e2c5ba0a99fbec0759b4411)) * Use ecommerce_item for mapping shopify items ([6a130f6](https://github.com/aerele/ecommerce_integrations/commit/6a130f61bc4b86b6b793db5ec7248c95f844f744)) * **ux:** open grn button ([a28fba1](https://github.com/aerele/ecommerce_integrations/commit/a28fba143ebc39276639b1719e544d91ea14a691)) * validate incoming SO items ([2ade779](https://github.com/aerele/ecommerce_integrations/commit/2ade7790a4776ee157ed67af5ed2c2dd65cb5deb)) * validate stock entry for unicommerce GRN ([741c904](https://github.com/aerele/ecommerce_integrations/commit/741c904bf731e0f3e3fc508b59d8e20f03432c9b)) * validation for max_retry_limit ([4f5cabc](https://github.com/aerele/ecommerce_integrations/commit/4f5cabc9853cc6d4d73e7654590a5c1d7dbf941e)) * warehouse allocations for generating invoice ([4b7b766](https://github.com/aerele/ecommerce_integrations/commit/4b7b7663bfeca7dcd2d24abe0aea8b4db7bef08f)) * warehouse specific addresses in SO [#118](https://github.com/aerele/ecommerce_integrations/issues/118) ([4c814f8](https://github.com/aerele/ecommerce_integrations/commit/4c814f86c3c0de31e30c6aac72944a9626bddf1e)) * Woocommerce Integraion ([7de7e92](https://github.com/aerele/ecommerce_integrations/commit/7de7e925cfcc428639a45e0da12b76ddade46957)) * zenoti category naming ([#141](https://github.com/aerele/ecommerce_integrations/issues/141)) ([33dd06a](https://github.com/aerele/ecommerce_integrations/commit/33dd06a22043a76364d5481546c35b8749df7182)) * Zenoti final changes ([df53f2d](https://github.com/aerele/ecommerce_integrations/commit/df53f2d2179d1d9b548b65348a2bd86f08d26b72)) * Zenoti initial commit ([5672fea](https://github.com/aerele/ecommerce_integrations/commit/5672fea6596a1c164eb476d441bbb755f3e82757)) ### Performance Improvements * add index on filterable custom fields ([a11b877](https://github.com/aerele/ecommerce_integrations/commit/a11b877feee46c3d849a0cc2f42b82b39656b583)) * batch and commit inventory updates to shopify ([#233](https://github.com/aerele/ecommerce_integrations/issues/233)) ([0dbdc0b](https://github.com/aerele/ecommerce_integrations/commit/0dbdc0b22086f2818aab42a5009b4d7be2ddee5c)) * check item existence before creating ([68d7730](https://github.com/aerele/ecommerce_integrations/commit/68d7730001b11a2ac4d65e422d28855fb5782d6a)) * create item price only for new item ([695b3d2](https://github.com/aerele/ecommerce_integrations/commit/695b3d2e41a881976eba9fdb6a6d85d941d141f8)) * directly call get_catalog_item() from get_products_details() ([9f76720](https://github.com/aerele/ecommerce_integrations/commit/9f76720e5bae2326e89e5c45cc902d0b827e455a)) * use cached shopify settings ([80ec3a2](https://github.com/aerele/ecommerce_integrations/commit/80ec3a2783285164dc44fc5e9eea5d9938465109)) ### Reverts * consolidation of items ([6378186](https://github.com/aerele/ecommerce_integrations/commit/6378186fd703cb821b7e40a630a64b929cc9c658)) --- ecommerce_integrations/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ecommerce_integrations/__init__.py b/ecommerce_integrations/__init__.py index 3e49871f9..5becc17c0 100644 --- a/ecommerce_integrations/__init__.py +++ b/ecommerce_integrations/__init__.py @@ -1 +1 @@ -__version__ = "1.20.1" +__version__ = "1.0.0" From f33e699b9248646419a8cb8b55faddb746f695ef Mon Sep 17 00:00:00 2001 From: ravibharathi656 Date: Wed, 8 Apr 2026 13:20:10 +0530 Subject: [PATCH 02/11] build: migrate tooling config to ruff and align boto3 --- ecommerce_integrations/__init__.py | 2 +- pyproject.toml | 68 ++++++++++++++++++++++++------ 2 files changed, 56 insertions(+), 14 deletions(-) diff --git a/ecommerce_integrations/__init__.py b/ecommerce_integrations/__init__.py index 5becc17c0..67d42d310 100644 --- a/ecommerce_integrations/__init__.py +++ b/ecommerce_integrations/__init__.py @@ -1 +1 @@ -__version__ = "1.0.0" +__version__ = "1.20.2" diff --git a/pyproject.toml b/pyproject.toml index c11448c3f..eefc96dd7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,15 +4,23 @@ description='Ecommerce integrations for ERPNext' authors = [ { name = "Frappe Technologies Pvt Ltd", email = "developers@frappe.io"} ] -requires-python = ">=3.10" +requires-python = ">=3.10,<3.15" readme = "./README.md" dynamic = ["version"] dependencies = [ "ShopifyAPI==12.4.0", # update after resolving pyjwt conflict in frappe - "boto3~=1.28.10", + "boto3~=1.34.143", ] +[project.urls] +Repository = "https://github.com/frappe/ecommerce_integrations.git" +"Bug Reports" = "https://github.com/frappe/ecommerce_integrations/issues" + +[tool.bench.frappe-dependencies] +frappe = ">=15.0.0,<16.0.0" +erpnext = ">=15.0.0,<16.0.0" + [project.license] file = "./LICENSE" @@ -20,14 +28,48 @@ file = "./LICENSE" requires = ["flit_core >=3.4,<4"] build-backend = "flit_core.buildapi" -[tool.black] -line-length = 99 - -[tool.isort] -line_length = 99 -multi_line_output = 3 -include_trailing_comma = true -force_grid_wrap = 0 -use_parentheses = true -ensure_newline_before_comments = true -indent = " " +[tool.ruff] +line-length = 110 +target-version = "py310" + +[tool.ruff.lint] +select = ["F", "E", "W", "I", "UP", "B", "RUF"] +ignore = [ + "B017", # assertRaises(Exception) - should be more specific + "B018", # useless expression, not assigned to anything + "B023", # function doesn't bind loop variable - will have last iteration's value + "B904", # raise inside except without from + "E101", # indentation contains mixed spaces and tabs + "E402", # module level import not at top of file + "E501", # line too long + "E741", # ambiguous variable name + "F401", # "unused" imports + "F403", # can't detect undefined names from * import + "F405", # can't detect undefined names from * import + "F722", # syntax error in forward type annotation + "W191", # indentation contains tabs + "RUF001", # string contains ambiguous unicode character +] +typing-modules = ["frappe.types.DF"] + +[tool.ruff.format] +quote-style = "double" +indent-style = "tab" +docstring-code-format = true + +[tool.ruff.lint.isort.sections] +"frappe" = ["frappe"] +"erpnext" = ["erpnext"] +"ecommerce_integrations" = ["ecommerce_integrations"] + +[tool.ruff.lint.isort] +section-order = [ + "future", + "standard-library", + "third-party", + "frappe", + "erpnext", + "ecommerce_integrations", + "first-party", + "local-folder", +] From f65bdee4dc83948592dea4b9ea50307d9375779c Mon Sep 17 00:00:00 2001 From: ravibharathi656 Date: Thu, 9 Apr 2026 00:19:15 +0530 Subject: [PATCH 03/11] chore: update github workflows --- .github/helper/install.sh | 10 +-- .github/helper/site_config.json | 2 +- .github/workflows/ci.yml | 32 ++++++-- .github/workflows/linters.yml | 28 +++---- .github/workflows/release.yml | 10 ++- .pre-commit-config.yaml | 39 +++++----- .../test_amazon_sp_api_settings.py | 76 ++++++++++++------- 7 files changed, 115 insertions(+), 82 deletions(-) diff --git a/.github/helper/install.sh b/.github/helper/install.sh index ce3401804..c3ebc403a 100644 --- a/.github/helper/install.sh +++ b/.github/helper/install.sh @@ -6,11 +6,11 @@ cd ~ || exit sudo apt-get update sudo apt-get -y remove mysql-server mysql-client -sudo apt-get -y install redis-server libcups2-dev mariadb-client-10.6 -qq +sudo apt-get -y install redis-server libcups2-dev mariadb-client -qq pip install frappe-bench -git clone https://github.com/frappe/frappe --branch develop --depth 1 +git clone https://github.com/frappe/frappe --branch version-15 --depth 1 bench init --skip-assets --frappe-path ~/frappe --python "$(which python)" frappe-bench mkdir ~/frappe-bench/sites/test_site @@ -33,11 +33,9 @@ sed -i 's/socketio:/# socketio:/g' Procfile sed -i 's/redis_socketio:/# redis_socketio:/g' Procfile bench get-app payments --branch develop -bench get-app erpnext --branch develop +bench get-app erpnext --branch version-15 +bench get-app ecommerce_integrations "${GITHUB_WORKSPACE}" bench start & bench --site test_site reinstall --yes - -bench get-app ecommerce_integrations "${GITHUB_WORKSPACE}" -bench --site test_site install-app ecommerce_integrations bench setup requirements --dev diff --git a/.github/helper/site_config.json b/.github/helper/site_config.json index 8c86f7372..77945c7f5 100644 --- a/.github/helper/site_config.json +++ b/.github/helper/site_config.json @@ -11,6 +11,6 @@ "root_login": "root", "root_password": "root", "host_name": "http://test_site:8000", - "install_apps": ["payments", "erpnext"], + "install_apps": ["payments", "erpnext", "ecommerce_integrations"], "throttle_user_limit": 100 } diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 366751e31..021afcc2c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,11 +3,24 @@ name: CI on: push: branches: - - develop + - main + - version-15 pull_request: branches: - - develop - main + - version-15 + paths-ignore: + - "**.css" + - "**.js" + - "**.md" + - "**.html" + - "**.csv" + schedule: + # Run everyday at midnight UTC / 5:30 IST + - cron: "0 0 * * *" + +env: + ECOMMERCE_BRANCH: ${{ github.base_ref || github.ref_name }} concurrency: group: develop-${{ github.event.number }} @@ -30,11 +43,11 @@ jobs: MARIADB_ROOT_PASSWORD: 'root' ports: - 3306:3306 - options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3 + options: --health-cmd="mariadb-admin ping" --health-interval=5s --health-timeout=2s --health-retries=3 steps: - name: Clone - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Setup Python uses: actions/setup-python@v2 @@ -51,15 +64,16 @@ jobs: run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts - name: Cache pip - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt') }} + key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml') }} restore-keys: | ${{ runner.os }}-pip- ${{ runner.os }}- + - name: Cache node modules - uses: actions/cache@v2 + uses: actions/cache@v4 env: cache-name: cache-node-modules with: @@ -74,7 +88,7 @@ jobs: id: yarn-cache-dir-path run: echo "::set-output name=dir::$(yarn cache dir)" - - uses: actions/cache@v2 + - uses: actions/cache@v4 id: yarn-cache with: path: ${{ steps.yarn-cache-dir-path.outputs.dir }} @@ -85,6 +99,8 @@ jobs: - name: Install run: | bash ${GITHUB_WORKSPACE}/.github/helper/install.sh + env: + BRANCH_TO_CLONE: ${{ env.ECOMMERCE_BRANCH }} - name: Run Tests diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml index ebb88c9ed..dbf763266 100644 --- a/.github/workflows/linters.yml +++ b/.github/workflows/linters.yml @@ -1,31 +1,31 @@ name: Linters on: - pull_request: { } + pull_request: + branches: + - main + - version-15 jobs: - linters: name: linters runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - - name: Set up Python 3.8 - uses: actions/setup-python@v2 + - name: Set up Python 3.10 + uses: actions/setup-python@v5 with: - python-version: 3.8 + python-version: '3.10' + cache: pip - name: Install and Run Pre-commit - uses: pre-commit/action@v2.0.3 + uses: pre-commit/action@v3.0.0 - name: Download Semgrep rules run: git clone --depth 1 https://github.com/frappe/semgrep-rules.git frappe-semgrep-rules - - uses: returntocorp/semgrep-action@v1 - env: - SEMGREP_TIMEOUT: 120 - with: - config: >- - r/python.lang.correctness - ./frappe-semgrep-rules/rules + - name: Run Semgrep rules + run: | + pip install semgrep==1.90.0 + semgrep ci --config ./frappe-semgrep-rules/rules --config r/python.lang.correctness diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cfe7b4bf1..05b969756 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,11 +9,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Repository - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Setup Node.js v14 - uses: actions/setup-node@v2 + persist-credentials: false + - name: Setup Node.js + uses: actions/setup-node@v4 with: node-version: 20 - name: Setup dependencies @@ -21,7 +22,8 @@ jobs: npm install @semantic-release/git @semantic-release/exec --no-save - name: Create Release env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ secrets.RELEASE_TOKEN || secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN || secrets.GITHUB_TOKEN }} GIT_AUTHOR_NAME: "Frappe PR Bot" GIT_AUTHOR_EMAIL: "developers@frappe.io" GIT_COMMITTER_NAME: "Frappe PR Bot" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6c5ded60d..1c321fc47 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,37 +1,34 @@ exclude: 'node_modules|.git' -default_stages: [commit] -fail_fast: true +default_stages: [pre-commit] +fail_fast: false repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.0.1 + rev: v4.3.0 hooks: - id: trailing-whitespace files: "ecommerce_integrations.*" + exclude: ".*json$|.*txt$|.*csv|.*md|.*svg" - id: end-of-file-fixer files: "ecommerce_integrations.*" exclude: ".*json$|.*txt$" - id: check-yaml + - id: check-merge-conflict + - id: check-ast + - id: check-json + - id: check-toml + - id: debug-statements - - repo: https://github.com/adityahase/black - rev: 364d1ddcf58eb6bad2e0b757329f06f40ea83044 + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.2.0 hooks: - - id: black - exclude: ".*setup.py$" - additional_dependencies: ['click==8.0.4'] - - - repo: https://github.com/PyCQA/isort - rev: 5.12.0 - hooks: - - id: isort - exclude: ".*setup.py$" - - - repo: https://github.com/PyCQA/flake8 - rev: 5.0.4 - hooks: - - id: flake8 - additional_dependencies: [flake8-isort] - exclude: ".*setup.py$" + - id: ruff + name: "Run ruff import sorter" + args: ["--select=I", "--fix"] + - id: ruff + name: "Run ruff linter" + - id: ruff-format + name: "Run ruff formatter" ci: autoupdate_schedule: weekly diff --git a/ecommerce_integrations/amazon/doctype/amazon_sp_api_settings/test_amazon_sp_api_settings.py b/ecommerce_integrations/amazon/doctype/amazon_sp_api_settings/test_amazon_sp_api_settings.py index 9f72487dc..6ce2da811 100644 --- a/ecommerce_integrations/amazon/doctype/amazon_sp_api_settings/test_amazon_sp_api_settings.py +++ b/ecommerce_integrations/amazon/doctype/amazon_sp_api_settings/test_amazon_sp_api_settings.py @@ -6,13 +6,15 @@ import os import time import unittest +from typing import Any, ClassVar -import frappe import responses -from frappe.exceptions import ValidationError from requests import request from requests.exceptions import HTTPError +import frappe +from frappe.exceptions import ValidationError + from ecommerce_integrations.amazon.doctype.amazon_sp_api_settings.amazon_repository import ( AmazonRepository, validate_amazon_sp_api_credentials, @@ -30,7 +32,7 @@ ) file_path = os.path.join(os.path.dirname(__file__), "test_data.json") -with open(file_path, "r") as json_file: +with open(file_path) as json_file: try: DATA = json.load(json_file) except json.decoder.JSONDecodeError as e: @@ -38,13 +40,16 @@ class TestSPAPI(SPAPI): - # Expected response after hitting the URL. - expected_response = {} + expected_response: ClassVar[dict[str, Any]] = {} @responses.activate def make_request( - self, method: str = "GET", append_to_base_uri: str = "", params: dict = None, data: dict = None, + self, + method: str = "GET", + append_to_base_uri: str = "", + params: dict | None = None, + data: dict | None = None, ) -> object: if isinstance(params, dict): params = Util.remove_empty(params) @@ -78,7 +83,7 @@ def make_request( class TestFinances(Finances, TestSPAPI): def list_financial_events_by_order_id( - self, order_id: str, max_results: int = None, next_token: str = None + self, order_id: str, max_results: int | None = None, next_token: str | None = None ) -> object: self.expected_response = DATA.get("list_financial_events_by_order_id_200") return super().list_financial_events_by_order_id(order_id, max_results, next_token) @@ -88,22 +93,22 @@ class TestOrders(Orders, TestSPAPI): def get_orders( self, created_after: str, - created_before: str = None, - last_updated_after: str = None, - last_updated_before: str = None, - order_statuses: list = None, - marketplace_ids: list = None, - fulfillment_channels: list = None, - payment_methods: list = None, - buyer_email: str = None, - seller_order_id: str = None, + created_before: str | None = None, + last_updated_after: str | None = None, + last_updated_before: str | None = None, + order_statuses: list | None = None, + marketplace_ids: list | None = None, + fulfillment_channels: list | None = None, + payment_methods: list | None = None, + buyer_email: str | None = None, + seller_order_id: str | None = None, max_results: int = 100, - easyship_shipment_statuses: list = None, - next_token: str = None, - amazon_order_ids: list = None, - actual_fulfillment_supply_source_id: str = None, + easyship_shipment_statuses: list | None = None, + next_token: str | None = None, + amazon_order_ids: list | None = None, + actual_fulfillment_supply_source_id: str | None = None, is_ispu: bool = False, - store_chain_store_id: str = None, + store_chain_store_id: str | None = None, ) -> object: self.expected_response = DATA.get("get_orders_200") return super().get_orders( @@ -126,13 +131,17 @@ def get_orders( store_chain_store_id, ) - def get_order_items(self, order_id: str, next_token: str = None) -> object: + def get_order_items(self, order_id: str, next_token: str | None = None) -> object: self.expected_response = DATA.get("get_order_items_200") return super().get_order_items(order_id, next_token) class TestCatalogItems(CatalogItems, TestSPAPI): - def get_catalog_item(self, asin: str, marketplace_id: str = None,) -> object: + def get_catalog_item( + self, + asin: str, + marketplace_id: str | None = None, + ) -> object: self.expected_response = DATA.get("get_catalog_item_200") return super().get_catalog_item(asin, marketplace_id) @@ -163,7 +172,11 @@ def get_company(): def get_warehouse(): warehouse_name = frappe.db.get_value( - "Warehouse", {"warehouse_name": "Amazon Test Warehouse",}, "warehouse_name" + "Warehouse", + { + "warehouse_name": "Amazon Test Warehouse", + }, + "warehouse_name", ) if not warehouse_name: @@ -181,12 +194,19 @@ def get_warehouse(): def get_item_group(): item_group_name = frappe.db.get_value( - "Item Group", {"item_group_name": "Amazon Test Warehouse",}, "item_group_name" + "Item Group", + { + "item_group_name": "Amazon Test Warehouse", + }, + "item_group_name", ) if not item_group_name: item_group = frappe.get_doc( - {"doctype": "Item Group", "item_group_name": "Amazon Test Warehouse",} + { + "doctype": "Item Group", + "item_group_name": "Amazon Test Warehouse", + } ) item_group.insert(ignore_permissions=True) item_group_name = item_group.item_group_name @@ -205,7 +225,7 @@ def get_item_group(): self.warehouse = get_warehouse() self.parent_item_group = get_item_group() self.price_list = "Standard Selling" - self.customer_group = "All Customer Groups" + self.customer_group = "_Testing Customer Group" self.territory = "All Territories" self.customer_type = "Individual" self.market_place_account_group = "Accounts Receivable - ATC" @@ -235,7 +255,7 @@ def __init__(self) -> None: def call_sp_api_method(self, sp_api_method, **kwargs): max_retries = self.amz_setting.max_retry_limit - for x in range(max_retries): + for _ in range(max_retries): try: result = sp_api_method(**kwargs) return result.get("payload") From 96c98c0c3caa05650fc22aa47e9aca52050def62 Mon Sep 17 00:00:00 2001 From: ravibharathi656 Date: Thu, 9 Apr 2026 09:22:58 +0530 Subject: [PATCH 04/11] ci: fix linter issues --- .../amazon_repository.py | 19 +++++--- .../amazon_sp_api_settings/amazon_sp_api.py | 36 +++++++++------- .../amazon_sp_api_settings.py | 20 +++++---- .../test_amazon_sp_api_settings.py | 2 +- .../controllers/scheduling.py | 4 +- .../ecommerce_integration_log.py | 2 +- .../doctype/ecommerce_item/ecommerce_item.py | 3 +- ecommerce_integrations/hooks.py | 4 +- .../set_default_amazon_item_fields_map.py | 12 +++++- ecommerce_integrations/shopify/connection.py | 12 +++--- ecommerce_integrations/shopify/customer.py | 7 +-- .../shopify_setting/shopify_setting.py | 11 +++-- .../shopify_setting/test_shopify_setting.py | 1 - ecommerce_integrations/shopify/fulfillment.py | 8 ++-- ecommerce_integrations/shopify/inventory.py | 5 ++- ecommerce_integrations/shopify/invoice.py | 4 +- ecommerce_integrations/shopify/order.py | 23 ++++++---- .../shopify_import_products.py | 8 +++- .../test_shopify_import_products.py | 16 ++++--- ecommerce_integrations/shopify/product.py | 43 ++++++++++++------- .../shopify/tests/test_connection.py | 5 +-- .../shopify/tests/test_product.py | 5 ++- ecommerce_integrations/shopify/tests/utils.py | 6 ++- ecommerce_integrations/shopify/utils.py | 9 ++-- .../unicommerce/api_client.py | 38 ++++++++++------ .../unicommerce/cancellation_and_returns.py | 16 +++---- .../unicommerce/customer.py | 1 - .../unicommerce/delivery_note.py | 4 +- .../test_unicommerce_settings.py | 7 ++- .../unicommerce_settings.py | 10 ++--- .../unicommerce_shipment_manifest.py | 13 ++---- ecommerce_integrations/unicommerce/grn.py | 10 ++--- .../unicommerce/inventory.py | 4 +- ecommerce_integrations/unicommerce/invoice.py | 33 +++++--------- ecommerce_integrations/unicommerce/order.py | 12 +----- .../unicommerce/pick_list.py | 4 +- ecommerce_integrations/unicommerce/product.py | 12 ++---- .../unicommerce/status_updater.py | 11 +---- .../unicommerce/tests/test_client.py | 12 +++--- .../unicommerce/tests/test_delivery_note.py | 4 +- .../unicommerce/tests/test_inventory.py | 5 ++- .../unicommerce/tests/test_invoice.py | 12 +++--- .../unicommerce/tests/test_order.py | 8 +++- .../unicommerce/tests/test_product.py | 7 ++- .../unicommerce/tests/test_status.py | 1 - ecommerce_integrations/unicommerce/utils.py | 2 +- ecommerce_integrations/utils/before_test.py | 3 +- .../zenoti_settings/zenoti_settings.py | 3 +- .../zenoti/purchase_transactions.py | 8 ++-- .../zenoti/sales_transactions.py | 8 +--- .../zenoti/stock_reconciliation.py | 11 ++--- ecommerce_integrations/zenoti/utils.py | 10 ++--- 52 files changed, 272 insertions(+), 262 deletions(-) diff --git a/ecommerce_integrations/amazon/doctype/amazon_sp_api_settings/amazon_repository.py b/ecommerce_integrations/amazon/doctype/amazon_sp_api_settings/amazon_repository.py index e5456d927..8d391b846 100644 --- a/ecommerce_integrations/amazon/doctype/amazon_sp_api_settings/amazon_repository.py +++ b/ecommerce_integrations/amazon/doctype/amazon_sp_api_settings/amazon_repository.py @@ -6,6 +6,7 @@ import urllib import dateutil + import frappe from frappe import _ @@ -62,7 +63,8 @@ def call_sp_api_method(self, sp_api_method, **kwargs) -> dict: msg = f"Error: {error}
Error Description: {errors.get(error)}" frappe.msgprint(msg, alert=True, indicator="red") frappe.log_error( - message=f"{error}: {errors.get(error)}", title=f'Method "{sp_api_method.__name__}" failed', + message=f"{error}: {errors.get(error)}", + title=f'Method "{sp_api_method.__name__}" failed', ) self.amz_setting.enable_sync = 0 @@ -271,9 +273,7 @@ def get_item_code(self, order_item) -> str: def get_order_items(self, order_id) -> list: orders = self.get_orders_instance() - order_items_payload = self.call_sp_api_method( - sp_api_method=orders.get_order_items, order_id=order_id - ) + order_items_payload = self.call_sp_api_method(sp_api_method=orders.get_order_items, order_id=order_id) final_order_items = [] warehouse = self.amz_setting.warehouse @@ -301,7 +301,9 @@ def get_order_items(self, order_id) -> list: break order_items_payload = self.call_sp_api_method( - sp_api_method=orders.get_order_items, order_id=order_id, next_token=next_token, + sp_api_method=orders.get_order_items, + order_id=order_id, + next_token=next_token, ) return final_order_items @@ -333,7 +335,8 @@ def create_customer(order) -> str: new_contact = frappe.new_doc("Contact") new_contact.first_name = order_customer_name new_contact.append( - "links", {"link_doctype": "Customer", "link_name": existing_customer_name}, + "links", + {"link_doctype": "Customer", "link_name": existing_customer_name}, ) new_contact.insert() @@ -469,7 +472,9 @@ def get_orders(self, created_after) -> list: break orders_payload = self.call_sp_api_method( - sp_api_method=orders.get_orders, created_after=created_after, next_token=next_token, + sp_api_method=orders.get_orders, + created_after=created_after, + next_token=next_token, ) return sales_orders diff --git a/ecommerce_integrations/amazon/doctype/amazon_sp_api_settings/amazon_sp_api.py b/ecommerce_integrations/amazon/doctype/amazon_sp_api_settings/amazon_sp_api.py index c2abd0de2..7158e97d0 100644 --- a/ecommerce_integrations/amazon/doctype/amazon_sp_api_settings/amazon_sp_api.py +++ b/ecommerce_integrations/amazon/doctype/amazon_sp_api_settings/amazon_sp_api.py @@ -156,9 +156,7 @@ def __call__(self, request): map(lambda H: H.lower(), request.headers.keys()), ) ) - canonical_headers = "".join( - map(lambda h: ":".join((h, request.headers[h])) + "\n", headers_to_sign) - ) + canonical_headers = "".join(map(lambda h: ":".join((h, request.headers[h])) + "\n", headers_to_sign)) signed_headers = ";".join(headers_to_sign) # Combine elements to create canonical request. @@ -208,7 +206,7 @@ def __init__(self, *args, **kwargs) -> None: class SPAPI(object): - """ Base Amazon SP-API class """ + """Base Amazon SP-API class""" # https://github.com/amzn/selling-partner-api-docs/blob/main/guides/en-US/developer-guide/SellingPartnerApiDeveloperGuide.md#connecting-to-the-selling-partner-api AUTH_URL = "https://api.amazon.com/auth/o2/token" @@ -246,9 +244,7 @@ def get_access_token(self) -> str: result = response.json() if response.status_code == 200: return result.get("access_token") - exception = SPAPIError( - error=result.get("error"), error_description=result.get("error_description") - ) + exception = SPAPIError(error=result.get("error"), error_description=result.get("error_description")) raise exception def get_auth(self) -> AWSSigV4: @@ -281,7 +277,11 @@ def get_headers(self) -> dict: return {"x-amz-access-token": self.get_access_token()} def make_request( - self, method: str = "GET", append_to_base_uri: str = "", params: dict = None, data: dict = None, + self, + method: str = "GET", + append_to_base_uri: str = "", + params: dict = None, + data: dict = None, ) -> dict: if isinstance(params, dict): params = Util.remove_empty(params) @@ -307,21 +307,21 @@ def list_to_dict(self, key: str, values: list, data: dict) -> None: class Finances(SPAPI): - """ Amazon Finances API """ + """Amazon Finances API""" BASE_URI = "/finances/v0/" def list_financial_events_by_order_id( self, order_id: str, max_results: int = None, next_token: str = None ) -> dict: - """ Returns all financial events for the specified order. """ + """Returns all financial events for the specified order.""" append_to_base_uri = f"orders/{order_id}/financialEvents" data = dict(MaxResultsPerPage=max_results, NextToken=next_token) return self.make_request(append_to_base_uri=append_to_base_uri, params=data) class Orders(SPAPI): - """ Amazon Orders API """ + """Amazon Orders API""" BASE_URI = "/orders/v0/orders" @@ -345,7 +345,7 @@ def get_orders( is_ispu: bool = False, store_chain_store_id: str = None, ) -> dict: - """ Returns orders created or updated during the time frame indicated by the specified parameters. You can also apply a range of filtering criteria to narrow the list of orders returned. If NextToken is present, that will be used to retrieve the orders instead of other criteria. """ + """Returns orders created or updated during the time frame indicated by the specified parameters. You can also apply a range of filtering criteria to narrow the list of orders returned. If NextToken is present, that will be used to retrieve the orders instead of other criteria.""" data = dict( CreatedAfter=created_after, CreatedBefore=created_before, @@ -374,19 +374,23 @@ def get_orders( return self.make_request(params=data) def get_order_items(self, order_id: str, next_token: str = None) -> dict: - """ Returns detailed order item information for the order indicated by the specified order ID. If NextToken is provided, it's used to retrieve the next page of order items. """ + """Returns detailed order item information for the order indicated by the specified order ID. If NextToken is provided, it's used to retrieve the next page of order items.""" append_to_base_uri = f"/{order_id}/orderItems" data = dict(NextToken=next_token) return self.make_request(append_to_base_uri=append_to_base_uri, params=data) class CatalogItems(SPAPI): - """ Amazon Catalog Items API """ + """Amazon Catalog Items API""" BASE_URI = "/catalog/v0" - def get_catalog_item(self, asin: str, marketplace_id: str = None,) -> dict: - """ Returns a specified item and its attributes. """ + def get_catalog_item( + self, + asin: str, + marketplace_id: str = None, + ) -> dict: + """Returns a specified item and its attributes.""" if not marketplace_id: marketplace_id = self.marketplace_id diff --git a/ecommerce_integrations/amazon/doctype/amazon_sp_api_settings/amazon_sp_api_settings.py b/ecommerce_integrations/amazon/doctype/amazon_sp_api_settings/amazon_sp_api_settings.py index 42ed32252..0765ebf27 100644 --- a/ecommerce_integrations/amazon/doctype/amazon_sp_api_settings/amazon_sp_api_settings.py +++ b/ecommerce_integrations/amazon/doctype/amazon_sp_api_settings/amazon_sp_api_settings.py @@ -96,8 +96,16 @@ def validate_credentials(self): def set_default_fields_map(self): for field_map in [ {"amazon_field": "ASIN", "item_field": "item_code", "use_to_find_item_code": 1}, - {"amazon_field": "SellerSKU", "item_field": None, "use_to_find_item_code": 0,}, - {"amazon_field": "Title", "item_field": None, "use_to_find_item_code": 0,}, + { + "amazon_field": "SellerSKU", + "item_field": None, + "use_to_find_item_code": 0, + }, + { + "amazon_field": "Title", + "item_field": None, + "use_to_find_item_code": 0, + }, ]: self.append("amazon_fields_map", field_map) @@ -124,9 +132,7 @@ def get_order_details(self): frappe.msgprint(_("Order details will be fetched in the background.")) else: - frappe.msgprint( - _("Please enable the Amazon SP API Settings {0}.").format(frappe.bold(self.name)) - ) + frappe.msgprint(_("Please enable the Amazon SP API Settings {0}.").format(frappe.bold(self.name))) # Called via a hook in every hour. @@ -167,9 +173,7 @@ def migrate_old_data(): if column_exists: item = frappe.qb.DocType("Item") - items = (frappe.qb.from_(item).select("*").where(item.amazon_item_code.notnull())).run( - as_dict=True - ) + items = (frappe.qb.from_(item).select("*").where(item.amazon_item_code.notnull())).run(as_dict=True) for item in items: if not frappe.db.exists("Ecommerce Item", {"erpnext_item_code": item.name}): diff --git a/ecommerce_integrations/amazon/doctype/amazon_sp_api_settings/test_amazon_sp_api_settings.py b/ecommerce_integrations/amazon/doctype/amazon_sp_api_settings/test_amazon_sp_api_settings.py index 6ce2da811..e90c744bd 100644 --- a/ecommerce_integrations/amazon/doctype/amazon_sp_api_settings/test_amazon_sp_api_settings.py +++ b/ecommerce_integrations/amazon/doctype/amazon_sp_api_settings/test_amazon_sp_api_settings.py @@ -225,7 +225,7 @@ def get_item_group(): self.warehouse = get_warehouse() self.parent_item_group = get_item_group() self.price_list = "Standard Selling" - self.customer_group = "_Testing Customer Group" + self.customer_group = "Individual" self.territory = "All Territories" self.customer_type = "Individual" self.market_place_account_group = "Accounts Receivable - ATC" diff --git a/ecommerce_integrations/controllers/scheduling.py b/ecommerce_integrations/controllers/scheduling.py index b97b4f81e..9b59e30ae 100644 --- a/ecommerce_integrations/controllers/scheduling.py +++ b/ecommerce_integrations/controllers/scheduling.py @@ -16,9 +16,7 @@ def need_to_run(setting, interval_field, timestamp_field) -> bool: interval = frappe.db.get_single_value(setting, interval_field, cache=True) last_run = frappe.db.get_single_value(setting, timestamp_field) - if last_run and get_datetime() < get_datetime( - add_to_date(last_run, minutes=cint(interval, default=10)) - ): + if last_run and get_datetime() < get_datetime(add_to_date(last_run, minutes=cint(interval, default=10))): return False frappe.db.set_value(setting, None, timestamp_field, now(), update_modified=False) diff --git a/ecommerce_integrations/ecommerce_integrations/doctype/ecommerce_integration_log/ecommerce_integration_log.py b/ecommerce_integrations/ecommerce_integrations/doctype/ecommerce_integration_log/ecommerce_integration_log.py index 4a9f67231..c5ed9f380 100644 --- a/ecommerce_integrations/ecommerce_integrations/doctype/ecommerce_integration_log/ecommerce_integration_log.py +++ b/ecommerce_integrations/ecommerce_integrations/doctype/ecommerce_integration_log/ecommerce_integration_log.py @@ -33,7 +33,7 @@ def _set_title(self): def clear_old_logs(days=90): table = frappe.qb.DocType("Ecommerce Integration Log") frappe.db.delete( - table, filters=((table.modified < (Now() - Interval(days=days)))) & (table.status == "Success") + table, filters=(table.modified < (Now() - Interval(days=days))) & (table.status == "Success") ) diff --git a/ecommerce_integrations/ecommerce_integrations/doctype/ecommerce_item/ecommerce_item.py b/ecommerce_integrations/ecommerce_integrations/doctype/ecommerce_item/ecommerce_item.py index 423a26829..7f087440d 100644 --- a/ecommerce_integrations/ecommerce_integrations/doctype/ecommerce_item/ecommerce_item.py +++ b/ecommerce_integrations/ecommerce_integrations/doctype/ecommerce_item/ecommerce_item.py @@ -4,11 +4,12 @@ from typing import Dict, Optional import frappe -from erpnext import get_default_company from frappe import _ from frappe.model.document import Document from frappe.utils import cstr, get_datetime, now +from erpnext import get_default_company + class EcommerceItem(Document): erpnext_item_code: str # item_code in ERPNext diff --git a/ecommerce_integrations/hooks.py b/ecommerce_integrations/hooks.py index face21c3f..c2ebc85c2 100644 --- a/ecommerce_integrations/hooks.py +++ b/ecommerce_integrations/hooks.py @@ -137,9 +137,7 @@ scheduler_events = { "all": ["ecommerce_integrations.shopify.inventory.update_inventory_on_shopify"], "daily": [], - "daily_long": [ - "ecommerce_integrations.zenoti.doctype.zenoti_settings.zenoti_settings.sync_stocks" - ], + "daily_long": ["ecommerce_integrations.zenoti.doctype.zenoti_settings.zenoti_settings.sync_stocks"], "hourly": [ "ecommerce_integrations.shopify.order.sync_old_orders", "ecommerce_integrations.amazon.doctype.amazon_sp_api_settings.amazon_sp_api_settings.schedule_get_order_details", diff --git a/ecommerce_integrations/patches/set_default_amazon_item_fields_map.py b/ecommerce_integrations/patches/set_default_amazon_item_fields_map.py index 9bf161bcf..5fcebd20d 100644 --- a/ecommerce_integrations/patches/set_default_amazon_item_fields_map.py +++ b/ecommerce_integrations/patches/set_default_amazon_item_fields_map.py @@ -6,8 +6,16 @@ def execute(): default_fields_map = [ {"amazon_field": "ASIN", "item_field": "item_code", "use_to_find_item_code": 1}, - {"amazon_field": "SellerSKU", "item_field": None, "use_to_find_item_code": 0,}, - {"amazon_field": "Title", "item_field": None, "use_to_find_item_code": 0,}, + { + "amazon_field": "SellerSKU", + "item_field": None, + "use_to_find_item_code": 0, + }, + { + "amazon_field": "Title", + "item_field": None, + "use_to_find_item_code": 0, + }, ] amz_settings = frappe.db.get_all("Amazon SP API Settings", pluck="name") diff --git a/ecommerce_integrations/shopify/connection.py b/ecommerce_integrations/shopify/connection.py index 003b51ee1..05df1697f 100644 --- a/ecommerce_integrations/shopify/connection.py +++ b/ecommerce_integrations/shopify/connection.py @@ -5,11 +5,12 @@ import json from typing import List -import frappe -from frappe import _ from shopify.resources import Webhook from shopify.session import Session +import frappe +from frappe import _ + from ecommerce_integrations.shopify.constants import ( API_VERSION, EVENT_MAPPER, @@ -24,7 +25,6 @@ def temp_shopify_session(func): @functools.wraps(func) def wrapper(*args, **kwargs): - # no auth in testing if frappe.flags.in_test: return func(*args, **kwargs) @@ -54,7 +54,9 @@ def register_webhooks(shopify_url: str, password: str) -> List[Webhook]: new_webhooks.append(webhook) else: create_shopify_log( - status="Error", response_data=webhook.to_dict(), exception=webhook.errors.full_messages(), + status="Error", + response_data=webhook.to_dict(), + exception=webhook.errors.full_messages(), ) return new_webhooks @@ -65,7 +67,6 @@ def unregister_webhooks(shopify_url: str, password: str) -> None: url = get_current_domain_name() with Session.temp(shopify_url, API_VERSION, password): - for webhook in Webhook.find(): if url in webhook.address: webhook.destroy() @@ -106,7 +107,6 @@ def store_request_data() -> None: def process_request(data, event): - # create log log = create_shopify_log(method=EVENT_MAPPER[event], request_data=data) diff --git a/ecommerce_integrations/shopify/customer.py b/ecommerce_integrations/shopify/customer.py index e4b4b9bc2..8d72a789f 100644 --- a/ecommerce_integrations/shopify/customer.py +++ b/ecommerce_integrations/shopify/customer.py @@ -85,7 +85,6 @@ def _update_existing_address( old_address.save() def create_customer_contact(self, shopify_customer: Dict[str, Any]) -> None: - if not (shopify_customer.get("first_name") and shopify_customer.get("email")): return @@ -99,9 +98,7 @@ def create_customer_contact(self, shopify_customer: Dict[str, Any]) -> None: if shopify_customer.get("email"): contact_fields["email_ids"] = [{"email_id": shopify_customer.get("email"), "is_primary": True}] - phone_no = shopify_customer.get("phone") or shopify_customer.get("default_address", {}).get( - "phone" - ) + phone_no = shopify_customer.get("phone") or shopify_customer.get("default_address", {}).get("phone") if validate_phone_number(phone_no, throw=False): contact_fields["phone_nos"] = [{"phone": phone_no, "is_primary_phone": True}] @@ -110,7 +107,7 @@ def create_customer_contact(self, shopify_customer: Dict[str, Any]) -> None: def _map_address_fields(shopify_address, customer_name, address_type, email): - """ returns dict with shopify address fields mapped to equivalent ERPNext fields""" + """returns dict with shopify address fields mapped to equivalent ERPNext fields""" address_fields = { "address_title": customer_name, "address_type": address_type, diff --git a/ecommerce_integrations/shopify/doctype/shopify_setting/shopify_setting.py b/ecommerce_integrations/shopify/doctype/shopify_setting/shopify_setting.py index 2fd82ba94..2c367f47f 100644 --- a/ecommerce_integrations/shopify/doctype/shopify_setting/shopify_setting.py +++ b/ecommerce_integrations/shopify/doctype/shopify_setting/shopify_setting.py @@ -3,12 +3,13 @@ from typing import Dict, List +from shopify.collection import PaginatedIterator +from shopify.resources import Location + import frappe from frappe import _ from frappe.custom.doctype.custom_field.custom_field import create_custom_fields from frappe.utils import get_datetime -from shopify.collection import PaginatedIterator -from shopify.resources import Location from ecommerce_integrations.controllers.setting import ( ERPNextWarehouse, @@ -99,14 +100,12 @@ def get_erpnext_warehouses(self) -> List[ERPNextWarehouse]: def get_erpnext_to_integration_wh_mapping(self) -> Dict[ERPNextWarehouse, IntegrationWarehouse]: return { - wh_map.erpnext_warehouse: wh_map.shopify_location_id - for wh_map in self.shopify_warehouse_mapping + wh_map.erpnext_warehouse: wh_map.shopify_location_id for wh_map in self.shopify_warehouse_mapping } def get_integration_to_erpnext_wh_mapping(self) -> Dict[IntegrationWarehouse, ERPNextWarehouse]: return { - wh_map.shopify_location_id: wh_map.erpnext_warehouse - for wh_map in self.shopify_warehouse_mapping + wh_map.shopify_location_id: wh_map.erpnext_warehouse for wh_map in self.shopify_warehouse_mapping } diff --git a/ecommerce_integrations/shopify/doctype/shopify_setting/test_shopify_setting.py b/ecommerce_integrations/shopify/doctype/shopify_setting/test_shopify_setting.py index 5277352cf..ab05370b9 100644 --- a/ecommerce_integrations/shopify/doctype/shopify_setting/test_shopify_setting.py +++ b/ecommerce_integrations/shopify/doctype/shopify_setting/test_shopify_setting.py @@ -29,7 +29,6 @@ def setUpClass(cls): ) def test_custom_field_creation(self): - setup_custom_fields() created_fields = frappe.get_all( diff --git a/ecommerce_integrations/shopify/fulfillment.py b/ecommerce_integrations/shopify/fulfillment.py index 97a6706eb..b61d661fa 100644 --- a/ecommerce_integrations/shopify/fulfillment.py +++ b/ecommerce_integrations/shopify/fulfillment.py @@ -1,9 +1,10 @@ from copy import deepcopy import frappe -from erpnext.selling.doctype.sales_order.sales_order import make_delivery_note from frappe.utils import cint, cstr, getdate +from erpnext.selling.doctype.sales_order.sales_order import make_delivery_note + from ecommerce_integrations.shopify.constants import ( FULLFILLMENT_ID_FIELD, ORDER_ID_FIELD, @@ -41,7 +42,6 @@ def create_delivery_note(shopify_order, setting, so): not frappe.db.get_value("Delivery Note", {FULLFILLMENT_ID_FIELD: fulfillment.get("id")}, "name") and so.docstatus == 1 ): - dn = make_delivery_note(so.name) setattr(dn, ORDER_ID_FIELD, fulfillment.get("order_id")) setattr(dn, ORDER_NUMBER_FIELD, shopify_order.get("name")) @@ -82,8 +82,6 @@ def find_matching_fullfilement_item(dn_item): for dn_item in dn_items: if shopify_item := find_matching_fullfilement_item(dn_item): - final_items.append( - dn_item.update({"qty": shopify_item.get("quantity"), "warehouse": warehouse}) - ) + final_items.append(dn_item.update({"qty": shopify_item.get("quantity"), "warehouse": warehouse})) return final_items diff --git a/ecommerce_integrations/shopify/inventory.py b/ecommerce_integrations/shopify/inventory.py index 526107dd3..d27683fd7 100644 --- a/ecommerce_integrations/shopify/inventory.py +++ b/ecommerce_integrations/shopify/inventory.py @@ -1,10 +1,11 @@ from collections import Counter -import frappe -from frappe.utils import cint, create_batch, now from pyactiveresource.connection import ResourceNotFound from shopify.resources import InventoryLevel, Variant +import frappe +from frappe.utils import cint, create_batch, now + from ecommerce_integrations.controllers.inventory import ( get_inventory_levels, update_inventory_sync_status, diff --git a/ecommerce_integrations/shopify/invoice.py b/ecommerce_integrations/shopify/invoice.py index 26afb8258..1fffd6a08 100644 --- a/ecommerce_integrations/shopify/invoice.py +++ b/ecommerce_integrations/shopify/invoice.py @@ -1,7 +1,8 @@ import frappe -from erpnext.selling.doctype.sales_order.sales_order import make_sales_invoice from frappe.utils import cint, cstr, getdate, nowdate +from erpnext.selling.doctype.sales_order.sales_order import make_sales_invoice + from ecommerce_integrations.shopify.constants import ( ORDER_ID_FIELD, ORDER_NUMBER_FIELD, @@ -37,7 +38,6 @@ def create_sales_invoice(shopify_order, setting, so): and not so.per_billed and cint(setting.sync_sales_invoice) ): - posting_date = getdate(shopify_order.get("created_at")) or nowdate() sales_invoice = make_sales_invoice(so.name, ignore_permissions=True) diff --git a/ecommerce_integrations/shopify/order.py b/ecommerce_integrations/shopify/order.py index 431c431cf..fae8d6b9c 100644 --- a/ecommerce_integrations/shopify/order.py +++ b/ecommerce_integrations/shopify/order.py @@ -1,11 +1,12 @@ import json from typing import Literal, Optional +from shopify.collection import PaginatedIterator +from shopify.resources import Order + import frappe from frappe import _ from frappe.utils import cint, cstr, flt, get_datetime, getdate, nowdate -from shopify.collection import PaginatedIterator -from shopify.resources import Order from ecommerce_integrations.shopify.connection import temp_shopify_session from ecommerce_integrations.shopify.constants import ( @@ -172,7 +173,6 @@ def get_order_items(order_items, setting, delivery_date, taxes_inclusive): def _get_item_price(line_item, taxes_inclusive: bool) -> float: - price = flt(line_item.get("price")) qty = cint(line_item.get("quantity")) @@ -206,7 +206,8 @@ def get_order_taxes(shopify_order, setting, items): "charge_type": "Actual", "account_head": get_tax_account_head(tax, charge_type="sales_tax"), "description": ( - get_tax_account_description(tax) or f"{tax.get('title')} - {tax.get('rate') * 100.0:.2f}%" + get_tax_account_description(tax) + or f"{tax.get('title')} - {tax.get('rate') * 100.0:.2f}%" ), "tax_amount": tax.get("price"), "included_in_print_rate": 0, @@ -263,7 +264,9 @@ def get_tax_account_head(tax, charge_type: Optional[Literal["shipping", "sales_t tax_title = str(tax.get("title")) tax_account = frappe.db.get_value( - "Shopify Tax Account", {"parent": SETTING_DOCTYPE, "shopify_tax": tax_title}, "tax_account", + "Shopify Tax Account", + {"parent": SETTING_DOCTYPE, "shopify_tax": tax_title}, + "tax_account", ) if not tax_account and charge_type: @@ -279,7 +282,9 @@ def get_tax_account_description(tax): tax_title = tax.get("title") tax_description = frappe.db.get_value( - "Shopify Tax Account", {"parent": SETTING_DOCTYPE, "shopify_tax": tax_title}, "tax_description", + "Shopify Tax Account", + {"parent": SETTING_DOCTYPE, "shopify_tax": tax_title}, + "tax_description", ) return tax_description @@ -317,7 +322,8 @@ def update_taxes_with_shipping_lines(taxes, shipping_lines, setting, items, taxe { "charge_type": "Actual", "account_head": get_tax_account_head(shipping_charge, charge_type="shipping"), - "description": get_tax_account_description(shipping_charge) or shipping_charge["title"], + "description": get_tax_account_description(shipping_charge) + or shipping_charge["title"], "tax_amount": shipping_charge_amount, "cost_center": setting.cost_center, } @@ -329,7 +335,8 @@ def update_taxes_with_shipping_lines(taxes, shipping_lines, setting, items, taxe "charge_type": "Actual", "account_head": get_tax_account_head(tax, charge_type="sales_tax"), "description": ( - get_tax_account_description(tax) or f"{tax.get('title')} - {tax.get('rate') * 100.0:.2f}%" + get_tax_account_description(tax) + or f"{tax.get('title')} - {tax.get('rate') * 100.0:.2f}%" ), "tax_amount": tax["price"], "cost_center": setting.cost_center, diff --git a/ecommerce_integrations/shopify/page/shopify_import_products/shopify_import_products.py b/ecommerce_integrations/shopify/page/shopify_import_products/shopify_import_products.py index ad2f94f3a..112aca075 100644 --- a/ecommerce_integrations/shopify/page/shopify_import_products/shopify_import_products.py +++ b/ecommerce_integrations/shopify/page/shopify_import_products/shopify_import_products.py @@ -1,8 +1,9 @@ from time import process_time +from shopify.resources import Product + import frappe from frappe.exceptions import UniqueValidationError -from shopify.resources import Product from ecommerce_integrations.ecommerce_integrations.doctype.ecommerce_item import ecommerce_item from ecommerce_integrations.shopify.connection import temp_shopify_session @@ -119,7 +120,10 @@ def is_synced(product): @frappe.whitelist() def import_all_products(): frappe.enqueue( - queue_sync_all_products, queue="long", job_name=SYNC_JOB_NAME, key=REALTIME_KEY, + queue_sync_all_products, + queue="long", + job_name=SYNC_JOB_NAME, + key=REALTIME_KEY, ) diff --git a/ecommerce_integrations/shopify/page/shopify_import_products/test_shopify_import_products.py b/ecommerce_integrations/shopify/page/shopify_import_products/test_shopify_import_products.py index 0f03997e2..0382f4a9f 100644 --- a/ecommerce_integrations/shopify/page/shopify_import_products/test_shopify_import_products.py +++ b/ecommerce_integrations/shopify/page/shopify_import_products/test_shopify_import_products.py @@ -1,9 +1,10 @@ import json import os -import frappe import shopify +import frappe + from ecommerce_integrations.shopify.product import ShopifyProduct from ...tests.utils import TestCase @@ -12,16 +13,13 @@ class TestShopifyImportProducts(TestCase): def __init__(self, obj): - with open( - os.path.join(os.path.dirname(__file__), "../../tests/data/bulk_products.json"), "rb" - ) as f: + with open(os.path.join(os.path.dirname(__file__), "../../tests/data/bulk_products.json"), "rb") as f: products_json = json.loads(f.read()) self._products = products_json["products"] super(TestShopifyImportProducts, self).__init__(obj) def test_import_all_products(self): - required_products = { "6808908169263": [ "40279118250031", @@ -31,7 +29,12 @@ def test_import_all_products(self): "40279118381103", "40279118413871", ], - "6808928124975": ["40279218028591", "40279218061359", "40279218094127", "40279218126895",], + "6808928124975": [ + "40279218028591", + "40279218061359", + "40279218094127", + "40279218126895", + ], "6808887689263": ["40279042883631", "40279042916399", "40279042949167"], "6808908955695": ["40279122673711", "40279122706479", "40279122739247"], "6808917737519": ["40279168221231", "40279168253999", "40279168286767"], @@ -64,7 +67,6 @@ def test_import_all_products(self): queue_sync_all_products() for product, required_variants in required_products.items(): - # has_variants is needed to avoid get_erpnext_item() # fetching the variant instead of template because of # matching integration_item_code diff --git a/ecommerce_integrations/shopify/product.py b/ecommerce_integrations/shopify/product.py index ffae32049..70b908b89 100644 --- a/ecommerce_integrations/shopify/product.py +++ b/ecommerce_integrations/shopify/product.py @@ -1,10 +1,11 @@ from typing import Optional +from shopify.resources import Product, Variant + import frappe from frappe import _, msgprint from frappe.utils import cint, cstr from frappe.utils.nestedset import get_root_of -from shopify.resources import Product, Variant from ecommerce_integrations.ecommerce_integrations.doctype.ecommerce_item import ecommerce_item from ecommerce_integrations.shopify.connection import temp_shopify_session @@ -38,7 +39,10 @@ def __init__( def is_synced(self) -> bool: return ecommerce_item.is_synced( - MODULE_NAME, integration_item_code=self.product_id, variant_id=self.variant_id, sku=self.sku, + MODULE_NAME, + integration_item_code=self.product_id, + variant_id=self.variant_id, + sku=self.sku, ) def get_erpnext_item(self): @@ -81,7 +85,8 @@ def _create_attribute(self, product_dict): "doctype": "Item Attribute", "attribute_name": attr.get("name"), "item_attribute_values": [ - {"attribute_value": attr_value, "abbr": attr_value} for attr_value in attr.get("values") + {"attribute_value": attr_value, "abbr": attr_value} + for attr_value in attr.get("values") ], } ).insert() @@ -175,7 +180,11 @@ def _create_item_variants(self, product_dict, warehouse, attributes): for i, variant_attr in enumerate(SHOPIFY_VARIANTS_ATTR_LIST): if variant.get(variant_attr): attributes[i].update( - {"attribute_value": self._get_attribute_value(variant.get(variant_attr), attributes[i])} + { + "attribute_value": self._get_attribute_value( + variant.get(variant_attr), attributes[i] + ) + } ) self._create_item(shopify_item_variant, warehouse, 0, attributes, template_item.name) @@ -263,9 +272,7 @@ def _get_item_image(product_dict): return None -def _match_sku_and_link_item( - item_dict, product_id, variant_id, variant_of=None, has_variant=False -) -> bool: +def _match_sku_and_link_item(item_dict, product_id, variant_id, variant_of=None, has_variant=False) -> bool: """Tries to match new item with existing item using Shopify SKU == item_code. Returns true if matched and linked. @@ -298,7 +305,6 @@ def _match_sku_and_link_item( def create_items_if_not_exist(order): """Using shopify order, sync all items that are not already synced.""" for item in order.get("line_items", []): - product_id = item["product_id"] variant_id = item.get("variant_id") sku = item.get("sku") @@ -401,7 +407,9 @@ def upload_erpnext_item(doc, method=None): try: variant_attributes[f"option{i+1}"] = item.attributes[i].attribute_value except IndexError: - frappe.throw(_("Shopify Error: Missing value for attribute {}").format(attr.attribute)) + frappe.throw( + _("Shopify Error: Missing value for attribute {}").format(attr.attribute) + ) product.variants.append(Variant(variant_attributes)) product.save() # push variant @@ -429,7 +437,9 @@ def upload_erpnext_item(doc, method=None): map_erpnext_item_to_shopify(shopify_product=product, erpnext_item=template_item) if not item.variant_of: update_default_variant_properties( - product, is_stock_item=template_item.is_stock_item, price=item.get(ITEM_SELLING_RATE_FIELD) + product, + is_stock_item=template_item.is_stock_item, + price=item.get(ITEM_SELLING_RATE_FIELD), ) else: variant_attributes = {"sku": item.item_code, "price": item.get(ITEM_SELLING_RATE_FIELD)} @@ -448,7 +458,9 @@ def upload_erpnext_item(doc, method=None): try: variant_attributes[f"option{i+1}"] = item.attributes[i].attribute_value except IndexError: - frappe.throw(_("Shopify Error: Missing value for attribute {}").format(attr.attribute)) + frappe.throw( + _("Shopify Error: Missing value for attribute {}").format(attr.attribute) + ) product.variants.append(Variant(variant_attributes)) is_successful = product.save() @@ -458,9 +470,7 @@ def upload_erpnext_item(doc, method=None): write_upload_log(status=is_successful, product=product, item=item, action="Updated") -def map_erpnext_variant_to_shopify_variant( - shopify_product: Product, erpnext_item, variant_attributes -): +def map_erpnext_variant_to_shopify_variant(shopify_product: Product, erpnext_item, variant_attributes): variant_product_id = frappe.db.get_value( "Ecommerce Item", {"erpnext_item_code": erpnext_item.name, "integration": MODULE_NAME}, @@ -547,7 +557,10 @@ def write_upload_log(status: bool, product: Product, item, action="Created") -> msgprint(msg, title="Note", indicator="orange") create_shopify_log( - status="Error", request_data=product.to_dict(), message=msg, method="upload_erpnext_item", + status="Error", + request_data=product.to_dict(), + message=msg, + method="upload_erpnext_item", ) else: create_shopify_log( diff --git a/ecommerce_integrations/shopify/tests/test_connection.py b/ecommerce_integrations/shopify/tests/test_connection.py index 697b88066..640256bf7 100644 --- a/ecommerce_integrations/shopify/tests/test_connection.py +++ b/ecommerce_integrations/shopify/tests/test_connection.py @@ -3,10 +3,11 @@ import unittest -import frappe from shopify.resources import Webhook from shopify.session import Session +import frappe + from ecommerce_integrations.shopify import connection from ecommerce_integrations.shopify.constants import API_VERSION, SETTING_DOCTYPE @@ -18,7 +19,6 @@ def setUpClass(cls): @unittest.skip("Can't run these tests in CI") def test_register_webhooks(self): - webhooks = connection.register_webhooks( self.setting.shopify_url, self.setting.get_password("password") ) @@ -30,7 +30,6 @@ def test_register_webhooks(self): @unittest.skip("Can't run these tests in CI") def test_unregister_webhooks(self): - connection.unregister_webhooks(self.setting.shopify_url, self.setting.get_password("password")) callback_url = connection.get_callback_url() diff --git a/ecommerce_integrations/shopify/tests/test_product.py b/ecommerce_integrations/shopify/tests/test_product.py index 31758ad7a..78d04d240 100644 --- a/ecommerce_integrations/shopify/tests/test_product.py +++ b/ecommerce_integrations/shopify/tests/test_product.py @@ -165,7 +165,10 @@ def make_item(item_code=None, properties=None): "item_name": item_code, "description": item_code, "item_group": "Products", - "attributes": [{"attribute": "Test Sync Size"}, {"attribute": "Test Sync Colour"},], + "attributes": [ + {"attribute": "Test Sync Size"}, + {"attribute": "Test Sync Colour"}, + ], "has_variants": 1, } ) diff --git a/ecommerce_integrations/shopify/tests/utils.py b/ecommerce_integrations/shopify/tests/utils.py index fd4f075ca..aa97b3e31 100644 --- a/ecommerce_integrations/shopify/tests/utils.py +++ b/ecommerce_integrations/shopify/tests/utils.py @@ -3,12 +3,14 @@ import unittest from unittest.mock import patch -import frappe import shopify -from erpnext import get_default_cost_center from pyactiveresource.activeresource import ActiveResource from pyactiveresource.testing import http_fake +import frappe + +from erpnext import get_default_cost_center + from ecommerce_integrations.shopify.constants import API_VERSION, SETTING_DOCTYPE # Following code is adapted from Shopify python api under MIT license with minor changes. diff --git a/ecommerce_integrations/shopify/utils.py b/ecommerce_integrations/shopify/utils.py index d1d55c00f..a9262ce37 100644 --- a/ecommerce_integrations/shopify/utils.py +++ b/ecommerce_integrations/shopify/utils.py @@ -26,11 +26,15 @@ def migrate_from_old_connector(payload=None, request_id=None): log = frappe.get_doc("Ecommerce Integration Log", request_id) else: log = create_shopify_log( - status="Queued", method="ecommerce_integrations.shopify.utils.migrate_from_old_connector", + status="Queued", + method="ecommerce_integrations.shopify.utils.migrate_from_old_connector", ) frappe.enqueue( - method=_migrate_items_to_ecommerce_item, queue="long", is_async=True, log=log, + method=_migrate_items_to_ecommerce_item, + queue="long", + is_async=True, + log=log, ) @@ -48,7 +52,6 @@ def ensure_old_connector_is_disabled(): def _migrate_items_to_ecommerce_item(log): - shopify_fields = ["shopify_product_id", "shopify_variant_id"] for field in shopify_fields: diff --git a/ecommerce_integrations/unicommerce/api_client.py b/ecommerce_integrations/unicommerce/api_client.py index 1b3581eb1..bb8efe721 100644 --- a/ecommerce_integrations/unicommerce/api_client.py +++ b/ecommerce_integrations/unicommerce/api_client.py @@ -1,11 +1,12 @@ import base64 from typing import Any, Dict, List, Optional, Tuple -import frappe import requests +from pytz import timezone + +import frappe from frappe import _ from frappe.utils import cint, cstr, get_datetime -from pytz import timezone from ecommerce_integrations.unicommerce.constants import SETTINGS_DOCTYPE from ecommerce_integrations.unicommerce.utils import create_unicommerce_log @@ -20,7 +21,9 @@ class UnicommerceAPIClient: """ def __init__( - self, url: Optional[str] = None, access_token: Optional[str] = None, + self, + url: Optional[str] = None, + access_token: Optional[str] = None, ): self.settings = frappe.get_doc(SETTINGS_DOCTYPE) self.base_url = url or f"https://{self.settings.unicommerce_site}" @@ -45,7 +48,6 @@ def request( files: Optional[JsonDict] = None, log_error=True, ) -> Tuple[JsonDict, bool]: - if headers is None: headers = {} @@ -143,9 +145,7 @@ def search_sales_order( # remove None values. body = {k: v for k, v in body.items() if v is not None} - search_results, status = self.request( - endpoint="/services/rest/v1/oms/saleOrder/search", body=body - ) + search_results, status = self.request(endpoint="/services/rest/v1/oms/saleOrder/search", body=body) if status and "elements" in search_results: return search_results["elements"] @@ -163,7 +163,9 @@ def get_inventory_snapshot( body = {"itemTypeSKUs": sku_codes, "updatedSinceInMinutes": updated_since} response, status = self.request( - endpoint="/services/rest/v1/inventory/inventorySnapshot/get", headers=extra_headers, body=body, + endpoint="/services/rest/v1/inventory/inventorySnapshot/get", + headers=extra_headers, + body=body, ) if status: @@ -329,7 +331,9 @@ def _positive(numbers): extra_headers = {"Facility": facility_code} return self.request( - endpoint="/services/rest/v1/oms/shippingPackage/edit", body=body, headers=extra_headers, + endpoint="/services/rest/v1/oms/shippingPackage/edit", + body=body, + headers=extra_headers, ) def get_invoice_label(self, shipping_package_code: str, facility_code: str) -> Optional[str]: @@ -371,7 +375,9 @@ def create_and_close_shipping_manifest( } response, status = self.request( - endpoint="/services/rest/v1/oms/shippingManifest/createclose", body=body, headers=extra_headers, + endpoint="/services/rest/v1/oms/shippingManifest/createclose", + body=body, + headers=extra_headers, ) if status: @@ -408,14 +414,20 @@ def search_shipping_packages( body = {k: v for k, v in body.items() if v is not None} search_results, statuses = self.request( - endpoint="/services/rest/v1/oms/shippingPackage/search", body=body, headers=extra_headers, + endpoint="/services/rest/v1/oms/shippingPackage/search", + body=body, + headers=extra_headers, ) if statuses and "elements" in search_results: return search_results["elements"] def create_import_job( - self, job_name: str, csv_filename: str, facility_code: str, job_type: str = "CREATE_NEW", + self, + job_name: str, + csv_filename: str, + facility_code: str, + job_type: str = "CREATE_NEW", ): """Create import job by specifying job name and CSV file @@ -448,7 +460,7 @@ def create_import_job( def _utc_timeformat(datetime) -> str: - """ Get datetime in UTC/GMT as required by Unicommerce""" + """Get datetime in UTC/GMT as required by Unicommerce""" return get_datetime(datetime).astimezone(timezone("UTC")).strftime("%Y-%m-%dT%H:%M:%SZ") diff --git a/ecommerce_integrations/unicommerce/cancellation_and_returns.py b/ecommerce_integrations/unicommerce/cancellation_and_returns.py index 264ea6406..4795ecae8 100644 --- a/ecommerce_integrations/unicommerce/cancellation_and_returns.py +++ b/ecommerce_integrations/unicommerce/cancellation_and_returns.py @@ -4,9 +4,10 @@ from typing import List import frappe +from frappe.utils import now_datetime + from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_sales_return from erpnext.controllers.accounts_controller import update_child_qty_rate -from frappe.utils import now_datetime from ecommerce_integrations.unicommerce.api_client import UnicommerceAPIClient from ecommerce_integrations.unicommerce.constants import ( @@ -90,9 +91,7 @@ def update_erpnext_order_items(so_data, so=None): def _delete_cancelled_items(erpnext_items, cancelled_items): - items = [ - d.as_dict() for d in erpnext_items if d.get(ORDER_ITEM_CODE_FIELD) not in cancelled_items - ] + items = [d.as_dict() for d in erpnext_items if d.get(ORDER_ITEM_CODE_FIELD) not in cancelled_items] # add `docname` same as name, required for Update Items functionality for item in items: @@ -177,7 +176,6 @@ def check_and_update_customer_initiated_returns(orders, client: UnicommerceAPICl def sync_customer_initiated_returns(so_data): - customer_returns = [r for r in so_data.get("returns", []) if r["type"] == "Customer Returned"] if not customer_returns: return @@ -194,9 +192,7 @@ def create_cir_credit_note(so_data, return_data): # Get items from SO which are returned, map SO item -> SI item with linked rows. so_item_code_map = {item.get(ORDER_ITEM_CODE_FIELD): item.name for item in so.items} - invoice_name = frappe.db.get_value( - "Sales Invoice", {ORDER_CODE_FIELD: so_data["code"], "is_return": 0} - ) + invoice_name = frappe.db.get_value("Sales Invoice", {ORDER_CODE_FIELD: so_data["code"], "is_return": 0}) si = frappe.get_doc("Sales Invoice", invoice_name) so_si_item_map = {item.so_detail: item.name for item in si.items} @@ -223,9 +219,7 @@ def _handle_partial_returns(credit_note, returned_items: List[str]) -> None: item_code_to_qty_map[item.item_code] += item.qty # remove non-returned items - credit_note.items = [ - item for item in credit_note.items if item.sales_invoice_item in returned_items - ] + credit_note.items = [item for item in credit_note.items if item.sales_invoice_item in returned_items] returned_qty_map = defaultdict(float) for item in credit_note.items: diff --git a/ecommerce_integrations/unicommerce/customer.py b/ecommerce_integrations/unicommerce/customer.py index c1b5248ae..969ba5960 100644 --- a/ecommerce_integrations/unicommerce/customer.py +++ b/ecommerce_integrations/unicommerce/customer.py @@ -93,7 +93,6 @@ def _create_customer_addresses(addresses: List[Dict[str, Any]], customer) -> Non def _create_customer_address(uni_address, address_type, customer, also_shipping=False): - country_code = uni_address.get("country") country = UNICOMMERCE_COUNTRY_MAPPING.get(country_code) diff --git a/ecommerce_integrations/unicommerce/delivery_note.py b/ecommerce_integrations/unicommerce/delivery_note.py index c578f6ab7..831005a94 100644 --- a/ecommerce_integrations/unicommerce/delivery_note.py +++ b/ecommerce_integrations/unicommerce/delivery_note.py @@ -24,9 +24,7 @@ def prepare_delivery_note(): ) for facility in enabled_facilities: - updated_packages = client.search_shipping_packages( - updated_since=minutes, facility_code=facility - ) + updated_packages = client.search_shipping_packages(updated_since=minutes, facility_code=facility) valid_packages = [p for p in updated_packages if p.get("channel") in enabled_channels] if not valid_packages: continue diff --git a/ecommerce_integrations/unicommerce/doctype/unicommerce_settings/test_unicommerce_settings.py b/ecommerce_integrations/unicommerce/doctype/unicommerce_settings/test_unicommerce_settings.py index d3fc0fc50..8a8fc9a97 100644 --- a/ecommerce_integrations/unicommerce/doctype/unicommerce_settings/test_unicommerce_settings.py +++ b/ecommerce_integrations/unicommerce/doctype/unicommerce_settings/test_unicommerce_settings.py @@ -1,8 +1,9 @@ # Copyright (c) 2021, Frappe and Contributors # See LICENSE -import frappe import responses + +import frappe from frappe.utils import now, now_datetime from ecommerce_integrations.unicommerce.constants import SETTINGS_DOCTYPE @@ -44,9 +45,7 @@ def test_failed_auth(self): """requirement: When improper credentials are provided, system throws error.""" # failure case - responses.add( - responses.GET, "https://demostaging.unicommerce.com/oauth/token", json={}, status=401 - ) + responses.add(responses.GET, "https://demostaging.unicommerce.com/oauth/token", json={}, status=401) self.assertRaises(frappe.ValidationError, self.settings.update_tokens) @responses.activate diff --git a/ecommerce_integrations/unicommerce/doctype/unicommerce_settings/unicommerce_settings.py b/ecommerce_integrations/unicommerce/doctype/unicommerce_settings/unicommerce_settings.py index cd5a46da5..b7b091430 100644 --- a/ecommerce_integrations/unicommerce/doctype/unicommerce_settings/unicommerce_settings.py +++ b/ecommerce_integrations/unicommerce/doctype/unicommerce_settings/unicommerce_settings.py @@ -3,8 +3,9 @@ from typing import Dict, List, Optional, Tuple -import frappe import requests + +import frappe from frappe import _ from frappe.custom.doctype.custom_field.custom_field import create_custom_fields from frappe.utils import add_to_date, get_datetime, now_datetime @@ -148,9 +149,7 @@ def get_erpnext_warehouses(self, all_wh=False) -> List[ERPNextWarehouse]: all_wh flag ignores enabled status. """ - return [ - wh_map.erpnext_warehouse for wh_map in self.warehouse_mapping if wh_map.enabled or all_wh - ] + return [wh_map.erpnext_warehouse for wh_map in self.warehouse_mapping if wh_map.enabled or all_wh] def get_erpnext_to_integration_wh_mapping( self, all_wh=False @@ -175,7 +174,7 @@ def get_integration_to_erpnext_wh_mapping( return {v: k for k, v in reverse_map.items()} def get_company_addresses(self, facility_code: str) -> Tuple[Optional[str], Optional[str]]: - """ Get mapped company billing and shipping addresses.""" + """Get mapped company billing and shipping addresses.""" for wh_map in self.warehouse_mapping: if wh_map.unicommerce_facility_code == facility_code: return wh_map.company_address, wh_map.dispatch_address @@ -183,7 +182,6 @@ def get_company_addresses(self, facility_code: str) -> Tuple[Optional[str], Opti def setup_custom_fields(update=True): - custom_sections = { "Sales Order": [ dict( diff --git a/ecommerce_integrations/unicommerce/doctype/unicommerce_shipment_manifest/unicommerce_shipment_manifest.py b/ecommerce_integrations/unicommerce/doctype/unicommerce_shipment_manifest/unicommerce_shipment_manifest.py index 7dc3e9df6..9955fffc6 100644 --- a/ecommerce_integrations/unicommerce/doctype/unicommerce_shipment_manifest/unicommerce_shipment_manifest.py +++ b/ecommerce_integrations/unicommerce/doctype/unicommerce_shipment_manifest/unicommerce_shipment_manifest.py @@ -152,9 +152,7 @@ def get_sales_invoice_details(sales_invoice): as_dict=True, ) - items = frappe.db.get_values( - "Sales Invoice Item", {"parent": sales_invoice}, "item_name", as_dict=True - ) + items = frappe.db.get_values("Sales Invoice Item", {"parent": sales_invoice}, "item_name", as_dict=True) unique_items = {item.item_name for item in items} si_data["item_list"] = ",".join(unique_items) @@ -163,9 +161,7 @@ def get_sales_invoice_details(sales_invoice): @frappe.whitelist() -def search_packages( - search_term: str, channel: Optional[str] = None, shipper: Optional[str] = None -): +def search_packages(search_term: str, channel: Optional[str] = None, shipper: Optional[str] = None): filters = { CHANNEL_ID_FIELD: channel, SHIPPING_PROVIDER_CODE: shipper, @@ -181,9 +177,7 @@ def search_packages( INVOICE_CODE_FIELD: search_term, } - packages = frappe.get_list( - "Sales Invoice", filters=filters, or_filters=or_filters, limit_page_length=1 - ) + packages = frappe.get_list("Sales Invoice", filters=filters, or_filters=or_filters, limit_page_length=1) if packages: return packages[0].name @@ -191,7 +185,6 @@ def search_packages( @frappe.whitelist() def get_shipping_package_list(source_name, target_doc=None): - if target_doc and isinstance(target_doc, str): target_doc = json.loads(target_doc) diff --git a/ecommerce_integrations/unicommerce/grn.py b/ecommerce_integrations/unicommerce/grn.py index a28db7c46..7f8e6d1a5 100644 --- a/ecommerce_integrations/unicommerce/grn.py +++ b/ecommerce_integrations/unicommerce/grn.py @@ -2,12 +2,13 @@ from typing import List import frappe -from erpnext.stock.doctype.batch.batch import Batch from frappe import _ from frappe.utils import cint, getdate from frappe.utils.csvutils import UnicodeWriter from frappe.utils.file_manager import save_file +from erpnext.stock.doctype.batch.batch import Batch + from ecommerce_integrations.unicommerce.api_client import UnicommerceAPIClient from ecommerce_integrations.unicommerce.constants import ( GRN_STOCK_ENTRY_TYPE, @@ -130,9 +131,7 @@ def upload_grn(doc, method=None): msg += _("Confirm the status on Import Log in Uniware.") frappe.msgprint(msg, title="Success") elif response.successful and errors: - frappe.msgprint( - "Partial success, unicommerce reported errors:
{}".format("
".join(errors)) - ) + frappe.msgprint("Partial success, unicommerce reported errors:
{}".format("
".join(errors))) def _prepare_grn_import_csv(stock_entry) -> str: @@ -191,7 +190,6 @@ def _prepare_grn_import_csv(stock_entry) -> str: def _get_csv_content(rows: List[GRNItemRow]) -> bytes: - writer = UnicodeWriter() for row in rows: @@ -208,7 +206,7 @@ def _get_unicommerce_format_date(date) -> str: def create_auto_grn_import(csv_filename: str, facility_code: str, client=None): - """ Create new import job for Auto GRN items""" + """Create new import job for Auto GRN items""" if client is None: client = UnicommerceAPIClient() resp = client.create_import_job( diff --git a/ecommerce_integrations/unicommerce/inventory.py b/ecommerce_integrations/unicommerce/inventory.py index 704c2c014..f67d9cd9d 100644 --- a/ecommerce_integrations/unicommerce/inventory.py +++ b/ecommerce_integrations/unicommerce/inventory.py @@ -32,9 +32,7 @@ def update_inventory_on_unicommerce(client=None, force=False): return # check if need to run based on configured sync frequency - if not force and not need_to_run( - SETTINGS_DOCTYPE, "inventory_sync_frequency", "last_inventory_sync" - ): + if not force and not need_to_run(SETTINGS_DOCTYPE, "inventory_sync_frequency", "last_inventory_sync"): return # get configured warehouses diff --git a/ecommerce_integrations/unicommerce/invoice.py b/ecommerce_integrations/unicommerce/invoice.py index 8f99a520f..492c3b239 100644 --- a/ecommerce_integrations/unicommerce/invoice.py +++ b/ecommerce_integrations/unicommerce/invoice.py @@ -3,13 +3,15 @@ from collections import defaultdict from typing import Any, Dict, List, NewType, Optional -import frappe import requests -from erpnext.selling.doctype.sales_order.sales_order import make_sales_invoice + +import frappe from frappe import _ from frappe.utils import cint, flt, nowdate from frappe.utils.file_manager import save_file +from erpnext.selling.doctype.sales_order.sales_order import make_sales_invoice + from ecommerce_integrations.ecommerce_integrations.doctype.ecommerce_item import ecommerce_item from ecommerce_integrations.unicommerce.api_client import UnicommerceAPIClient from ecommerce_integrations.unicommerce.constants import ( @@ -153,7 +155,6 @@ def bulk_generate_invoices( def _log_invoice_generation(sales_orders, failed_orders): - failed_orders = set(failed_orders) failed_orders.update(_get_orders_with_missing_invoice(sales_orders)) successful_orders = list(set(sales_orders) - set(failed_orders)) @@ -236,9 +237,7 @@ def _validate_wh_allocation(warehouse_allocation: WHAllocation): frappe.throw(msg) -def _generate_invoice( - client: UnicommerceAPIClient, erpnext_order, channel_config, warehouse_allocation=None -): +def _generate_invoice(client: UnicommerceAPIClient, erpnext_order, channel_config, warehouse_allocation=None): unicommerce_so_code = erpnext_order.get(ORDER_CODE_FIELD) so_data = client.get_sales_order(unicommerce_so_code) @@ -286,16 +285,12 @@ def _fetch_and_sync_invoice( """ so_data = client.get_sales_order(unicommerce_so_code) - shipping_packages = [ - d["code"] for d in so_data["shippingPackages"] if d["status"] in INVOICED_STATE - ] + shipping_packages = [d["code"] for d in so_data["shippingPackages"] if d["status"] in INVOICED_STATE] for package in shipping_packages: invoice_response = invoice_responses.get(package) or {} invoice_data = client.get_sales_invoice(package, facility_code)["invoice"] - label_pdf = fetch_label_pdf( - package, invoice_response, client=client, facility_code=facility_code - ) + label_pdf = fetch_label_pdf(package, invoice_response, client=client, facility_code=facility_code) create_sales_invoice( invoice_data, erpnext_so_code, @@ -351,9 +346,7 @@ def create_sales_invoice( shipping_package_code = si_data.get("shippingPackageCode") shipping_package_info = _get_shipping_package(so_data, shipping_package_code) or {} - tracking_no = invoice_response.get("trackingNumber") or shipping_package_info.get( - "trackingNumber" - ) + tracking_no = invoice_response.get("trackingNumber") or shipping_package_info.get("trackingNumber") shipping_provider_code = ( invoice_response.get("shippingProviderCode") or shipping_package_info.get("shippingProvider") @@ -454,7 +447,7 @@ def _get_line_items( cost_center: str, warehouse_allocations: Optional[WHAllocation] = None, ) -> List[Dict[str, Any]]: - """ Invoice items can be different and are consolidated, hence recomputing is required """ + """Invoice items can be different and are consolidated, hence recomputing is required""" si_items = [] for item in line_items: @@ -482,14 +475,11 @@ def _get_line_items( def _assign_wh_and_so_row(line_items, warehouse_allocation: List[ItemWHAlloc], so_code: str): - so_items = frappe.get_doc("Sales Order", so_code).items so_item_price_map = {d.name: d.rate for d in so_items} # remove cancelled items - warehouse_allocation = [ - d for d in warehouse_allocation if d["sales_order_row"] in so_item_price_map - ] + warehouse_allocation = [d for d in warehouse_allocation if d["sales_order_row"] in so_item_price_map] # update price for item in warehouse_allocation: @@ -510,7 +500,7 @@ def _assign_wh_and_so_row(line_items, warehouse_allocation: List[ItemWHAlloc], s def _verify_total(si, si_data) -> None: - """ Leave a comment if grand total does not match unicommerce total""" + """Leave a comment if grand total does not match unicommerce total""" if abs(si.grand_total - flt(si_data["total"])) > 0.5: si.add_comment(text=f"Invoice totals mismatch: Unicommerce reported total of {si_data['total']}") @@ -541,7 +531,6 @@ def make_payment_entry(invoice, channel_config, invoice_posting_date=None): def fetch_label_pdf(package, invoicing_response, client, facility_code): - if invoicing_response and invoicing_response.get("shippingLabelLink"): link = invoicing_response.get("shippingLabelLink") return fetch_pdf_as_base64(link) diff --git a/ecommerce_integrations/unicommerce/order.py b/ecommerce_integrations/unicommerce/order.py index 6b1ea741b..d0820573c 100644 --- a/ecommerce_integrations/unicommerce/order.py +++ b/ecommerce_integrations/unicommerce/order.py @@ -64,7 +64,6 @@ def sync_new_orders(client: UnicommerceAPIClient = None, force=False): def _get_new_orders( client: UnicommerceAPIClient, status: Optional[str] ) -> Optional[Iterator[UnicommerceOrder]]: - """Search new sales order from unicommerce.""" updated_since = 24 * 60 # minutes @@ -125,7 +124,6 @@ def _create_sales_invoices(unicommerce_order, sales_order, client: UnicommerceAP def create_order(payload: UnicommerceOrder, request_id: Optional[str] = None, client=None) -> None: - order = payload existing_so = frappe.db.get_value("Sales Order", {ORDER_CODE_FIELD: order["code"]}) @@ -174,7 +172,6 @@ def _sync_order_items(order: UnicommerceOrder, client: UnicommerceAPIClient) -> def _create_order(order: UnicommerceOrder, customer) -> None: - channel_config = frappe.get_doc("Unicommerce Channel", order["channel"]) settings = frappe.get_cached_doc(SETTINGS_DOCTYPE) @@ -221,7 +218,6 @@ def _create_order(order: UnicommerceOrder, customer) -> None: def _get_line_items( line_items, default_warehouse: Optional[str] = None, is_cancelled: bool = False ) -> List[Dict[str, Any]]: - settings = frappe.get_cached_doc(SETTINGS_DOCTYPE) wh_map = settings.get_integration_to_erpnext_wh_mapping(all_wh=True) so_items = [] @@ -330,9 +326,7 @@ def _update_package_info_on_unicommerce(so_code): shipping_packages = updated_so_data.get("shippingPackages") if not shipping_packages: - frappe.throw( - frappe._("Shipping package not present on Unicommerce for order {}").format(so.name) - ) + frappe.throw(frappe._("Shipping package not present on Unicommerce for order {}").format(so.name)) shipping_package_code = shipping_packages[0].get("code") @@ -374,9 +368,7 @@ def _get_batch_no(so_line_item) -> Optional[str]: } }, """ - batch_no = ((so_line_item.get("batchDTO") or {}).get("batchFieldsDTO") or {}).get( - "vendorBatchNumber" - ) + batch_no = ((so_line_item.get("batchDTO") or {}).get("batchFieldsDTO") or {}).get("vendorBatchNumber") if batch_no and frappe.db.exists("Batch", batch_no): return batch_no diff --git a/ecommerce_integrations/unicommerce/pick_list.py b/ecommerce_integrations/unicommerce/pick_list.py index 7a8833470..c5334fc75 100644 --- a/ecommerce_integrations/unicommerce/pick_list.py +++ b/ecommerce_integrations/unicommerce/pick_list.py @@ -21,7 +21,9 @@ def validate(self, method=None): if pl.picked_qty > pl.qty: pl.picked_qty = pl.qty - frappe.throw(_("Row {0} Picked Qty cannot be more than Sales Order Qty").format(pl.idx)) + frappe.throw( + _("Row {0} Picked Qty cannot be more than Sales Order Qty").format(pl.idx) + ) if pl.picked_qty == 0 and pl.docstatus == 1: frappe.throw( _("You have not picked {0} in row {1} . Pick the item to proceed!").format( diff --git a/ecommerce_integrations/unicommerce/product.py b/ecommerce_integrations/unicommerce/product.py index 553bc9e38..249e91057 100644 --- a/ecommerce_integrations/unicommerce/product.py +++ b/ecommerce_integrations/unicommerce/product.py @@ -1,10 +1,11 @@ from typing import List, NewType +from stdnum.ean import is_valid as validate_barcode + import frappe from frappe import _ from frappe.utils import get_url, now, to_markdown from frappe.utils.nestedset import get_root_of -from stdnum.ean import is_valid as validate_barcode from ecommerce_integrations.ecommerce_integrations.doctype.ecommerce_item import ecommerce_item from ecommerce_integrations.unicommerce.api_client import JsonDict, UnicommerceAPIClient @@ -91,7 +92,6 @@ def _create_item_dict(uni_item): _validate_create_brand(uni_item.get("brand")) for uni_field, erpnext_field in UNI_TO_ERPNEXT_ITEM_MAPPING.items(): - value = uni_item.get(uni_field) if not _validate_field(erpnext_field, value): continue @@ -284,9 +284,7 @@ def _build_unicommerce_item(item_code: ItemCode) -> JsonDict: elif barcode.barcode_type == "UPC-A": item_json["upc"] = barcode.barcode - item_json["categoryCode"] = frappe.db.get_value( - "Item Group", item.item_group, PRODUCT_CATEGORY_FIELD - ) + item_json["categoryCode"] = frappe.db.get_value("Item Group", item.item_group, PRODUCT_CATEGORY_FIELD) # append site prefix to image url item_json["imageUrl"] = get_url(item.image) item_json["maxRetailPrice"] = item.standard_rate @@ -338,6 +336,4 @@ def validate_item(doc, method=None): item_group = frappe.get_cached_doc("Item Group", item.item_group) if not item_group.get(PRODUCT_CATEGORY_FIELD): - frappe.throw( - _("Unicommerce Product category required in Item Group: {}").format(item_group.name) - ) + frappe.throw(_("Unicommerce Product category required in Item Group: {}").format(item_group.name)) diff --git a/ecommerce_integrations/unicommerce/status_updater.py b/ecommerce_integrations/unicommerce/status_updater.py index fdd85dd61..c81d79666 100644 --- a/ecommerce_integrations/unicommerce/status_updater.py +++ b/ecommerce_integrations/unicommerce/status_updater.py @@ -47,7 +47,6 @@ def update_sales_order_status(): - settings = frappe.get_cached_doc(SETTINGS_DOCTYPE) if not settings.is_enabled(): return @@ -58,9 +57,7 @@ def update_sales_order_status(): minutes = days_to_sync * 24 * 60 updated_orders = client.search_sales_order(updated_since=minutes) - enabled_channels = frappe.db.get_list( - "Unicommerce Channel", filters={"enabled": 1}, pluck="channel_id" - ) + enabled_channels = frappe.db.get_list("Unicommerce Channel", filters={"enabled": 1}, pluck="channel_id") valid_orders = [order for order in updated_orders if order.get("channel") in enabled_channels] if valid_orders: _update_order_status_fields(valid_orders) @@ -79,7 +76,6 @@ def update_sales_order_status(): def _update_order_status_fields(orders): - order_status_map = {d["code"]: d["status"] for d in orders} order_codes = list(order_status_map.keys()) @@ -121,9 +117,7 @@ def update_shipping_package_status(): # find all Facilities enabled_facilities = list(settings.get_integration_to_erpnext_wh_mapping().keys()) - enabled_channels = frappe.db.get_list( - "Unicommerce Channel", filters={"enabled": 1}, pluck="channel_id" - ) + enabled_channels = frappe.db.get_list("Unicommerce Channel", filters={"enabled": 1}, pluck="channel_id") for facility in enabled_facilities: updated_packages = client.search_shipping_packages(updated_since=minutes, facility_code=facility) @@ -140,7 +134,6 @@ def update_shipping_package_status(): def _update_package_status_fields(packages): - package_status_map = {d["code"]: d["status"] for d in packages} package_codes = list(package_status_map.keys()) diff --git a/ecommerce_integrations/unicommerce/tests/test_client.py b/ecommerce_integrations/unicommerce/tests/test_client.py index 50f6c5f34..40671c69f 100644 --- a/ecommerce_integrations/unicommerce/tests/test_client.py +++ b/ecommerce_integrations/unicommerce/tests/test_client.py @@ -2,10 +2,11 @@ import json from unittest.mock import patch -import frappe import responses from responses.matchers import query_param_matcher +import frappe + from ecommerce_integrations.unicommerce.api_client import UnicommerceAPIClient from ecommerce_integrations.unicommerce.tests.utils import TestCase @@ -131,7 +132,6 @@ def test_create_update_item(self): self.assertTrue(response["successful"]) def test_bulk_inventory_sync(self): - expected_body = { "inventoryAdjustments": [ { @@ -289,9 +289,7 @@ def test_update_shipping_package(self): ], ) - self.client.update_shipping_package( - "SP_CODE", "TEST", "DEFAULT", length=100, width=200, height=300 - ) + self.client.update_shipping_package("SP_CODE", "TEST", "DEFAULT", length=100, width=200, height=300) self.assert_last_request_headers("Facility", "TEST") def test_get_invoice_label(self): @@ -323,7 +321,9 @@ def test_bulk_import(self): responses.POST, "https://demostaging.unicommerce.com/services/rest/v1/data/import/job/create", status=200, - match=[query_param_matcher({"name": "Auto GRN Items", "importOption": "CREATE_NEW"}),], + match=[ + query_param_matcher({"name": "Auto GRN Items", "importOption": "CREATE_NEW"}), + ], json={"successful": True}, ) diff --git a/ecommerce_integrations/unicommerce/tests/test_delivery_note.py b/ecommerce_integrations/unicommerce/tests/test_delivery_note.py index 205ad8e10..9cdbdc421 100644 --- a/ecommerce_integrations/unicommerce/tests/test_delivery_note.py +++ b/ecommerce_integrations/unicommerce/tests/test_delivery_note.py @@ -1,8 +1,10 @@ import base64 import unittest -import frappe import responses + +import frappe + from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry from ecommerce_integrations.unicommerce.constants import ( diff --git a/ecommerce_integrations/unicommerce/tests/test_inventory.py b/ecommerce_integrations/unicommerce/tests/test_inventory.py index b931c9f35..c92dbe5eb 100644 --- a/ecommerce_integrations/unicommerce/tests/test_inventory.py +++ b/ecommerce_integrations/unicommerce/tests/test_inventory.py @@ -1,7 +1,9 @@ from unittest.mock import patch -import frappe import responses + +import frappe + from erpnext.stock.doctype.item.test_item import make_item from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry from erpnext.stock.utils import get_stock_balance @@ -93,7 +95,6 @@ def test_inventory_sync(self): def make_ecommerce_item(item_code): - if ecommerce_item.is_synced(MODULE_NAME, item_code): return diff --git a/ecommerce_integrations/unicommerce/tests/test_invoice.py b/ecommerce_integrations/unicommerce/tests/test_invoice.py index ac80d9fa0..cb9931784 100644 --- a/ecommerce_integrations/unicommerce/tests/test_invoice.py +++ b/ecommerce_integrations/unicommerce/tests/test_invoice.py @@ -1,8 +1,10 @@ import base64 import unittest -import frappe import responses + +import frappe + from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry from ecommerce_integrations.unicommerce.constants import ( @@ -55,9 +57,7 @@ def test_create_invoice(self): attachments = frappe.get_all( "File", fields=["name", "file_name"], filters={"attached_to_name": si.name} ) - self.assertGreaterEqual( - len(attachments), 2, msg=f"Expected 2 attachments, found: {str(attachments)}" - ) + self.assertGreaterEqual(len(attachments), 2, msg=f"Expected 2 attachments, found: {str(attachments)}") def test_end_to_end_invoice_generation(self): """Full invoice generation test with mocked responses.""" @@ -113,6 +113,4 @@ def test_end_to_end_invoice_generation(self): attachments = frappe.get_all( "File", fields=["name", "file_name"], filters={"attached_to_name": si.name} ) - self.assertGreaterEqual( - len(attachments), 2, msg=f"Expected 2 attachments, found: {str(attachments)}" - ) + self.assertGreaterEqual(len(attachments), 2, msg=f"Expected 2 attachments, found: {str(attachments)}") diff --git a/ecommerce_integrations/unicommerce/tests/test_order.py b/ecommerce_integrations/unicommerce/tests/test_order.py index 20b3071fd..1a78cadf7 100644 --- a/ecommerce_integrations/unicommerce/tests/test_order.py +++ b/ecommerce_integrations/unicommerce/tests/test_order.py @@ -26,7 +26,13 @@ def setUpClass(cls): def test_validate_item_list(self): order_files = ["order-SO5905", "order-SO5906", "order-SO5907"] - items_list = [{"MC-100", "TITANIUM_WATCH"}, {"MC-100",}, {"MC-100", "TITANIUM_WATCH"}] + items_list = [ + {"MC-100", "TITANIUM_WATCH"}, + { + "MC-100", + }, + {"MC-100", "TITANIUM_WATCH"}, + ] for order_file, items in zip(order_files, items_list): order = self.load_fixture(order_file)["saleOrderDTO"] diff --git a/ecommerce_integrations/unicommerce/tests/test_product.py b/ecommerce_integrations/unicommerce/tests/test_product.py index 96c72e5b5..380488a13 100644 --- a/ecommerce_integrations/unicommerce/tests/test_product.py +++ b/ecommerce_integrations/unicommerce/tests/test_product.py @@ -1,6 +1,7 @@ -import frappe import responses +import frappe + from ecommerce_integrations.ecommerce_integrations.doctype.ecommerce_item import ecommerce_item from ecommerce_integrations.unicommerce.constants import MODULE_NAME from ecommerce_integrations.unicommerce.product import ( @@ -28,9 +29,7 @@ def test_import_missing_item_raises_error(self): json=self.load_fixture("missing_item"), match=[responses.json_params_matcher({"skuCode": "MISSING"})], ) - self.assertRaises( - frappe.ValidationError, import_product_from_unicommerce, "MISSING", self.client - ) + self.assertRaises(frappe.ValidationError, import_product_from_unicommerce, "MISSING", self.client) log = frappe.get_last_doc("Ecommerce Integration Log", filters={"integration": "unicommerce"}) self.assertTrue("Failed to import" in log.message, "Logging for missing item not working") diff --git a/ecommerce_integrations/unicommerce/tests/test_status.py b/ecommerce_integrations/unicommerce/tests/test_status.py index 6d61564c5..8cdc6581c 100644 --- a/ecommerce_integrations/unicommerce/tests/test_status.py +++ b/ecommerce_integrations/unicommerce/tests/test_status.py @@ -15,7 +15,6 @@ def test_serialization(self): _serialize_items([si_item.as_dict()]) def test_delete_cancelled_items(self): - item1 = frappe.new_doc("Sales Order Item").update({ORDER_ITEM_CODE_FIELD: "cancelled"}) item2 = frappe.new_doc("Sales Order Item").update({ORDER_ITEM_CODE_FIELD: "not cancelled"}) diff --git a/ecommerce_integrations/unicommerce/utils.py b/ecommerce_integrations/unicommerce/utils.py index 966c53eea..cd4d699a6 100644 --- a/ecommerce_integrations/unicommerce/utils.py +++ b/ecommerce_integrations/unicommerce/utils.py @@ -48,7 +48,7 @@ def force_sync(document) -> None: def get_unicommerce_date(timestamp: int) -> datetime.date: - """ Convert unicommerce ms timestamp to datetime.""" + """Convert unicommerce ms timestamp to datetime.""" return datetime.date.fromtimestamp(timestamp // 1000) diff --git a/ecommerce_integrations/utils/before_test.py b/ecommerce_integrations/utils/before_test.py index 0622afa6b..e1d319d12 100644 --- a/ecommerce_integrations/utils/before_test.py +++ b/ecommerce_integrations/utils/before_test.py @@ -1,7 +1,8 @@ import frappe -from erpnext.setup.utils import enable_all_roles_and_domains from frappe.utils import now_datetime +from erpnext.setup.utils import enable_all_roles_and_domains + def before_tests(): frappe.clear_cache() diff --git a/ecommerce_integrations/zenoti/doctype/zenoti_settings/zenoti_settings.py b/ecommerce_integrations/zenoti/doctype/zenoti_settings/zenoti_settings.py index afc23434d..da33db2d5 100644 --- a/ecommerce_integrations/zenoti/doctype/zenoti_settings/zenoti_settings.py +++ b/ecommerce_integrations/zenoti/doctype/zenoti_settings/zenoti_settings.py @@ -1,8 +1,9 @@ # Copyright (c) 2021, Frappe and contributors # For license information, please see LICENSE -import frappe import requests + +import frappe from frappe import _ from frappe.custom.doctype.custom_field.custom_field import create_custom_fields from frappe.model.document import Document diff --git a/ecommerce_integrations/zenoti/purchase_transactions.py b/ecommerce_integrations/zenoti/purchase_transactions.py index e49840c8e..c4a66e0e0 100644 --- a/ecommerce_integrations/zenoti/purchase_transactions.py +++ b/ecommerce_integrations/zenoti/purchase_transactions.py @@ -28,9 +28,7 @@ def get_list_of_purchase_orders_for_center(center, date=None): start_date = add_to_date(end_date, days=-1) route = "inventory/purchase_orders?center_id=" url_end = "&show_delivery_details=true&date_criteria=1&status=-1" - full_url = ( - api_url + route + center + "&start_date=" + start_date + "&end_date=" + end_date + url_end - ) + full_url = api_url + route + center + "&start_date=" + start_date + "&end_date=" + end_date + url_end all_orders = make_api_call(full_url) return all_orders @@ -208,7 +206,9 @@ def add_items(doc, item_data): invoice_item[key] = value if key == "item_code": item_code = frappe.db.get_value( - "Item", {"zenoti_item_code": item["item_code"], "item_name": item["item_name"]}, "item_code" + "Item", + {"zenoti_item_code": item["item_code"], "item_name": item["item_name"]}, + "item_code", ) invoice_item["item_code"] = item_code diff --git a/ecommerce_integrations/zenoti/sales_transactions.py b/ecommerce_integrations/zenoti/sales_transactions.py index 5fe131b1e..a1ae3cf6d 100644 --- a/ecommerce_integrations/zenoti/sales_transactions.py +++ b/ecommerce_integrations/zenoti/sales_transactions.py @@ -250,9 +250,7 @@ def process_sales_line_items(invoice, cost_center, center): if len(item_err_msg_list): item_err_msg = "\n".join(err for err in item_err_msg_list) err_msg_list.append(item_err_msg) - emp_err_msg = check_for_employee( - line_item["employee"]["name"], line_item["employee"]["code"], center - ) + emp_err_msg = check_for_employee(line_item["employee"]["name"], line_item["employee"]["code"], center) if emp_err_msg: err_msg_list.append(emp_err_msg) sold_by = frappe.db.get_value( @@ -449,9 +447,7 @@ def make_invoice(invoice_details): doc.posting_time = invoice_details["posting_time"] doc.due_date = invoice_details["posting_date"] doc.cost_center = invoice_details["cost_center"] - doc.selling_price_list = frappe.db.get_single_value( - "Zenoti Settings", "default_selling_price_list" - ) + doc.selling_price_list = frappe.db.get_single_value("Zenoti Settings", "default_selling_price_list") doc.set_warehouse = invoice_details["set_warehouse"] doc.update_stock = 1 doc.rounding_adjustment = invoice_details["rounding_adjustment"] diff --git a/ecommerce_integrations/zenoti/stock_reconciliation.py b/ecommerce_integrations/zenoti/stock_reconciliation.py index 249374f71..e97c3a40b 100644 --- a/ecommerce_integrations/zenoti/stock_reconciliation.py +++ b/ecommerce_integrations/zenoti/stock_reconciliation.py @@ -1,8 +1,9 @@ import frappe -from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import get_stock_balance_for from frappe import _ from frappe.utils import flt, now +from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import get_stock_balance_for + from ecommerce_integrations.zenoti.utils import api_url, check_for_item, make_api_call @@ -10,9 +11,7 @@ def process_stock_reconciliation(center, error_logs, date=None): if not date: date = now() list_for_entry = [] - stock_quantities_of_products_in_a_center = retrieve_stock_quantities_of_products( - center.name, date - ) + stock_quantities_of_products_in_a_center = retrieve_stock_quantities_of_products(center.name, date) if stock_quantities_of_products_in_a_center: cost_center = center.get("erpnext_cost_center") if not cost_center: @@ -81,7 +80,9 @@ def add_items_to_reconcile(doc, list_for_entry): invoice_item[key] = value if key == "item_code": item_code = frappe.db.get_value( - "Item", {"zenoti_item_code": item["item_code"], "item_name": item["item_name"]}, "item_code" + "Item", + {"zenoti_item_code": item["item_code"], "item_name": item["item_name"]}, + "item_code", ) invoice_item["item_code"] = item_code doc.append("items", invoice_item) diff --git a/ecommerce_integrations/zenoti/utils.py b/ecommerce_integrations/zenoti/utils.py index 16b696927..560795d45 100644 --- a/ecommerce_integrations/zenoti/utils.py +++ b/ecommerce_integrations/zenoti/utils.py @@ -1,12 +1,14 @@ import json import math -import frappe import requests -from erpnext.controllers.accounts_controller import add_taxes_from_tax_template + +import frappe from frappe import _ from frappe.utils import cint, flt +from erpnext.controllers.accounts_controller import add_taxes_from_tax_template + api_url = "https://api.zenoti.com/v1/" item_type = { @@ -80,9 +82,7 @@ def check_for_item(list_of_items, item_group, center=None): def make_item(item, item_group, center=None): item_details, center = get_item_details(item, item_group, center) if not item_details: - err_msg = _("Details for Item {0} does not exist in Zenoti").format( - frappe.bold(item["item_name"]) - ) + err_msg = _("Details for Item {0} does not exist in Zenoti").format(frappe.bold(item["item_name"])) return err_msg create_item(item, item_details, item_group, center) From 7c89e9d89c687062f5294a00dc5c0e9158b8e1e6 Mon Sep 17 00:00:00 2001 From: ravibharathi656 Date: Wed, 15 Apr 2026 15:12:35 +0530 Subject: [PATCH 05/11] fix: address ruff checks --- .../amazon_repository.py | 6 +- .../amazon_sp_api_settings/amazon_sp_api.py | 42 ++++++------ .../amazon_sp_api_settings.py | 2 +- .../controllers/customer.py | 6 +- .../controllers/inventory.py | 10 ++- ecommerce_integrations/controllers/setting.py | 8 +-- .../doctype/ecommerce_item/ecommerce_item.py | 26 ++++---- ecommerce_integrations/shopify/connection.py | 3 +- ecommerce_integrations/shopify/customer.py | 14 ++-- .../shopify_setting/shopify_setting.py | 8 +-- ecommerce_integrations/shopify/order.py | 4 +- .../shopify_import_products.py | 4 +- .../test_shopify_import_products.py | 4 +- ecommerce_integrations/shopify/product.py | 12 ++-- ecommerce_integrations/shopify/tests/utils.py | 8 +-- ecommerce_integrations/shopify/utils.py | 5 +- .../unicommerce/api_client.py | 64 +++++++++---------- .../unicommerce/cancellation_and_returns.py | 9 ++- .../unicommerce/customer.py | 4 +- .../unicommerce_settings.py | 10 ++- .../unicommerce_shipment_manifest.py | 5 +- ecommerce_integrations/unicommerce/grn.py | 5 +- .../unicommerce/inventory.py | 5 +- ecommerce_integrations/unicommerce/invoice.py | 37 ++++++----- ecommerce_integrations/unicommerce/order.py | 23 ++++--- ecommerce_integrations/unicommerce/product.py | 12 ++-- .../unicommerce/tests/test_invoice.py | 4 +- .../unicommerce/tests/test_order.py | 2 +- .../unicommerce/tests/utils.py | 3 +- .../zenoti/stock_reconciliation.py | 2 +- ecommerce_integrations/zenoti/utils.py | 2 +- 31 files changed, 165 insertions(+), 184 deletions(-) diff --git a/ecommerce_integrations/amazon/doctype/amazon_sp_api_settings/amazon_repository.py b/ecommerce_integrations/amazon/doctype/amazon_sp_api_settings/amazon_repository.py index 8d391b846..503808e2c 100644 --- a/ecommerce_integrations/amazon/doctype/amazon_sp_api_settings/amazon_repository.py +++ b/ecommerce_integrations/amazon/doctype/amazon_sp_api_settings/amazon_repository.py @@ -48,7 +48,7 @@ def call_sp_api_method(self, sp_api_method, **kwargs) -> dict: errors = {} max_retries = self.amz_setting.max_retry_limit - for x in range(max_retries): + for _x in range(max_retries): try: result = sp_api_method(**kwargs) return result.get("payload") @@ -78,11 +78,11 @@ def get_finances_instance(self) -> Finances: return Finances(**self.instance_params) def get_account(self, name) -> str: - account_name = frappe.db.get_value("Account", {"account_name": "Amazon {0}".format(name)}) + account_name = frappe.db.get_value("Account", {"account_name": f"Amazon {name}"}) if not account_name: new_account = frappe.new_doc("Account") - new_account.account_name = "Amazon {0}".format(name) + new_account.account_name = f"Amazon {name}" new_account.company = self.amz_setting.company new_account.parent_account = self.amz_setting.market_place_account_group new_account.insert(ignore_permissions=True) diff --git a/ecommerce_integrations/amazon/doctype/amazon_sp_api_settings/amazon_sp_api.py b/ecommerce_integrations/amazon/doctype/amazon_sp_api_settings/amazon_sp_api.py index 7158e97d0..d277389ae 100644 --- a/ecommerce_integrations/amazon/doctype/amazon_sp_api_settings/amazon_sp_api.py +++ b/ecommerce_integrations/amazon/doctype/amazon_sp_api_settings/amazon_sp_api.py @@ -136,7 +136,7 @@ def __call__(self, request): # Create payload hash (hash of the request body content). if request.method == "GET": - payload_hash = hashlib.sha256(("").encode("utf-8")).hexdigest() + payload_hash = hashlib.sha256(b"").hexdigest() else: if request.body: if isinstance(request.body, bytes): @@ -205,7 +205,7 @@ def __init__(self, *args, **kwargs) -> None: super().__init__(*args) -class SPAPI(object): +class SPAPI: """Base Amazon SP-API class""" # https://github.com/amzn/selling-partner-api-docs/blob/main/guides/en-US/developer-guide/SellingPartnerApiDeveloperGuide.md#connecting-to-the-selling-partner-api @@ -280,8 +280,8 @@ def make_request( self, method: str = "GET", append_to_base_uri: str = "", - params: dict = None, - data: dict = None, + params: dict | None = None, + data: dict | None = None, ) -> dict: if isinstance(params, dict): params = Util.remove_empty(params) @@ -312,7 +312,7 @@ class Finances(SPAPI): BASE_URI = "/finances/v0/" def list_financial_events_by_order_id( - self, order_id: str, max_results: int = None, next_token: str = None + self, order_id: str, max_results: int | None = None, next_token: str | None = None ) -> dict: """Returns all financial events for the specified order.""" append_to_base_uri = f"orders/{order_id}/financialEvents" @@ -328,22 +328,22 @@ class Orders(SPAPI): def get_orders( self, created_after: str, - created_before: str = None, - last_updated_after: str = None, - last_updated_before: str = None, - order_statuses: list = None, - marketplace_ids: list = None, - fulfillment_channels: list = None, - payment_methods: list = None, - buyer_email: str = None, - seller_order_id: str = None, + created_before: str | None = None, + last_updated_after: str | None = None, + last_updated_before: str | None = None, + order_statuses: list | None = None, + marketplace_ids: list | None = None, + fulfillment_channels: list | None = None, + payment_methods: list | None = None, + buyer_email: str | None = None, + seller_order_id: str | None = None, max_results: int = 100, - easyship_shipment_statuses: list = None, - next_token: str = None, - amazon_order_ids: list = None, - actual_fulfillment_supply_source_id: str = None, + easyship_shipment_statuses: list | None = None, + next_token: str | None = None, + amazon_order_ids: list | None = None, + actual_fulfillment_supply_source_id: str | None = None, is_ispu: bool = False, - store_chain_store_id: str = None, + store_chain_store_id: str | None = None, ) -> dict: """Returns orders created or updated during the time frame indicated by the specified parameters. You can also apply a range of filtering criteria to narrow the list of orders returned. If NextToken is present, that will be used to retrieve the orders instead of other criteria.""" data = dict( @@ -373,7 +373,7 @@ def get_orders( return self.make_request(params=data) - def get_order_items(self, order_id: str, next_token: str = None) -> dict: + def get_order_items(self, order_id: str, next_token: str | None = None) -> dict: """Returns detailed order item information for the order indicated by the specified order ID. If NextToken is provided, it's used to retrieve the next page of order items.""" append_to_base_uri = f"/{order_id}/orderItems" data = dict(NextToken=next_token) @@ -388,7 +388,7 @@ class CatalogItems(SPAPI): def get_catalog_item( self, asin: str, - marketplace_id: str = None, + marketplace_id: str | None = None, ) -> dict: """Returns a specified item and its attributes.""" if not marketplace_id: diff --git a/ecommerce_integrations/amazon/doctype/amazon_sp_api_settings/amazon_sp_api_settings.py b/ecommerce_integrations/amazon/doctype/amazon_sp_api_settings/amazon_sp_api_settings.py index 0765ebf27..bcafd5ac7 100644 --- a/ecommerce_integrations/amazon/doctype/amazon_sp_api_settings/amazon_sp_api_settings.py +++ b/ecommerce_integrations/amazon/doctype/amazon_sp_api_settings/amazon_sp_api_settings.py @@ -32,7 +32,7 @@ def validate(self): frappe.throw(frappe._("Value for Max Retry Limit must be less than or equal to 5.")) def save(self): - super(AmazonSPAPISettings, self).save() + super().save() if not self.is_old_data_migrated: migrate_old_data() diff --git a/ecommerce_integrations/controllers/customer.py b/ecommerce_integrations/controllers/customer.py index b6b231c4d..4c543b7ac 100644 --- a/ecommerce_integrations/controllers/customer.py +++ b/ecommerce_integrations/controllers/customer.py @@ -1,5 +1,3 @@ -from typing import Dict - import frappe from frappe import _ from frappe.utils.nestedset import get_root_of @@ -50,7 +48,7 @@ def get_customer_address_doc(self, address_type: str): except frappe.DoesNotExistError: return None - def create_customer_address(self, address: Dict[str, str]) -> None: + def create_customer_address(self, address: dict[str, str]) -> None: """Create address from dictionary containing fields used in Address doctype of ERPNext.""" customer_doc = self.get_customer_doc() @@ -63,7 +61,7 @@ def create_customer_address(self, address: Dict[str, str]) -> None: } ).insert(ignore_mandatory=True) - def create_customer_contact(self, contact: Dict[str, str]) -> None: + def create_customer_contact(self, contact: dict[str, str]) -> None: """Create contact from dictionary containing fields used in Address doctype of ERPNext.""" customer_doc = self.get_customer_doc() diff --git a/ecommerce_integrations/controllers/inventory.py b/ecommerce_integrations/controllers/inventory.py index 3438086f7..59b02f352 100644 --- a/ecommerce_integrations/controllers/inventory.py +++ b/ecommerce_integrations/controllers/inventory.py @@ -1,12 +1,10 @@ -from typing import List, Tuple - import frappe from frappe import _dict from frappe.utils import now from frappe.utils.nestedset import get_descendants_of -def get_inventory_levels(warehouses: Tuple[str], integration: str) -> List[_dict]: +def get_inventory_levels(warehouses: tuple[str], integration: str) -> list[_dict]: """ Get list of dict containing items for which the inventory needs to be updated on Integeration. @@ -26,7 +24,7 @@ def get_inventory_levels(warehouses: Tuple[str], integration: str) -> List[_dict AND bin.modified > ei.inventory_synced_on AND ei.integration = %s """, - values=warehouses + (integration,), + values=(*warehouses, integration), as_dict=1, ) @@ -40,7 +38,7 @@ def get_inventory_levels_of_group_warehouse(warehouse: str, integration: str): leaf warehouses is required""" child_warehouse = get_descendants_of("Warehouse", warehouse) - all_warehouses = tuple(child_warehouse) + (warehouse,) + all_warehouses = (*tuple(child_warehouse), warehouse) data = frappe.db.sql( f""" @@ -61,7 +59,7 @@ def get_inventory_levels_of_group_warehouse(warehouse: str, integration: str): HAVING last_updated > last_synced """, - values=all_warehouses + (integration,), + values=(*all_warehouses, integration), as_dict=1, ) diff --git a/ecommerce_integrations/controllers/setting.py b/ecommerce_integrations/controllers/setting.py index 7b48c66b7..8f6e0da29 100644 --- a/ecommerce_integrations/controllers/setting.py +++ b/ecommerce_integrations/controllers/setting.py @@ -1,4 +1,4 @@ -from typing import Dict, List, NewType +from typing import NewType from frappe.model.document import Document @@ -11,11 +11,11 @@ def is_enabled(self) -> bool: """Check if integration is enabled or not.""" raise NotImplementedError() - def get_erpnext_warehouses(self) -> List[ERPNextWarehouse]: + def get_erpnext_warehouses(self) -> list[ERPNextWarehouse]: raise NotImplementedError() - def get_erpnext_to_integration_wh_mapping(self) -> Dict[ERPNextWarehouse, IntegrationWarehouse]: + def get_erpnext_to_integration_wh_mapping(self) -> dict[ERPNextWarehouse, IntegrationWarehouse]: raise NotImplementedError() - def get_integration_to_erpnext_wh_mapping(self) -> Dict[IntegrationWarehouse, ERPNextWarehouse]: + def get_integration_to_erpnext_wh_mapping(self) -> dict[IntegrationWarehouse, ERPNextWarehouse]: raise NotImplementedError() diff --git a/ecommerce_integrations/ecommerce_integrations/doctype/ecommerce_item/ecommerce_item.py b/ecommerce_integrations/ecommerce_integrations/doctype/ecommerce_item/ecommerce_item.py index 7f087440d..b3b4ebfa9 100644 --- a/ecommerce_integrations/ecommerce_integrations/doctype/ecommerce_item/ecommerce_item.py +++ b/ecommerce_integrations/ecommerce_integrations/doctype/ecommerce_item/ecommerce_item.py @@ -1,8 +1,6 @@ # Copyright (c) 2021, Frappe and contributors # For license information, please see LICENSE -from typing import Dict, Optional - import frappe from frappe import _ from frappe.model.document import Document @@ -57,8 +55,8 @@ def set_defaults(self): def is_synced( integration: str, integration_item_code: str, - variant_id: Optional[str] = None, - sku: Optional[str] = None, + variant_id: str | None = None, + sku: str | None = None, ) -> bool: """Check if item is synced from integration. @@ -87,9 +85,9 @@ def _is_sku_synced(integration: str, sku: str) -> bool: def get_erpnext_item_code( integration: str, integration_item_code: str, - variant_id: Optional[str] = None, - has_variants: Optional[int] = 0, -) -> Optional[str]: + variant_id: str | None = None, + has_variants: int | None = 0, +) -> str | None: filters = {"integration": integration, "integration_item_code": integration_item_code} if variant_id: filters.update({"variant_id": variant_id}) @@ -102,9 +100,9 @@ def get_erpnext_item_code( def get_erpnext_item( integration: str, integration_item_code: str, - variant_id: Optional[str] = None, - sku: Optional[str] = None, - has_variants: Optional[int] = 0, + variant_id: str | None = None, + sku: str | None = None, + has_variants: int | None = 0, ): """Get ERPNext item for specified ecommerce_item. @@ -128,10 +126,10 @@ def get_erpnext_item( def create_ecommerce_item( integration: str, integration_item_code: str, - item_dict: Dict, - variant_id: Optional[str] = None, - sku: Optional[str] = None, - variant_of: Optional[str] = None, + item_dict: dict, + variant_id: str | None = None, + sku: str | None = None, + variant_of: str | None = None, has_variants=0, ) -> None: """Create Item in erpnext and link it with Ecommerce item doctype. diff --git a/ecommerce_integrations/shopify/connection.py b/ecommerce_integrations/shopify/connection.py index 05df1697f..9805443d9 100644 --- a/ecommerce_integrations/shopify/connection.py +++ b/ecommerce_integrations/shopify/connection.py @@ -3,7 +3,6 @@ import hashlib import hmac import json -from typing import List from shopify.resources import Webhook from shopify.session import Session @@ -39,7 +38,7 @@ def wrapper(*args, **kwargs): return wrapper -def register_webhooks(shopify_url: str, password: str) -> List[Webhook]: +def register_webhooks(shopify_url: str, password: str) -> list[Webhook]: """Register required webhooks with shopify and return registered webhooks.""" new_webhooks = [] diff --git a/ecommerce_integrations/shopify/customer.py b/ecommerce_integrations/shopify/customer.py index 8d72a789f..3a0ee952f 100644 --- a/ecommerce_integrations/shopify/customer.py +++ b/ecommerce_integrations/shopify/customer.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, Optional +from typing import Any import frappe from frappe import _ @@ -18,7 +18,7 @@ def __init__(self, customer_id: str): self.setting = frappe.get_doc(SETTING_DOCTYPE) super().__init__(customer_id, CUSTOMER_ID_FIELD, MODULE_NAME) - def sync_customer(self, customer: Dict[str, Any]) -> None: + def sync_customer(self, customer: dict[str, Any]) -> None: """Create Customer in ERPNext using shopify's Customer dict.""" customer_name = cstr(customer.get("first_name")) + " " + cstr(customer.get("last_name")) @@ -45,9 +45,9 @@ def sync_customer(self, customer: Dict[str, Any]) -> None: def create_customer_address( self, customer_name, - shopify_address: Dict[str, Any], + shopify_address: dict[str, Any], address_type: str = "Billing", - email: Optional[str] = None, + email: str | None = None, ) -> None: """Create customer address(es) using Customer dict provided by shopify.""" address_fields = _map_address_fields(shopify_address, customer_name, address_type, email) @@ -68,9 +68,9 @@ def update_existing_addresses(self, customer): def _update_existing_address( self, customer_name, - shopify_address: Dict[str, Any], + shopify_address: dict[str, Any], address_type: str = "Billing", - email: Optional[str] = None, + email: str | None = None, ) -> None: old_address = self.get_customer_address_doc(address_type) @@ -84,7 +84,7 @@ def _update_existing_address( old_address.flags.ignore_mandatory = True old_address.save() - def create_customer_contact(self, shopify_customer: Dict[str, Any]) -> None: + def create_customer_contact(self, shopify_customer: dict[str, Any]) -> None: if not (shopify_customer.get("first_name") and shopify_customer.get("email")): return diff --git a/ecommerce_integrations/shopify/doctype/shopify_setting/shopify_setting.py b/ecommerce_integrations/shopify/doctype/shopify_setting/shopify_setting.py index 2c367f47f..019c8ea61 100644 --- a/ecommerce_integrations/shopify/doctype/shopify_setting/shopify_setting.py +++ b/ecommerce_integrations/shopify/doctype/shopify_setting/shopify_setting.py @@ -1,8 +1,6 @@ # Copyright (c) 2021, Frappe and contributors # For license information, please see LICENSE -from typing import Dict, List - from shopify.collection import PaginatedIterator from shopify.resources import Location @@ -95,15 +93,15 @@ def update_location_table(self): {"shopify_location_id": location.id, "shopify_location_name": location.name}, ) - def get_erpnext_warehouses(self) -> List[ERPNextWarehouse]: + def get_erpnext_warehouses(self) -> list[ERPNextWarehouse]: return [wh_map.erpnext_warehouse for wh_map in self.shopify_warehouse_mapping] - def get_erpnext_to_integration_wh_mapping(self) -> Dict[ERPNextWarehouse, IntegrationWarehouse]: + def get_erpnext_to_integration_wh_mapping(self) -> dict[ERPNextWarehouse, IntegrationWarehouse]: return { wh_map.erpnext_warehouse: wh_map.shopify_location_id for wh_map in self.shopify_warehouse_mapping } - def get_integration_to_erpnext_wh_mapping(self) -> Dict[IntegrationWarehouse, ERPNextWarehouse]: + def get_integration_to_erpnext_wh_mapping(self) -> dict[IntegrationWarehouse, ERPNextWarehouse]: return { wh_map.shopify_location_id: wh_map.erpnext_warehouse for wh_map in self.shopify_warehouse_mapping } diff --git a/ecommerce_integrations/shopify/order.py b/ecommerce_integrations/shopify/order.py index fae8d6b9c..88978522c 100644 --- a/ecommerce_integrations/shopify/order.py +++ b/ecommerce_integrations/shopify/order.py @@ -1,5 +1,5 @@ import json -from typing import Literal, Optional +from typing import Literal from shopify.collection import PaginatedIterator from shopify.resources import Order @@ -260,7 +260,7 @@ def consolidate_order_taxes(taxes): return tax_account_wise_data.values() -def get_tax_account_head(tax, charge_type: Optional[Literal["shipping", "sales_tax"]] = None): +def get_tax_account_head(tax, charge_type: Literal["shipping", "sales_tax"] | None = None): tax_title = str(tax.get("title")) tax_account = frappe.db.get_value( diff --git a/ecommerce_integrations/shopify/page/shopify_import_products/shopify_import_products.py b/ecommerce_integrations/shopify/page/shopify_import_products/shopify_import_products.py index 112aca075..8216a539b 100644 --- a/ecommerce_integrations/shopify/page/shopify_import_products/shopify_import_products.py +++ b/ecommerce_integrations/shopify/page/shopify_import_products/shopify_import_products.py @@ -154,12 +154,12 @@ def queue_sync_all_products(*args, **kwargs): publish(f"✅ Synced Product {product.id}", synced=True) except UniqueValidationError as e: - publish(f"❌ Error Syncing Product {product.id} : {str(e)}", error=True) + publish(f"❌ Error Syncing Product {product.id} : {e!s}", error=True) frappe.db.rollback(save_point=savepoint) continue except Exception as e: - publish(f"❌ Error Syncing Product {product.id} : {str(e)}", error=True) + publish(f"❌ Error Syncing Product {product.id} : {e!s}", error=True) frappe.db.rollback(save_point=savepoint) continue diff --git a/ecommerce_integrations/shopify/page/shopify_import_products/test_shopify_import_products.py b/ecommerce_integrations/shopify/page/shopify_import_products/test_shopify_import_products.py index 0382f4a9f..6619ac244 100644 --- a/ecommerce_integrations/shopify/page/shopify_import_products/test_shopify_import_products.py +++ b/ecommerce_integrations/shopify/page/shopify_import_products/test_shopify_import_products.py @@ -17,7 +17,7 @@ def __init__(self, obj): products_json = json.loads(f.read()) self._products = products_json["products"] - super(TestShopifyImportProducts, self).__init__(obj) + super().__init__(obj) def test_import_all_products(self): required_products = { @@ -97,7 +97,7 @@ def test_import_all_products(self): self.assertEqual(sorted(required_variants), sorted(created_ecom_variants)) def fake_single_product_from_bulk(self, product): - item = [p for p in self._products if str(p["id"]) == product][0] + item = next(p for p in self._products if str(p["id"]) == product) product_json = json.dumps({"product": item}) diff --git a/ecommerce_integrations/shopify/product.py b/ecommerce_integrations/shopify/product.py index 70b908b89..9f51c1f1d 100644 --- a/ecommerce_integrations/shopify/product.py +++ b/ecommerce_integrations/shopify/product.py @@ -1,5 +1,3 @@ -from typing import Optional - from shopify.resources import Product, Variant import frappe @@ -24,9 +22,9 @@ class ShopifyProduct: def __init__( self, product_id: str, - variant_id: Optional[str] = None, - sku: Optional[str] = None, - has_variants: Optional[int] = 0, + variant_id: str | None = None, + sku: str | None = None, + has_variants: int | None = 0, ): self.product_id = str(product_id) self.variant_id = str(variant_id) if variant_id else None @@ -530,8 +528,8 @@ def get_shopify_weight_uom(erpnext_weight_uom: str) -> str: def update_default_variant_properties( shopify_product: Product, is_stock_item: bool, - sku: Optional[str] = None, - price: Optional[float] = None, + sku: str | None = None, + price: float | None = None, ): """Shopify creates default variant upon saving the product. diff --git a/ecommerce_integrations/shopify/tests/utils.py b/ecommerce_integrations/shopify/tests/utils.py index aa97b3e31..ee70d9cc6 100644 --- a/ecommerce_integrations/shopify/tests/utils.py +++ b/ecommerce_integrations/shopify/tests/utils.py @@ -97,7 +97,7 @@ def setUp(self): self.http.site = "https://frappetest.myshopify.com" def load_fixture(self, name, format="json"): - with open(os.path.dirname(__file__) + "/data/%s.%s" % (name, format), "rb") as f: + with open(os.path.dirname(__file__) + f"/data/{name}.{format}", "rb") as f: return f.read() def fake(self, endpoint, **kwargs): @@ -108,9 +108,9 @@ def fake(self, endpoint, **kwargs): if "extension" in kwargs and not kwargs["extension"]: extension = "" else: - extension = ".%s" % (kwargs.pop("extension", "json")) + extension = f".{kwargs.pop('extension', 'json')}" - url = "https://frappetest.myshopify.com%s/%s%s" % (prefix, endpoint, extension) + url = f"https://frappetest.myshopify.com{prefix}/{endpoint}{extension}" try: url = kwargs["url"] except KeyError: @@ -118,7 +118,7 @@ def fake(self, endpoint, **kwargs): headers = {} if kwargs.pop("has_user_agent", True): - userAgent = "ShopifyPythonAPI/%s Python/%s" % (shopify.VERSION, sys.version.split(" ", 1)[0]) + userAgent = f"ShopifyPythonAPI/{shopify.VERSION} Python/{sys.version.split(' ', 1)[0]}" headers["User-agent"] = userAgent try: diff --git a/ecommerce_integrations/shopify/utils.py b/ecommerce_integrations/shopify/utils.py index a9262ce37..06bf1f582 100644 --- a/ecommerce_integrations/shopify/utils.py +++ b/ecommerce_integrations/shopify/utils.py @@ -1,6 +1,5 @@ # Copyright (c) 2021, Frappe and contributors # For license information, please see LICENSE -from typing import List import frappe from frappe import _, _dict @@ -73,7 +72,7 @@ def _migrate_items_to_ecommerce_item(log): log.save() -def _get_items_to_migrate() -> List[_dict]: +def _get_items_to_migrate() -> list[_dict]: """get all list of items that have shopify fields but do not have associated ecommerce item.""" old_data = frappe.db.sql( @@ -87,7 +86,7 @@ def _get_items_to_migrate() -> List[_dict]: return old_data or [] -def _create_ecommerce_items(items: List[_dict]) -> None: +def _create_ecommerce_items(items: list[_dict]) -> None: for item in items: if not all((item.erpnext_item_code, item.shopify_product_id, item.shopify_variant_id)): continue diff --git a/ecommerce_integrations/unicommerce/api_client.py b/ecommerce_integrations/unicommerce/api_client.py index bb8efe721..82febe6bf 100644 --- a/ecommerce_integrations/unicommerce/api_client.py +++ b/ecommerce_integrations/unicommerce/api_client.py @@ -1,18 +1,16 @@ import base64 -from typing import Any, Dict, List, Optional, Tuple +from typing import Any import requests from pytz import timezone import frappe -from frappe import _ +from frappe import _, _dict from frappe.utils import cint, cstr, get_datetime from ecommerce_integrations.unicommerce.constants import SETTINGS_DOCTYPE from ecommerce_integrations.unicommerce.utils import create_unicommerce_log -JsonDict = Dict[str, Any] - class UnicommerceAPIClient: """Wrapper around Unicommerce REST API @@ -22,8 +20,8 @@ class UnicommerceAPIClient: def __init__( self, - url: Optional[str] = None, - access_token: Optional[str] = None, + url: str | None = None, + access_token: str | None = None, ): self.settings = frappe.get_doc(SETTINGS_DOCTYPE) self.base_url = url or f"https://{self.settings.unicommerce_site}" @@ -42,12 +40,12 @@ def request( self, endpoint: str, method: str = "POST", - headers: Optional[JsonDict] = None, - body: Optional[JsonDict] = None, - params: Optional[JsonDict] = None, - files: Optional[JsonDict] = None, + headers: dict[str, Any] | None = None, + body: dict[str, Any] | None = None, + params: dict[str, Any] | None = None, + files: dict[str, Any] | None = None, log_error=True, - ) -> Tuple[JsonDict, bool]: + ) -> tuple[_dict | bytes | None, bool]: if headers is None: headers = {} @@ -85,7 +83,7 @@ def request( return data, status - def get_unicommerce_item(self, sku: str, log_error=True) -> Optional[JsonDict]: + def get_unicommerce_item(self, sku: str, log_error=True) -> _dict | None: """Get Unicommerce item data for specified SKU code. ref: https://documentation.unicommerce.com/docs/itemtype-get.html @@ -96,7 +94,9 @@ def get_unicommerce_item(self, sku: str, log_error=True) -> Optional[JsonDict]: if status: return item - def create_update_item(self, item_dict: JsonDict, update=False) -> Tuple[JsonDict, bool]: + def create_update_item( + self, item_dict: dict[str, Any], update=False + ) -> tuple[_dict | bytes | None, bool]: """Create/update item on unicommerce. ref: https://documentation.unicommerce.com/docs/createoredit-itemtype.html @@ -108,7 +108,7 @@ def create_update_item(self, item_dict: JsonDict, update=False) -> Tuple[JsonDic endpoint = "/services/rest/v1/catalog/itemType/edit" return self.request(endpoint=endpoint, body={"itemType": item_dict}) - def get_sales_order(self, order_code: str) -> Optional[JsonDict]: + def get_sales_order(self, order_code: str) -> _dict | None: """Get details for a sales order. ref: https://documentation.unicommerce.com/docs/saleorder-get.html @@ -122,13 +122,13 @@ def get_sales_order(self, order_code: str) -> Optional[JsonDict]: def search_sales_order( self, - from_date: Optional[str] = None, - to_date: Optional[str] = None, - status: Optional[str] = None, - channel: Optional[str] = None, - facility_codes: Optional[List[str]] = None, - updated_since: Optional[int] = None, - ) -> Optional[List[JsonDict]]: + from_date: str | None = None, + to_date: str | None = None, + status: str | None = None, + channel: str | None = None, + facility_codes: list[str] | None = None, + updated_since: int | None = None, + ) -> list[dict[str, Any]] | None: """Search sales order using specified parameters and return search results. ref: https://documentation.unicommerce.com/docs/saleorder-search.html @@ -151,8 +151,8 @@ def search_sales_order( return search_results["elements"] def get_inventory_snapshot( - self, sku_codes: List[str], facility_code: str, updated_since: int = 1430 - ) -> Optional[JsonDict]: + self, sku_codes: list[str], facility_code: str, updated_since: int = 1430 + ) -> _dict | None: """Get current inventory snapshot. ref: https://documentation.unicommerce.com/docs/inventory-snapshot.html @@ -171,7 +171,7 @@ def get_inventory_snapshot( if status: return response - def bulk_inventory_update(self, facility_code: str, inventory_map: Dict[str, int]): + def bulk_inventory_update(self, facility_code: str, inventory_map: dict[str, int]): """Bulk update inventory on unicommerce using SKU and qty. The qty should be "total" quantity. @@ -221,8 +221,8 @@ def bulk_inventory_update(self, facility_code: str, inventory_map: Dict[str, int return response, False def create_sales_invoice( - self, so_code: str, so_item_codes: List[str], facility_code: str - ) -> Optional[JsonDict]: + self, so_code: str, so_item_codes: list[str], facility_code: str + ) -> _dict | None: body = {"saleOrderCode": so_code, "saleOrderItemCodes": so_item_codes} extra_headers = {"Facility": facility_code} @@ -282,7 +282,7 @@ def create_invoice_and_label_by_shipping_code( def get_sales_invoice( self, shipping_package_code: str, facility_code: str, is_return: bool = False - ) -> Optional[JsonDict]: + ) -> _dict | None: """Get invoice details ref: https://documentation.unicommerce.com/docs/invoice-getdetails.html @@ -336,7 +336,7 @@ def _positive(numbers): headers=extra_headers, ) - def get_invoice_label(self, shipping_package_code: str, facility_code: str) -> Optional[str]: + def get_invoice_label(self, shipping_package_code: str, facility_code: str) -> bytes | None: """Get the generated label for a given shipping package. ref: undocumented. @@ -356,7 +356,7 @@ def create_and_close_shipping_manifest( channel: str, shipping_provider_code: str, shipping_method_code: str, - shipping_packages: List[str], + shipping_packages: list[str], facility_code: str, third_party_shipping: bool = True, ): @@ -396,9 +396,9 @@ def get_shipping_manifest(self, shipping_manifest_code, facility_code): def search_shipping_packages( self, facility_code: str, - channel: Optional[str] = None, - statuses: Optional[List[str]] = None, - updated_since: Optional[int] = 6 * 60, + channel: str | None = None, + statuses: list[str] | None = None, + updated_since: int | None = 6 * 60, ): """Search shipping packages on unicommerce matching specified criterias. diff --git a/ecommerce_integrations/unicommerce/cancellation_and_returns.py b/ecommerce_integrations/unicommerce/cancellation_and_returns.py index 4795ecae8..29bd59681 100644 --- a/ecommerce_integrations/unicommerce/cancellation_and_returns.py +++ b/ecommerce_integrations/unicommerce/cancellation_and_returns.py @@ -1,7 +1,6 @@ import json from collections import defaultdict from datetime import date, datetime -from typing import List import frappe from frappe.utils import now_datetime @@ -23,7 +22,7 @@ ) -def fully_cancel_orders(unicommerce_order_codes: List[str]) -> None: +def fully_cancel_orders(unicommerce_order_codes: list[str]) -> None: """Perform "cancel" action on ERPNext sales orders which are fully cancelled in Unicommerce.""" current_orders_status = frappe.db.get_values( @@ -103,7 +102,7 @@ def _serialize_items(trans_items) -> str: # serialie date/datetime objects to string for item in trans_items: for k, v in item.items(): - if isinstance(v, (datetime, date)): + if isinstance(v, datetime | date): item[k] = str(v) return json.dumps(trans_items) @@ -156,7 +155,7 @@ def create_credit_note(invoice_name): for tax in credit_note.taxes: tax.item_wise_tax_detail = json.loads(tax.item_wise_tax_detail) - for item, tax_distribution in tax.item_wise_tax_detail.items(): + for _, tax_distribution in tax.item_wise_tax_detail.items(): tax_distribution[1] *= -1 tax.item_wise_tax_detail = json.dumps(tax.item_wise_tax_detail) @@ -211,7 +210,7 @@ def create_cir_credit_note(so_data, return_data): credit_note.save() -def _handle_partial_returns(credit_note, returned_items: List[str]) -> None: +def _handle_partial_returns(credit_note, returned_items: list[str]) -> None: """Remove non-returned item from credit note and update taxes""" item_code_to_qty_map = defaultdict(float) diff --git a/ecommerce_integrations/unicommerce/customer.py b/ecommerce_integrations/unicommerce/customer.py index 969ba5960..7cac4a309 100644 --- a/ecommerce_integrations/unicommerce/customer.py +++ b/ecommerce_integrations/unicommerce/customer.py @@ -1,5 +1,5 @@ import json -from typing import Any, Dict, List +from typing import Any import frappe from frappe import _ @@ -78,7 +78,7 @@ def _check_if_customer_exists(address, customer_code): return frappe.get_doc("Customer", customer_name) -def _create_customer_addresses(addresses: List[Dict[str, Any]], customer) -> None: +def _create_customer_addresses(addresses: list[dict[str, Any]], customer) -> None: """Create address from dictionary containing fields used in Address doctype of ERPNext. Unicommerce orders contain address list, diff --git a/ecommerce_integrations/unicommerce/doctype/unicommerce_settings/unicommerce_settings.py b/ecommerce_integrations/unicommerce/doctype/unicommerce_settings/unicommerce_settings.py index b7b091430..a4510a25d 100644 --- a/ecommerce_integrations/unicommerce/doctype/unicommerce_settings/unicommerce_settings.py +++ b/ecommerce_integrations/unicommerce/doctype/unicommerce_settings/unicommerce_settings.py @@ -1,8 +1,6 @@ # Copyright (c) 2021, Frappe and contributors # For license information, please see LICENSE -from typing import Dict, List, Optional, Tuple - import requests import frappe @@ -144,7 +142,7 @@ def validate_warehouse_mapping(self): _("Warehouse Mapping should be unique and one-to-one without repeating same warehouses.") ) - def get_erpnext_warehouses(self, all_wh=False) -> List[ERPNextWarehouse]: + def get_erpnext_warehouses(self, all_wh=False) -> list[ERPNextWarehouse]: """Get list of configured ERPNext warehouses. all_wh flag ignores enabled status. @@ -153,7 +151,7 @@ def get_erpnext_warehouses(self, all_wh=False) -> List[ERPNextWarehouse]: def get_erpnext_to_integration_wh_mapping( self, all_wh=False - ) -> Dict[ERPNextWarehouse, IntegrationWarehouse]: + ) -> dict[ERPNextWarehouse, IntegrationWarehouse]: """Get enabled mapping from ERPNextWarehouse to Unicommerce facility. all_wh flag ignores enabled status.""" @@ -165,7 +163,7 @@ def get_erpnext_to_integration_wh_mapping( def get_integration_to_erpnext_wh_mapping( self, all_wh=False - ) -> Dict[IntegrationWarehouse, ERPNextWarehouse]: + ) -> dict[IntegrationWarehouse, ERPNextWarehouse]: """Get enabled mapping from Unicommerce facility to ERPNext warehouse. all_wh flag ignores enabled status.""" @@ -173,7 +171,7 @@ def get_integration_to_erpnext_wh_mapping( return {v: k for k, v in reverse_map.items()} - def get_company_addresses(self, facility_code: str) -> Tuple[Optional[str], Optional[str]]: + def get_company_addresses(self, facility_code: str) -> tuple[str | None, str | None]: """Get mapped company billing and shipping addresses.""" for wh_map in self.warehouse_mapping: if wh_map.unicommerce_facility_code == facility_code: diff --git a/ecommerce_integrations/unicommerce/doctype/unicommerce_shipment_manifest/unicommerce_shipment_manifest.py b/ecommerce_integrations/unicommerce/doctype/unicommerce_shipment_manifest/unicommerce_shipment_manifest.py index 9955fffc6..03f6595ae 100644 --- a/ecommerce_integrations/unicommerce/doctype/unicommerce_shipment_manifest/unicommerce_shipment_manifest.py +++ b/ecommerce_integrations/unicommerce/doctype/unicommerce_shipment_manifest/unicommerce_shipment_manifest.py @@ -2,7 +2,6 @@ # For license information, please see LICENSE import json -from typing import Optional import frappe from frappe import _ @@ -80,7 +79,7 @@ def get_facility_code(self) -> str: ",".join(facility_codes) ) ) - return list(facility_codes)[0] + return next(iter(facility_codes)) def create_and_close_manifest_on_unicommerce(self): shipping_packages = [d.shipping_package_code for d in self.manifest_items] @@ -161,7 +160,7 @@ def get_sales_invoice_details(sales_invoice): @frappe.whitelist() -def search_packages(search_term: str, channel: Optional[str] = None, shipper: Optional[str] = None): +def search_packages(search_term: str, channel: str | None = None, shipper: str | None = None): filters = { CHANNEL_ID_FIELD: channel, SHIPPING_PROVIDER_CODE: shipper, diff --git a/ecommerce_integrations/unicommerce/grn.py b/ecommerce_integrations/unicommerce/grn.py index 7f8e6d1a5..45554133b 100644 --- a/ecommerce_integrations/unicommerce/grn.py +++ b/ecommerce_integrations/unicommerce/grn.py @@ -1,5 +1,4 @@ from dataclasses import dataclass -from typing import List import frappe from frappe import _ @@ -96,7 +95,7 @@ def get_facility_code(stock_entry, unicommerce_settings) -> str: _("{} only supports one target warehouse (unicommerce facility)").format(GRN_STOCK_ENTRY_TYPE) ) - warehouse = list(target_warehouses)[0] + warehouse = next(iter(target_warehouses)) warehouse_mapping = unicommerce_settings.get_erpnext_to_integration_wh_mapping(all_wh=True) facility = warehouse_mapping.get(warehouse) @@ -189,7 +188,7 @@ def _prepare_grn_import_csv(stock_entry) -> str: return file.file_name -def _get_csv_content(rows: List[GRNItemRow]) -> bytes: +def _get_csv_content(rows: list[GRNItemRow]) -> bytes: writer = UnicodeWriter() for row in rows: diff --git a/ecommerce_integrations/unicommerce/inventory.py b/ecommerce_integrations/unicommerce/inventory.py index f67d9cd9d..d8f6961de 100644 --- a/ecommerce_integrations/unicommerce/inventory.py +++ b/ecommerce_integrations/unicommerce/inventory.py @@ -1,5 +1,4 @@ from collections import defaultdict -from typing import Dict import frappe from frappe.utils import cint, now @@ -43,7 +42,7 @@ def update_inventory_on_unicommerce(client=None, force=False): client = UnicommerceAPIClient() # track which ecommerce item was updated successfully - success_map: Dict[str, bool] = defaultdict(lambda: True) + success_map: dict[str, bool] = defaultdict(lambda: True) inventory_synced_on = now() for warehouse in warehouses: @@ -80,7 +79,7 @@ def update_inventory_on_unicommerce(client=None, force=False): _update_inventory_sync_status(success_map, inventory_synced_on) -def _update_inventory_sync_status(ecom_item_success_map: Dict[str, bool], timestamp: str) -> None: +def _update_inventory_sync_status(ecom_item_success_map: dict[str, bool], timestamp: str) -> None: for ecom_item, status in ecom_item_success_map.items(): if status: update_inventory_sync_status(ecom_item, timestamp) diff --git a/ecommerce_integrations/unicommerce/invoice.py b/ecommerce_integrations/unicommerce/invoice.py index 492c3b239..c8f8095eb 100644 --- a/ecommerce_integrations/unicommerce/invoice.py +++ b/ecommerce_integrations/unicommerce/invoice.py @@ -1,12 +1,12 @@ import base64 import json from collections import defaultdict -from typing import Any, Dict, List, NewType, Optional +from typing import Any, NewType import requests import frappe -from frappe import _ +from frappe import _, _dict from frappe.utils import cint, flt, nowdate from frappe.utils.file_manager import save_file @@ -36,7 +36,6 @@ remove_non_alphanumeric_chars, ) -JsonDict = Dict[str, Any] SOCode = NewType("SOCode", str) # TypedDict @@ -44,17 +43,17 @@ # item_code: str # warehouse: str # batch_no: str -ItemWHAlloc = Dict[str, str] +ItemWHAlloc = dict[str, str] -WHAllocation = Dict[SOCode, List[ItemWHAlloc]] +WHAllocation = dict[SOCode, list[ItemWHAlloc]] INVOICED_STATE = ["PACKED", "READY_TO_SHIP", "DISPATCHED", "MANIFESTED", "SHIPPED", "DELIVERED"] @frappe.whitelist() def generate_unicommerce_invoices( - sales_orders: List[SOCode], warehouse_allocation: Optional[WHAllocation] = None + sales_orders: list[SOCode], warehouse_allocation: WHAllocation | None = None ): """Request generation of invoice to Unicommerce and sync that invoice. @@ -128,8 +127,8 @@ def generate_unicommerce_invoices( def bulk_generate_invoices( - sales_orders: List[SOCode], - warehouse_allocation: Optional[WHAllocation] = None, + sales_orders: list[SOCode], + warehouse_allocation: WHAllocation | None = None, request_id=None, client=None, ): @@ -188,7 +187,7 @@ def _get_orders_with_missing_invoice(sales_orders): return missing_invoices -def update_invoicing_status(sales_orders: List[str], status: str) -> None: +def update_invoicing_status(sales_orders: list[str], status: str) -> None: if not sales_orders: return @@ -303,14 +302,14 @@ def _fetch_and_sync_invoice( def create_sales_invoice( - si_data: JsonDict, + si_data: _dict, so_code: str, update_stock=0, submit=True, shipping_label=None, warehouse_allocations=None, invoice_response=None, - so_data: Optional[JsonDict] = None, + so_data: _dict | None = None, ): """Create ERPNext Sales Invcoice using Unicommerce sales invoice data and related Sales order. @@ -405,10 +404,10 @@ def create_sales_invoice( def attach_unicommerce_docs( sales_invoice: str, - invoice: Optional[str], - label: Optional[str], - invoice_code: Optional[str], - package_code: Optional[str], + invoice: str | None, + label: str | None, + invoice_code: str | None, + package_code: str | None, ) -> None: """Attach invoice and label to specified sales invoice. @@ -445,8 +444,8 @@ def _get_line_items( warehouse: str, so_code: str, cost_center: str, - warehouse_allocations: Optional[WHAllocation] = None, -) -> List[Dict[str, Any]]: + warehouse_allocations: WHAllocation | None = None, +) -> list[dict[str, Any]]: """Invoice items can be different and are consolidated, hence recomputing is required""" si_items = [] @@ -474,7 +473,7 @@ def _get_line_items( return si_items -def _assign_wh_and_so_row(line_items, warehouse_allocation: List[ItemWHAlloc], so_code: str): +def _assign_wh_and_so_row(line_items, warehouse_allocation: list[ItemWHAlloc], so_code: str): so_items = frappe.get_doc("Sales Order", so_code).items so_item_price_map = {d.name: d.rate for d in so_items} @@ -491,7 +490,7 @@ def _assign_wh_and_so_row(line_items, warehouse_allocation: List[ItemWHAlloc], s line_items.sort(key=sort_key) # update references - for item, wh_alloc in zip(line_items, warehouse_allocation): + for item, wh_alloc in zip(line_items, warehouse_allocation, strict=True): item["so_detail"] = wh_alloc["sales_order_row"] item["warehouse"] = wh_alloc["warehouse"] item["batch_no"] = wh_alloc.get("batch_no") diff --git a/ecommerce_integrations/unicommerce/order.py b/ecommerce_integrations/unicommerce/order.py index d0820573c..7c78608b1 100644 --- a/ecommerce_integrations/unicommerce/order.py +++ b/ecommerce_integrations/unicommerce/order.py @@ -1,6 +1,7 @@ import json from collections import defaultdict, namedtuple -from typing import Any, Dict, Iterator, List, NewType, Optional, Set, Tuple +from collections.abc import Iterator +from typing import Any, NewType import frappe from frappe.utils import add_to_date, flt @@ -29,7 +30,7 @@ from ecommerce_integrations.unicommerce.utils import create_unicommerce_log, get_unicommerce_date from ecommerce_integrations.utils.taxation import get_dummy_tax_category -UnicommerceOrder = NewType("UnicommerceOrder", Dict[str, Any]) +UnicommerceOrder = NewType("UnicommerceOrder", dict[str, Any]) def sync_new_orders(client: UnicommerceAPIClient = None, force=False): @@ -61,9 +62,7 @@ def sync_new_orders(client: UnicommerceAPIClient = None, force=False): _create_sales_invoices(order, sales_order, client) -def _get_new_orders( - client: UnicommerceAPIClient, status: Optional[str] -) -> Optional[Iterator[UnicommerceOrder]]: +def _get_new_orders(client: UnicommerceAPIClient, status: str | None) -> Iterator[UnicommerceOrder] | None: """Search new sales order from unicommerce.""" updated_since = 24 * 60 # minutes @@ -123,7 +122,7 @@ def _create_sales_invoices(unicommerce_order, sales_order, client: UnicommerceAP frappe.flags.request_id = None -def create_order(payload: UnicommerceOrder, request_id: Optional[str] = None, client=None) -> None: +def create_order(payload: UnicommerceOrder, request_id: str | None = None, client=None) -> None: order = payload existing_so = frappe.db.get_value("Sales Order", {ORDER_CODE_FIELD: order["code"]}) @@ -156,7 +155,7 @@ def create_order(payload: UnicommerceOrder, request_id: Optional[str] = None, cl return order -def _sync_order_items(order: UnicommerceOrder, client: UnicommerceAPIClient) -> Set[str]: +def _sync_order_items(order: UnicommerceOrder, client: UnicommerceAPIClient) -> set[str]: """Ensure all items are synced before processing order. If not synced then product sync for specific item is initiated""" @@ -216,8 +215,8 @@ def _create_order(order: UnicommerceOrder, customer) -> None: def _get_line_items( - line_items, default_warehouse: Optional[str] = None, is_cancelled: bool = False -) -> List[Dict[str, Any]]: + line_items, default_warehouse: str | None = None, is_cancelled: bool = False +) -> list[dict[str, Any]]: settings = frappe.get_cached_doc(SETTINGS_DOCTYPE) wh_map = settings.get_integration_to_erpnext_wh_mapping(all_wh=True) so_items = [] @@ -245,7 +244,7 @@ def _get_line_items( return so_items -def get_taxes(line_items, channel_config) -> List: +def get_taxes(line_items, channel_config) -> list: taxes = [] # Note: Tax details are NOT available during SO stage. @@ -297,7 +296,7 @@ def _get_facility_code(line_items) -> str: if len(facility_codes) > 1: frappe.throw("Multiple facility codes found in single order") - return list(facility_codes)[0] + return next(iter(facility_codes)) def update_shipping_info(doc, method=None): @@ -350,7 +349,7 @@ def _update_package_info_on_unicommerce(so_code): raise -def _get_batch_no(so_line_item) -> Optional[str]: +def _get_batch_no(so_line_item) -> str | None: """If specified vendor batch code is valid batch number in ERPNext then get batch no. SO line items contain batch no detail like this: diff --git a/ecommerce_integrations/unicommerce/product.py b/ecommerce_integrations/unicommerce/product.py index 249e91057..86d2cd6ee 100644 --- a/ecommerce_integrations/unicommerce/product.py +++ b/ecommerce_integrations/unicommerce/product.py @@ -1,4 +1,4 @@ -from typing import List, NewType +from typing import Any, NewType from stdnum.ean import is_valid as validate_barcode @@ -8,7 +8,7 @@ from frappe.utils.nestedset import get_root_of from ecommerce_integrations.ecommerce_integrations.doctype.ecommerce_item import ecommerce_item -from ecommerce_integrations.unicommerce.api_client import JsonDict, UnicommerceAPIClient +from ecommerce_integrations.unicommerce.api_client import UnicommerceAPIClient from ecommerce_integrations.unicommerce.constants import ( DEFAULT_WEIGHT_UOM, ITEM_BATCH_GROUP_FIELD, @@ -218,7 +218,7 @@ def upload_new_items(force=False) -> None: log.save() -def _get_new_items() -> List[ItemCode]: +def _get_new_items() -> list[ItemCode]: new_items = frappe.db.sql( f""" SELECT item.item_code @@ -234,8 +234,8 @@ def _get_new_items() -> List[ItemCode]: def upload_items_to_unicommerce( - item_codes: List[ItemCode], client: UnicommerceAPIClient = None -) -> List[ItemCode]: + item_codes: list[ItemCode], client: UnicommerceAPIClient = None +) -> list[ItemCode]: """Upload multiple items to Unicommerce. Return Successfully synced item codes. @@ -259,7 +259,7 @@ def upload_items_to_unicommerce( return synced_items -def _build_unicommerce_item(item_code: ItemCode) -> JsonDict: +def _build_unicommerce_item(item_code: ItemCode) -> dict[str, Any]: """Build Unicommerce item JSON using an ERPNext item""" item = frappe.get_doc("Item", item_code) diff --git a/ecommerce_integrations/unicommerce/tests/test_invoice.py b/ecommerce_integrations/unicommerce/tests/test_invoice.py index cb9931784..28da6232a 100644 --- a/ecommerce_integrations/unicommerce/tests/test_invoice.py +++ b/ecommerce_integrations/unicommerce/tests/test_invoice.py @@ -57,7 +57,7 @@ def test_create_invoice(self): attachments = frappe.get_all( "File", fields=["name", "file_name"], filters={"attached_to_name": si.name} ) - self.assertGreaterEqual(len(attachments), 2, msg=f"Expected 2 attachments, found: {str(attachments)}") + self.assertGreaterEqual(len(attachments), 2, msg=f"Expected 2 attachments, found: {attachments!s}") def test_end_to_end_invoice_generation(self): """Full invoice generation test with mocked responses.""" @@ -113,4 +113,4 @@ def test_end_to_end_invoice_generation(self): attachments = frappe.get_all( "File", fields=["name", "file_name"], filters={"attached_to_name": si.name} ) - self.assertGreaterEqual(len(attachments), 2, msg=f"Expected 2 attachments, found: {str(attachments)}") + self.assertGreaterEqual(len(attachments), 2, msg=f"Expected 2 attachments, found: {attachments!s}") diff --git a/ecommerce_integrations/unicommerce/tests/test_order.py b/ecommerce_integrations/unicommerce/tests/test_order.py index 1a78cadf7..dd9797bfc 100644 --- a/ecommerce_integrations/unicommerce/tests/test_order.py +++ b/ecommerce_integrations/unicommerce/tests/test_order.py @@ -34,7 +34,7 @@ def test_validate_item_list(self): {"MC-100", "TITANIUM_WATCH"}, ] - for order_file, items in zip(order_files, items_list): + for order_file, items in zip(order_files, items_list, strict=True): order = self.load_fixture(order_file)["saleOrderDTO"] self.assertEqual(items, _sync_order_items(order, client=self.client)) diff --git a/ecommerce_integrations/unicommerce/tests/utils.py b/ecommerce_integrations/unicommerce/tests/utils.py index 3c0e06880..d55d5ddce 100644 --- a/ecommerce_integrations/unicommerce/tests/utils.py +++ b/ecommerce_integrations/unicommerce/tests/utils.py @@ -1,6 +1,7 @@ import copy import json import os +import typing import unittest import frappe @@ -12,7 +13,7 @@ class TestCase(unittest.TestCase): - config = { + config: typing.ClassVar[dict] = { "is_enabled": 1, "enable_inventory_sync": 1, "use_stock_entry_for_grn": 1, diff --git a/ecommerce_integrations/zenoti/stock_reconciliation.py b/ecommerce_integrations/zenoti/stock_reconciliation.py index e97c3a40b..6df6b1887 100644 --- a/ecommerce_integrations/zenoti/stock_reconciliation.py +++ b/ecommerce_integrations/zenoti/stock_reconciliation.py @@ -30,7 +30,7 @@ def process_stock_reconciliation(center, error_logs, date=None): def retrieve_stock_quantities_of_products(center, date): - url = api_url + "inventory/stock?center_id={0}&inventory_date={1}".format(center, date) + url = api_url + f"inventory/stock?center_id={center}&inventory_date={date}" stock_quantities_of_products = make_api_call(url) return stock_quantities_of_products diff --git a/ecommerce_integrations/zenoti/utils.py b/ecommerce_integrations/zenoti/utils.py index 560795d45..97d72c6fc 100644 --- a/ecommerce_integrations/zenoti/utils.py +++ b/ecommerce_integrations/zenoti/utils.py @@ -276,7 +276,7 @@ def get_state(country_id, state_id): if list_of_states_of_the_country: for states in list_of_states_of_the_country["states"]: if states["id"] == state_id: - state == states + state = states return state From 9511eabcdb77bc473f33490ea258701d2e8e2b09 Mon Sep 17 00:00:00 2001 From: ravibharathi656 Date: Wed, 15 Apr 2026 16:58:34 +0530 Subject: [PATCH 06/11] fix: semgrep issue --- .../amazon_sp_api_settings/amazon_sp_api.py | 20 ++--- .../controllers/inventory.py | 80 ++++++++++--------- .../unicommerce_shipment_manifest.py | 6 +- 3 files changed, 57 insertions(+), 49 deletions(-) diff --git a/ecommerce_integrations/amazon/doctype/amazon_sp_api_settings/amazon_sp_api.py b/ecommerce_integrations/amazon/doctype/amazon_sp_api_settings/amazon_sp_api.py index d277389ae..decdcccbe 100644 --- a/ecommerce_integrations/amazon/doctype/amazon_sp_api_settings/amazon_sp_api.py +++ b/ecommerce_integrations/amazon/doctype/amazon_sp_api_settings/amazon_sp_api.py @@ -113,7 +113,7 @@ def __call__(self, request): uri = parsed_url.path if len(parsed_url.query) > 0: - query_string = dict(map(lambda i: i.split("="), parsed_url.query.split("&"))) + query_string = dict(i.split("=") for i in parsed_url.query.split("&")) else: query_string = dict() @@ -132,8 +132,7 @@ def __call__(self, request): # http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html # Query string values must be URL-encoded (space=%20) and be sorted by name. - canonical_query_string = "&".join(map(lambda p: "=".join(p), sorted(query_string.items()))) - + canonical_query_string = "&".join("=".join(p) for p in sorted(query_string.items())) # Create payload hash (hash of the request body content). if request.method == "GET": payload_hash = hashlib.sha256(b"").hexdigest() @@ -150,13 +149,14 @@ def __call__(self, request): # Create the canonical headers and signed headers. Header names # must be trimmed and lowercase, and sorted in code point order from # low to high. Note that there is a trailing \n. - headers_to_sign = sorted( - filter( - lambda h: h.startswith("x-amz-") or h == "host", - map(lambda H: H.lower(), request.headers.keys()), - ) - ) - canonical_headers = "".join(map(lambda h: ":".join((h, request.headers[h])) + "\n", headers_to_sign)) + headers_to_sign = [] + for header in request.headers.keys(): + header = header.lower() + if header.startswith("x-amz-") or header == "host": + headers_to_sign.append(header) + headers_to_sign.sort() + + canonical_headers = "".join(f"{h}:{request.headers[h]}\n" for h in headers_to_sign) signed_headers = ";".join(headers_to_sign) # Combine elements to create canonical request. diff --git a/ecommerce_integrations/controllers/inventory.py b/ecommerce_integrations/controllers/inventory.py index 59b02f352..05918e8ee 100644 --- a/ecommerce_integrations/controllers/inventory.py +++ b/ecommerce_integrations/controllers/inventory.py @@ -1,5 +1,6 @@ import frappe from frappe import _dict +from frappe.query_builder.functions import Max, Sum from frappe.utils import now from frappe.utils.nestedset import get_descendants_of @@ -14,21 +15,26 @@ def get_inventory_levels(warehouses: tuple[str], integration: str) -> list[_dict returns: list of _dict containing ecom_item, item_code, integration_item_code, variant_id, actual_qty, warehouse, reserved_qty """ - data = frappe.db.sql( - f""" - SELECT ei.name as ecom_item, bin.item_code as item_code, integration_item_code, variant_id, actual_qty, warehouse, reserved_qty - FROM `tabEcommerce Item` ei - JOIN tabBin bin - ON ei.erpnext_item_code = bin.item_code - WHERE bin.warehouse in ({', '.join('%s' for _ in warehouses)}) - AND bin.modified > ei.inventory_synced_on - AND ei.integration = %s - """, - values=(*warehouses, integration), - as_dict=1, - ) - - return data + bin = frappe.qb.DocType("Bin") + ecommerce_item = frappe.qb.DocType("Ecommerce Item") + + return ( + frappe.qb.from_(ecommerce_item) + .join(bin) + .on(ecommerce_item.erpnext_item_code == bin.item_code) + .select( + ecommerce_item.name.as_("ecom_item"), + bin.item_code, + ecommerce_item.integration_item_code, + ecommerce_item.variant_id, + bin.actual_qty, + bin.warehouse, + bin.reserved_qty, + ) + .where(bin.warehouse.isin(warehouses)) + .where(bin.modified > ecommerce_item.inventory_synced_on) + .where(ecommerce_item.integration == integration) + ).run(as_dict=True) def get_inventory_levels_of_group_warehouse(warehouse: str, integration: str): @@ -40,28 +46,28 @@ def get_inventory_levels_of_group_warehouse(warehouse: str, integration: str): child_warehouse = get_descendants_of("Warehouse", warehouse) all_warehouses = (*tuple(child_warehouse), warehouse) - data = frappe.db.sql( - f""" - SELECT ei.name as ecom_item, bin.item_code as item_code, - integration_item_code, - variant_id, - sum(actual_qty) as actual_qty, - sum(reserved_qty) as reserved_qty, - max(bin.modified) as last_updated, - max(ei.inventory_synced_on) as last_synced - FROM `tabEcommerce Item` ei - JOIN tabBin bin - ON ei.erpnext_item_code = bin.item_code - WHERE bin.warehouse in ({', '.join(['%s'] * len(all_warehouses))}) - AND integration = %s - GROUP BY - ei.erpnext_item_code - HAVING - last_updated > last_synced - """, - values=(*all_warehouses, integration), - as_dict=1, - ) + bin = frappe.qb.DocType("Bin") + ecommerce_item = frappe.qb.DocType("Ecommerce Item") + + data = ( + frappe.qb.from_(ecommerce_item) + .join(bin) + .on(ecommerce_item.erpnext_item_code == bin.item_code) + .select( + ecommerce_item.name.as_("ecom_item"), + bin.item_code, + ecommerce_item.integration_item_code, + ecommerce_item.variant_id, + Sum(bin.actual_qty).as_("actual_qty"), + Sum(bin.reserved_qty).as_("reserved_qty"), + Max(bin.modified).as_("last_updated"), + Max(ecommerce_item.inventory_synced_on).as_("last_synced"), + ) + .where(bin.warehouse.isin(all_warehouses)) + .where(ecommerce_item.integration == integration) + .groupby(ecommerce_item.erpnext_item_code) + .having(Max(bin.modified) > Max(ecommerce_item.inventory_synced_on)) + ).run(as_dict=True) # add warehouse as group warehouse for sending to integrations for item in data: diff --git a/ecommerce_integrations/unicommerce/doctype/unicommerce_shipment_manifest/unicommerce_shipment_manifest.py b/ecommerce_integrations/unicommerce/doctype/unicommerce_shipment_manifest/unicommerce_shipment_manifest.py index 03f6595ae..61ddf87f2 100644 --- a/ecommerce_integrations/unicommerce/doctype/unicommerce_shipment_manifest/unicommerce_shipment_manifest.py +++ b/ecommerce_integrations/unicommerce/doctype/unicommerce_shipment_manifest/unicommerce_shipment_manifest.py @@ -183,9 +183,11 @@ def search_packages(search_term: str, channel: str | None = None, shipper: str | @frappe.whitelist() -def get_shipping_package_list(source_name, target_doc=None): +def get_shipping_package_list(source_name: str, target_doc: dict | str | None = None) -> dict: if target_doc and isinstance(target_doc, str): - target_doc = json.loads(target_doc) + target_doc = frappe._dict(json.loads(target_doc)) + elif target_doc is None: + target_doc = frappe._dict() target_doc.setdefault("manifest_items", []).append({"sales_invoice": source_name}) From eae814347776af76cc94504478ec36675c651a6e Mon Sep 17 00:00:00 2001 From: ravibharathi656 Date: Thu, 16 Apr 2026 12:12:26 +0530 Subject: [PATCH 07/11] refactor: clean up load_fixture path handling --- ecommerce_integrations/unicommerce/tests/utils.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ecommerce_integrations/unicommerce/tests/utils.py b/ecommerce_integrations/unicommerce/tests/utils.py index d55d5ddce..a3bcf6369 100644 --- a/ecommerce_integrations/unicommerce/tests/utils.py +++ b/ecommerce_integrations/unicommerce/tests/utils.py @@ -71,9 +71,8 @@ def tearDownClass(cls): frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 0) def load_fixture(self, name): - with open(os.path.dirname(__file__) + f"/fixtures/{name}.json", "rb") as f: - data = f.read() - return json.loads(data) + fixture_path = os.path.join(os.path.dirname(__file__), "fixtures", f"{name}.json") + return frappe.get_file_json(fixture_path) def _setup_test_item_categories(): From 9044d7187f4ea48fc1fa8294d042c0cf83b16612 Mon Sep 17 00:00:00 2001 From: ravibharathi656 Date: Fri, 17 Apr 2026 12:37:37 +0530 Subject: [PATCH 08/11] fix(unicommerce): dn creation --- .../unicommerce/delivery_note.py | 62 ++++--------------- 1 file changed, 13 insertions(+), 49 deletions(-) diff --git a/ecommerce_integrations/unicommerce/delivery_note.py b/ecommerce_integrations/unicommerce/delivery_note.py index 831005a94..0af339bd0 100644 --- a/ecommerce_integrations/unicommerce/delivery_note.py +++ b/ecommerce_integrations/unicommerce/delivery_note.py @@ -46,52 +46,16 @@ def prepare_delivery_note(): def create_delivery_note(so, sales_invoice): - try: - # Create the delivery note - from frappe.model.mapper import make_mapped_doc - - res = make_mapped_doc( - method="erpnext.selling.doctype.sales_order.sales_order.make_delivery_note", source_name=so.name - ) - res.update({"items": []}) - for item in sales_invoice.items: - res.append( - "items", - { - "item_code": item.item_code, - "item_name": item.item_name, - "description": item.description, - "qty": item.qty, - "uom": item.uom, - "rate": item.rate, - "amount": item.amount, - "warehouse": item.warehouse, - "against_sales_order": item.sales_order, - "batch_no": item.batch_no, - "so_detail": item.so_detail, - }, - ) - for item in sales_invoice.taxes: - res.append( - "taxes", - { - "charge_type": item.charge_type, - "account_head": item.account_head, - "tax_amount": item.tax_amount, - "description": item.description, - "item_wise_tax_detail": item.item_wise_tax_detail, - "dont_recompute_tax": item.dont_recompute_tax, - }, - ) - res.unicommerce_order_code = sales_invoice.unicommerce_order_code - res.unicommerce_shipment_id = sales_invoice.unicommerce_shipping_package_code - res.save() - res.submit() - log = create_unicommerce_log(method="create_delevery_note", make_new=True) - frappe.flags.request_id = log.name - except Exception as e: - create_unicommerce_log(status="Error", exception=e, rollback=True) - else: - create_unicommerce_log(status="Success") - frappe.flags.request_id = None - return res + # Create the delivery note + from erpnext.selling.doctype.sales_order.sales_order import make_delivery_note + + res = make_delivery_note(source_name=so.name) + res.unicommerce_order_code = sales_invoice.unicommerce_order_code + res.unicommerce_shipment_id = sales_invoice.unicommerce_shipping_package_code + res.save() + res.submit() + log = create_unicommerce_log(method="create_delevery_note", make_new=True) + frappe.flags.request_id = log.name + create_unicommerce_log(status="Success") + frappe.flags.request_id = None + return res From c1fe546febc9e90b09a14ac3e99618eae72d82b5 Mon Sep 17 00:00:00 2001 From: ravibharathi656 Date: Fri, 17 Apr 2026 13:36:55 +0530 Subject: [PATCH 09/11] ci: skip codecov upload for pull requests --- .github/workflows/ci.yml | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 021afcc2c..8386c843c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,6 +30,8 @@ jobs: tests: runs-on: ubuntu-latest timeout-minutes: 20 + env: + WITH_COVERAGE: ${{ github.event_name != 'pull_request' }} strategy: fail-fast: false @@ -104,13 +106,31 @@ jobs: - name: Run Tests - run: cd ~/frappe-bench/ && bench --site test_site run-tests --app ecommerce_integrations --coverage + run: cd ~/frappe-bench/ && bench --site test_site run-tests --app ecommerce_integrations ${{ env.WITH_COVERAGE == 'true' && '--coverage' || '' }} env: TYPE: server + - name: Upload coverage data + uses: actions/upload-artifact@v4 + if: github.event_name != 'pull_request' + with: + name: coverage + path: /home/runner/frappe-bench/sites/coverage.xml + + coverage: + name: Coverage Wrap Up + needs: tests + runs-on: ubuntu-latest + if: ${{ github.event_name != 'pull_request' }} + steps: + - name: Clone + uses: actions/checkout@v4 + + - name: Download artifacts + uses: actions/download-artifact@v4 + - name: Upload coverage data uses: codecov/codecov-action@v2 with: fail_ci_if_error: true - files: /home/runner/frappe-bench/sites/coverage.xml verbose: true From 98c1d23f2d03bb67cf19148aaa7d21d2c3e16bc6 Mon Sep 17 00:00:00 2001 From: ravibharathi656 Date: Wed, 29 Apr 2026 12:07:51 +0530 Subject: [PATCH 10/11] ci: update github actions versions --- .github/workflows/ci.yml | 2 +- .../unicommerce/delivery_note.py | 30 +++++++++++-------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8386c843c..1a56df1f3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,7 +52,7 @@ jobs: uses: actions/checkout@v4 - name: Setup Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: '3.10' diff --git a/ecommerce_integrations/unicommerce/delivery_note.py b/ecommerce_integrations/unicommerce/delivery_note.py index 0af339bd0..4856bb35f 100644 --- a/ecommerce_integrations/unicommerce/delivery_note.py +++ b/ecommerce_integrations/unicommerce/delivery_note.py @@ -46,16 +46,20 @@ def prepare_delivery_note(): def create_delivery_note(so, sales_invoice): - # Create the delivery note - from erpnext.selling.doctype.sales_order.sales_order import make_delivery_note - - res = make_delivery_note(source_name=so.name) - res.unicommerce_order_code = sales_invoice.unicommerce_order_code - res.unicommerce_shipment_id = sales_invoice.unicommerce_shipping_package_code - res.save() - res.submit() - log = create_unicommerce_log(method="create_delevery_note", make_new=True) - frappe.flags.request_id = log.name - create_unicommerce_log(status="Success") - frappe.flags.request_id = None - return res + try: + # Create the delivery note + from erpnext.selling.doctype.sales_order.sales_order import make_delivery_note + + res = make_delivery_note(source_name=so.name) + res.unicommerce_order_code = sales_invoice.unicommerce_order_code + res.unicommerce_shipment_id = sales_invoice.unicommerce_shipping_package_code + res.save() + res.submit() + log = create_unicommerce_log(method="create_delevery_note", make_new=True) + frappe.flags.request_id = log.name + except Exception as e: + create_unicommerce_log(status="Error", exception=e, rollback=True) + else: + create_unicommerce_log(status="Success") + frappe.flags.request_id = None + return res From 22d7babf2b1f8d95fd0ce861b9125c4be7c6ea51 Mon Sep 17 00:00:00 2001 From: ravibharathi656 Date: Wed, 29 Apr 2026 12:21:41 +0530 Subject: [PATCH 11/11] ci: update concurrency group name --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1a56df1f3..ca62cf088 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ env: ECOMMERCE_BRANCH: ${{ github.base_ref || github.ref_name }} concurrency: - group: develop-${{ github.event.number }} + group: ci-${{ github.event.number || github.ref_name }} cancel-in-progress: true jobs: