From e28e6726f17954ba292a57e0c90d4b28a143f5e9 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 14 Apr 2022 18:34:47 +0530 Subject: [PATCH 01/24] fix: SO analysis rpt will fetch SO's without Delivery note as well --- .../selling/report/sales_order_analysis/sales_order_analysis.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py index a41051331f..dcfb10a9d5 100644 --- a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py +++ b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py @@ -81,7 +81,7 @@ def get_data(conditions, filters): ON sii.so_detail = soi.name and sii.docstatus = 1) LEFT JOIN `tabDelivery Note Item` dni on dni.so_detail = soi.name - RIGHT JOIN `tabDelivery Note` dn + LEFT JOIN `tabDelivery Note` dn on dni.parent = dn.name and dn.docstatus = 1 WHERE soi.parent = so.name From 13487e240880306f2e5341eaba4dc350e1e97783 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sat, 16 Apr 2022 10:25:44 +0530 Subject: [PATCH 02/24] test: Sales order analysis report --- .../test_sales_order_analysis.py | 166 ++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 erpnext/selling/report/sales_order_analysis/test_sales_order_analysis.py diff --git a/erpnext/selling/report/sales_order_analysis/test_sales_order_analysis.py b/erpnext/selling/report/sales_order_analysis/test_sales_order_analysis.py new file mode 100644 index 0000000000..25cbb73449 --- /dev/null +++ b/erpnext/selling/report/sales_order_analysis/test_sales_order_analysis.py @@ -0,0 +1,166 @@ +import frappe +from frappe.tests.utils import FrappeTestCase +from frappe.utils import add_days + +from erpnext.selling.doctype.sales_order.sales_order import make_delivery_note, make_sales_invoice +from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order +from erpnext.selling.report.sales_order_analysis.sales_order_analysis import execute +from erpnext.stock.doctype.item.test_item import create_item + +test_dependencies = ["Sales Order", "Item", "Sales Invoice", "Delivery Note"] + + +class TestSalesOrderAnalysis(FrappeTestCase): + def create_sales_order(self, transaction_date): + item = create_item(item_code="_Test Excavator", is_stock_item=0) + so = make_sales_order( + transaction_date=transaction_date, + item=item.item_code, + qty=10, + rate=100000, + do_not_save=True, + ) + so.po_no = "" + so.taxes_and_charges = "" + so.taxes = "" + so.items[0].delivery_date = add_days(transaction_date, 15) + so.save() + so.submit() + return item, so + + def create_sales_invoice(self, so): + sinv = make_sales_invoice(so.name) + sinv.posting_date = so.transaction_date + sinv.taxes_and_charges = "" + sinv.taxes = "" + sinv.insert() + sinv.submit() + return sinv + + def create_delivery_note(self, so): + dn = make_delivery_note(so.name) + dn.set_posting_time = True + dn.posting_date = add_days(so.transaction_date, 1) + dn.save() + dn.submit() + return dn + + def test_01_so_to_deliver_and_bill(self): + transaction_date = "2021-06-01" + item, so = self.create_sales_order(transaction_date) + columns, data, message, chart = execute( + { + "company": "_Test Company", + "from_date": "2021-06-01", + "to_date": "2021-06-30", + "status": ["To Deliver and Bill"], + } + ) + expected_value = { + "status": "To Deliver and Bill", + "sales_order": so.name, + "delay_days": frappe.utils.date_diff(frappe.utils.datetime.date.today(), so.delivery_date), + "qty": 10, + "delivered_qty": 0, + "pending_qty": 10, + "qty_to_bill": 10, + "time_taken_to_deliver": 0, + } + self.assertEqual(len(data), 1) + for key, val in expected_value.items(): + with self.subTest(key=key, val=val): + self.assertEqual(data[0][key], val) + + def test_02_so_to_deliver(self): + transaction_date = "2021-06-01" + item, so = self.create_sales_order(transaction_date) + self.create_sales_invoice(so) + columns, data, message, chart = execute( + { + "company": "_Test Company", + "from_date": "2021-06-01", + "to_date": "2021-06-30", + "status": ["To Deliver"], + } + ) + expected_value = { + "status": "To Deliver", + "sales_order": so.name, + "delay_days": frappe.utils.date_diff(frappe.utils.datetime.date.today(), so.delivery_date), + "qty": 10, + "delivered_qty": 0, + "pending_qty": 10, + "qty_to_bill": 0, + "time_taken_to_deliver": 0, + } + self.assertEqual(len(data), 1) + for key, val in expected_value.items(): + with self.subTest(key=key, val=val): + self.assertEqual(data[0][key], val) + + def test_03_so_to_bill(self): + transaction_date = "2021-06-01" + item, so = self.create_sales_order(transaction_date) + self.create_delivery_note(so) + columns, data, message, chart = execute( + { + "company": "_Test Company", + "from_date": "2021-06-01", + "to_date": "2021-06-30", + "status": ["To Bill"], + } + ) + expected_value = { + "status": "To Bill", + "sales_order": so.name, + "delay_days": frappe.utils.date_diff(frappe.utils.datetime.date.today(), so.delivery_date), + "qty": 10, + "delivered_qty": 10, + "pending_qty": 0, + "qty_to_bill": 10, + "time_taken_to_deliver": 86400, + } + self.assertEqual(len(data), 1) + for key, val in expected_value.items(): + with self.subTest(key=key, val=val): + self.assertEqual(data[0][key], val) + + def test_04_so_completed(self): + transaction_date = "2021-06-01" + item, so = self.create_sales_order(transaction_date) + self.create_sales_invoice(so) + self.create_delivery_note(so) + columns, data, message, chart = execute( + { + "company": "_Test Company", + "from_date": "2021-06-01", + "to_date": "2021-06-30", + "status": ["Completed"], + } + ) + expected_value = { + "status": "Completed", + "sales_order": so.name, + "delay_days": frappe.utils.date_diff(frappe.utils.datetime.date.today(), so.delivery_date), + "qty": 10, + "delivered_qty": 10, + "pending_qty": 0, + "qty_to_bill": 0, + "billed_qty": 10, + "time_taken_to_deliver": 86400, + } + self.assertEqual(len(data), 1) + for key, val in expected_value.items(): + with self.subTest(key=key, val=val): + self.assertEqual(data[0][key], val) + + def test_05_all_so_status(self): + columns, data, message, chart = execute( + { + "company": "_Test Company", + "from_date": "2021-06-01", + "to_date": "2021-06-30", + } + ) + # SO's from first 4 test cases should be in output + self.assertEqual(len(data), 4) From 9c081947ec65c86ed54b4bddf022ef50b16cbbec Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 18 Apr 2022 10:21:15 +0530 Subject: [PATCH 03/24] fix: Price changing on creating Sales retrun from Delivery Note --- erpnext/public/js/controllers/transaction.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index c9faf683f9..767221e99d 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1388,6 +1388,11 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe return; } + // Target doc created from a mapped doc + if (this.frm.doc.__onload && this.frm.doc.__onload.ignore_price_list) { + return; + } + return this.frm.call({ method: "erpnext.accounts.doctype.pricing_rule.pricing_rule.apply_pricing_rule", args: { args: args, doc: me.frm.doc }, @@ -1504,7 +1509,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe me.remove_pricing_rule(frappe.get_doc(d.doctype, d.name)); } - if (d.free_item_data) { + if (d.free_item_data.length > 0) { me.apply_product_discount(d); } From 41249c57c436e8ceb4b1737a7de824f8b7e80dbd Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 18 Apr 2022 10:38:22 +0530 Subject: [PATCH 04/24] chore: Add sematic releases --- .github/workflows/release.yml | 25 +++++++++++++++++++++++++ .releaserc | 25 +++++++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 .github/workflows/release.yml create mode 100644 .releaserc diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000000..0fff48a267 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,25 @@ +name: Generate Semantic Release +on: + push: + branches: + - test-release +jobs: + release: + name: Release + runs-on: ubuntu-18.04 + steps: + - name: Checkout Entire Repository + uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Setup Node.js v14 + uses: actions/setup-node@v2 + with: + node-version: 14 + - name: Setup dependencies + run: | + npm install @semantic-release/git @semantic-release/exec --no-save + - name: Create Release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: npx semantic-release \ No newline at end of file diff --git a/.releaserc b/.releaserc new file mode 100644 index 0000000000..4ac5466f6c --- /dev/null +++ b/.releaserc @@ -0,0 +1,25 @@ +{ + "branches": ["version-13"], + "plugins": [ + "@semantic-release/commit-analyzer", + "@semantic-release/release-notes-generator", + [ + "@semantic-release/exec", { + "prepareCmd": 'sed -ir "s/[0-9]*\.[0-9]*\.[0-9]*/${nextRelease.version}/" erpnext/__init__.py' + } + ], + [ + "@semantic-release/git", { + "assets": ["erpnext/__init__.py"], + "message": "chore(release): Bumped to Version ${nextRelease.version}\n\n${nextRelease.notes}" + } + ], + [ + "@semantic-release/github", { + "assets": [ + {"path": "dist/*"}, + ] + } + ] + ] +} \ No newline at end of file From cc1bdd426b188d450fac9f7604ad6e9b52696d59 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 18 Apr 2022 10:48:31 +0530 Subject: [PATCH 05/24] chore: Update branch name --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0fff48a267..5c4ece5416 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,7 +2,7 @@ name: Generate Semantic Release on: push: branches: - - test-release + - version-13 jobs: release: name: Release From c12a36aed90c82794a1092d1f5a1d9ff25d40040 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 18 Apr 2022 13:05:56 +0530 Subject: [PATCH 06/24] chore: block major releases --- .releaserc | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.releaserc b/.releaserc index 4ac5466f6c..4d2c1333f0 100644 --- a/.releaserc +++ b/.releaserc @@ -1,7 +1,12 @@ { "branches": ["version-13"], "plugins": [ - "@semantic-release/commit-analyzer", + "@semantic-release/commit-analyzer", { + "preset": "angular", + "releaseRules": [ + {"breaking": true, "release": false} + ] + }, "@semantic-release/release-notes-generator", [ "@semantic-release/exec", { From 6fc11cb4c57264574cd64ccf588feae88b1236e5 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 18 Apr 2022 16:17:36 +0530 Subject: [PATCH 07/24] ci: use latest ubuntu container --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5c4ece5416..532485f21f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,7 +6,7 @@ on: jobs: release: name: Release - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest steps: - name: Checkout Entire Repository uses: actions/checkout@v2 From e0a9a69d764c015ea6be287b31686ebb764a7067 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 18 Apr 2022 16:45:01 +0530 Subject: [PATCH 08/24] chore: do not publish any assets --- .releaserc | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/.releaserc b/.releaserc index 4d2c1333f0..8a758ed30a 100644 --- a/.releaserc +++ b/.releaserc @@ -19,12 +19,6 @@ "message": "chore(release): Bumped to Version ${nextRelease.version}\n\n${nextRelease.notes}" } ], - [ - "@semantic-release/github", { - "assets": [ - {"path": "dist/*"}, - ] - } - ] + "@semantic-release/github" ] } \ No newline at end of file From c993ac09dfb29ff6ce6e80baf7d5f981255405c2 Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 18 Apr 2022 18:01:48 +0530 Subject: [PATCH 09/24] fix: Query filter fields from Website Item instead of Item master - tweak `filters.py` to correctly query filter field values from Website Item - Use Website Item for filter field options in Settings and Item Group Field Filter table --- .../e_commerce_settings/e_commerce_settings.js | 6 +++--- .../e_commerce/product_data_engine/filters.py | 16 +++++++++++----- erpnext/setup/doctype/item_group/item_group.js | 10 +++++----- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.js b/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.js index 6302d260e0..a8966b07a7 100644 --- a/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.js +++ b/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.js @@ -24,17 +24,17 @@ frappe.ui.form.on("E Commerce Settings", { ); } - frappe.model.with_doctype("Item", () => { + frappe.model.with_doctype("Website Item", () => { const web_item_meta = frappe.get_meta('Website Item'); const valid_fields = web_item_meta.fields.filter( df => ["Link", "Table MultiSelect"].includes(df.fieldtype) && !df.hidden ).map(df => ({ label: df.label, value: df.fieldname })); - frm.fields_dict.filter_fields.grid.update_docfield_property( + frm.get_field("filter_fields").grid.update_docfield_property( 'fieldname', 'fieldtype', 'Select' ); - frm.fields_dict.filter_fields.grid.update_docfield_property( + frm.get_field("filter_fields").grid.update_docfield_property( 'fieldname', 'options', valid_fields ); }); diff --git a/erpnext/e_commerce/product_data_engine/filters.py b/erpnext/e_commerce/product_data_engine/filters.py index feb89c1830..e5e5e97f86 100644 --- a/erpnext/e_commerce/product_data_engine/filters.py +++ b/erpnext/e_commerce/product_data_engine/filters.py @@ -22,12 +22,14 @@ class ProductFiltersBuilder: fields, filter_data = [], [] filter_fields = [row.fieldname for row in self.doc.filter_fields] # fields in settings - # filter valid field filters i.e. those that exist in Item - item_meta = frappe.get_meta("Item", cached=True) - fields = [item_meta.get_field(field) for field in filter_fields if item_meta.has_field(field)] + # filter valid field filters i.e. those that exist in Website Item + web_item_meta = frappe.get_meta("Website Item", cached=True) + fields = [ + web_item_meta.get_field(field) for field in filter_fields if web_item_meta.has_field(field) + ] for df in fields: - item_filters, item_or_filters = {"published_in_website": 1}, [] + item_filters, item_or_filters = {"published": 1}, [] link_doctype_values = self.get_filtered_link_doctype_records(df) if df.fieldtype == "Link": @@ -50,9 +52,13 @@ class ProductFiltersBuilder: ] ) + # exclude variants if mentioned in settings + if frappe.db.get_single_value("E Commerce Settings", "hide_variants"): + item_filters["variant_of"] = ["is", "not set"] + # Get link field values attached to published items item_values = frappe.get_all( - "Item", + "Website Item", fields=[df.fieldname], filters=item_filters, or_filters=item_or_filters, diff --git a/erpnext/setup/doctype/item_group/item_group.js b/erpnext/setup/doctype/item_group/item_group.js index f570c2faec..cf96dc1a7d 100644 --- a/erpnext/setup/doctype/item_group/item_group.js +++ b/erpnext/setup/doctype/item_group/item_group.js @@ -72,17 +72,17 @@ frappe.ui.form.on("Item Group", { }); } - frappe.model.with_doctype('Item', () => { - const item_meta = frappe.get_meta('Item'); + frappe.model.with_doctype('Website Item', () => { + const web_item_meta = frappe.get_meta('Website Item'); - const valid_fields = item_meta.fields.filter( + const valid_fields = web_item_meta.fields.filter( df => ['Link', 'Table MultiSelect'].includes(df.fieldtype) && !df.hidden ).map(df => ({ label: df.label, value: df.fieldname })); - frm.fields_dict.filter_fields.grid.update_docfield_property( + frm.get_field("filter_fields").grid.update_docfield_property( 'fieldname', 'fieldtype', 'Select' ); - frm.fields_dict.filter_fields.grid.update_docfield_property( + frm.get_field("filter_fields").grid.update_docfield_property( 'fieldname', 'options', valid_fields ); }); From d35b37881bd8f7957ba32e321072596d20d2041d Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 18 Apr 2022 18:50:58 +0530 Subject: [PATCH 10/24] fix: Validate field filter wrt to Website Item & re-use validation in Item Group --- .../e_commerce_settings.py | 19 ++++++++++--------- .../setup/doctype/item_group/item_group.py | 2 ++ 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.py b/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.py index f85667e1b2..c27d29a62c 100644 --- a/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.py +++ b/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.py @@ -27,7 +27,7 @@ class ECommerceSettings(Document): self.is_redisearch_loaded = is_search_module_loaded() def validate(self): - self.validate_field_filters() + self.validate_field_filters(self.filter_fields, self.enable_field_filters) self.validate_attribute_filters() self.validate_checkout() self.validate_search_index_fields() @@ -51,21 +51,22 @@ class ECommerceSettings(Document): define_autocomplete_dictionary() create_website_items_index() - def validate_field_filters(self): - if not (self.enable_field_filters and self.filter_fields): + @staticmethod + def validate_field_filters(filter_fields, enable_field_filters): + if not (enable_field_filters and filter_fields): return - item_meta = frappe.get_meta("Item") + web_item_meta = frappe.get_meta("Website Item") valid_fields = [ - df.fieldname for df in item_meta.fields if df.fieldtype in ["Link", "Table MultiSelect"] + df.fieldname for df in web_item_meta.fields if df.fieldtype in ["Link", "Table MultiSelect"] ] - for f in self.filter_fields: - if f.fieldname not in valid_fields: + for row in filter_fields: + if row.fieldname not in valid_fields: frappe.throw( _( - "Filter Fields Row #{0}: Fieldname {1} must be of type 'Link' or 'Table MultiSelect'" - ).format(f.idx, f.fieldname) + "Filter Fields Row #{0}: Fieldname {1} must be of type 'Link' or 'Table MultiSelect'" + ).format(row.idx, frappe.bold(row.fieldname)) ) def validate_attribute_filters(self): diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py index 35557a5047..6e940d0cfd 100644 --- a/erpnext/setup/doctype/item_group/item_group.py +++ b/erpnext/setup/doctype/item_group/item_group.py @@ -11,6 +11,7 @@ from frappe.utils.nestedset import NestedSet from frappe.website.utils import clear_cache from frappe.website.website_generator import WebsiteGenerator +from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import ECommerceSettings from erpnext.e_commerce.product_data_engine.filters import ProductFiltersBuilder @@ -35,6 +36,7 @@ class ItemGroup(NestedSet, WebsiteGenerator): self.make_route() self.validate_item_group_defaults() + ECommerceSettings.validate_field_filters(self.filter_fields, enable_field_filters=True) def on_update(self): NestedSet.on_update(self) From 1e80b97915d38c2f4fd51baade606c83098db51b Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 18 Apr 2022 19:01:43 +0530 Subject: [PATCH 11/24] refactor: Change Filter Fields table fieldtype to `Autocomplete` - Remove dynamic js fieldtype change to `Select` --- .../e_commerce_settings.js | 11 +-- .../website_filter_field.json | 97 +++++-------------- .../setup/doctype/item_group/item_group.js | 11 +-- 3 files changed, 36 insertions(+), 83 deletions(-) diff --git a/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.js b/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.js index a8966b07a7..69b9cfaa68 100644 --- a/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.js +++ b/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.js @@ -27,13 +27,12 @@ frappe.ui.form.on("E Commerce Settings", { frappe.model.with_doctype("Website Item", () => { const web_item_meta = frappe.get_meta('Website Item'); - const valid_fields = web_item_meta.fields.filter( - df => ["Link", "Table MultiSelect"].includes(df.fieldtype) && !df.hidden - ).map(df => ({ label: df.label, value: df.fieldname })); - - frm.get_field("filter_fields").grid.update_docfield_property( - 'fieldname', 'fieldtype', 'Select' + const valid_fields = web_item_meta.fields.filter(df => + ["Link", "Table MultiSelect"].includes(df.fieldtype) && !df.hidden + ).map(df => + ({ label: df.label, value: df.fieldname }) ); + frm.get_field("filter_fields").grid.update_docfield_property( 'fieldname', 'options', valid_fields ); diff --git a/erpnext/portal/doctype/website_filter_field/website_filter_field.json b/erpnext/portal/doctype/website_filter_field/website_filter_field.json index 67c0d0ae73..45543a6d2c 100644 --- a/erpnext/portal/doctype/website_filter_field/website_filter_field.json +++ b/erpnext/portal/doctype/website_filter_field/website_filter_field.json @@ -1,76 +1,31 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2018-12-31 17:06:08.716134", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "creation": "2018-12-31 17:06:08.716134", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "fieldname" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "fieldname", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Fieldname", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldname": "fieldname", + "fieldtype": "Autocomplete", + "in_list_view": 1, + "label": "Fieldname" } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2019-01-01 18:26:11.550380", - "modified_by": "Administrator", - "module": "Portal", - "name": "Website Filter Field", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + ], + "istable": 1, + "links": [], + "modified": "2022-04-18 18:55:17.835666", + "modified_by": "Administrator", + "module": "Portal", + "name": "Website Filter Field", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "states": [], + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/setup/doctype/item_group/item_group.js b/erpnext/setup/doctype/item_group/item_group.js index cf96dc1a7d..4b04ac1d5e 100644 --- a/erpnext/setup/doctype/item_group/item_group.js +++ b/erpnext/setup/doctype/item_group/item_group.js @@ -75,13 +75,12 @@ frappe.ui.form.on("Item Group", { frappe.model.with_doctype('Website Item', () => { const web_item_meta = frappe.get_meta('Website Item'); - const valid_fields = web_item_meta.fields.filter( - df => ['Link', 'Table MultiSelect'].includes(df.fieldtype) && !df.hidden - ).map(df => ({ label: df.label, value: df.fieldname })); - - frm.get_field("filter_fields").grid.update_docfield_property( - 'fieldname', 'fieldtype', 'Select' + const valid_fields = web_item_meta.fields.filter(df => + ['Link', 'Table MultiSelect'].includes(df.fieldtype) && !df.hidden + ).map(df => + ({ label: df.label, value: df.fieldname }) ); + frm.get_field("filter_fields").grid.update_docfield_property( 'fieldname', 'options', valid_fields ); From d0cd1943980772f5596f052da257f5de8a889c11 Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 18 Apr 2022 21:40:19 +0530 Subject: [PATCH 12/24] chore: Patch to copy custom fields (field filters) from Item to Website Item --- erpnext/patches.txt | 1 + ...py_custom_field_filters_to_website_item.py | 54 +++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 erpnext/patches/v13_0/copy_custom_field_filters_to_website_item.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 63b6bb73e8..fb1020e223 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -364,3 +364,4 @@ erpnext.patches.v13_0.set_return_against_in_pos_invoice_references erpnext.patches.v13_0.remove_unknown_links_to_prod_plan_items # 24-03-2022 erpnext.patches.v13_0.update_expense_claim_status_for_paid_advances erpnext.patches.v13_0.create_gst_custom_fields_in_quotation +erpnext.patches.v13_0.copy_custom_field_filters_to_website_item #22 diff --git a/erpnext/patches/v13_0/copy_custom_field_filters_to_website_item.py b/erpnext/patches/v13_0/copy_custom_field_filters_to_website_item.py new file mode 100644 index 0000000000..5f2125144f --- /dev/null +++ b/erpnext/patches/v13_0/copy_custom_field_filters_to_website_item.py @@ -0,0 +1,54 @@ +import frappe +from frappe.custom.doctype.custom_field.custom_field import create_custom_field + + +def execute(): + "Add Field Filters, that are not standard fields in Website Item, as Custom Fields." + settings = frappe.get_doc("E Commerce Settings") + + if not (settings.filter_fields or settings.field_filters): + return + + item_meta = frappe.get_meta("Item") + valid_item_fields = [ + df.fieldname for df in item_meta.fields if df.fieldtype in ["Link", "Table MultiSelect"] + ] + + web_item_meta = frappe.get_meta("Website Item") + valid_web_item_fields = [ + df.fieldname for df in web_item_meta.fields if df.fieldtype in ["Link", "Table MultiSelect"] + ] + + for row in settings.filter_fields: + # skip if illegal field + if row.fieldname not in valid_item_fields: + continue + + # if Item field is not in Website Item, add it as a custom field + if row.fieldname not in valid_web_item_fields: + df = item_meta.get_field(row.fieldname) + create_custom_field( + "Website Item", + dict( + owner="Administrator", + fieldname=df.fieldname, + label=df.label, + fieldtype=df.fieldtype, + options=df.options, + description=df.description, + read_only=df.read_only, + no_copy=df.no_copy, + insert_after="on_backorder", + ), + ) + + # map field values + frappe.db.sql( + """ + UPDATE `tabWebsite Item` wi, `tabItem` i + SET wi.{0} = i.{0} + WHERE wi.item_code = i.item_code + """.format( + row.fieldname + ) + ) From 7c547f152f1a82d4486c854dd5b3e0a3942f56e8 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 19 Apr 2022 12:18:46 +0530 Subject: [PATCH 13/24] fix(patch): check if column is present while fixing reverse linking (#30737) --- ...itional_salary_encashment_and_incentive.py | 98 ++++++++++--------- 1 file changed, 51 insertions(+), 47 deletions(-) diff --git a/erpnext/patches/v13_0/patch_to_fix_reverse_linking_in_additional_salary_encashment_and_incentive.py b/erpnext/patches/v13_0/patch_to_fix_reverse_linking_in_additional_salary_encashment_and_incentive.py index edd0a9706b..45acf49205 100644 --- a/erpnext/patches/v13_0/patch_to_fix_reverse_linking_in_additional_salary_encashment_and_incentive.py +++ b/erpnext/patches/v13_0/patch_to_fix_reverse_linking_in_additional_salary_encashment_and_incentive.py @@ -10,54 +10,58 @@ def execute(): frappe.reload_doc("hr", "doctype", "Leave Encashment") - additional_salaries = frappe.get_all( - "Additional Salary", - fields=["name", "salary_slip", "type", "salary_component"], - filters={"salary_slip": ["!=", ""]}, - group_by="salary_slip", - ) - leave_encashments = frappe.get_all( - "Leave Encashment", - fields=["name", "additional_salary"], - filters={"additional_salary": ["!=", ""]}, - ) - employee_incentives = frappe.get_all( - "Employee Incentive", - fields=["name", "additional_salary"], - filters={"additional_salary": ["!=", ""]}, - ) - - for incentive in employee_incentives: - frappe.db.sql( - """ UPDATE `tabAdditional Salary` - SET ref_doctype = 'Employee Incentive', ref_docname = %s - WHERE name = %s - """, - (incentive["name"], incentive["additional_salary"]), + if frappe.db.has_column("Leave Encashment", "additional_salary"): + leave_encashments = frappe.get_all( + "Leave Encashment", + fields=["name", "additional_salary"], + filters={"additional_salary": ["!=", ""]}, ) - - for leave_encashment in leave_encashments: - frappe.db.sql( - """ UPDATE `tabAdditional Salary` - SET ref_doctype = 'Leave Encashment', ref_docname = %s - WHERE name = %s - """, - (leave_encashment["name"], leave_encashment["additional_salary"]), - ) - - salary_slips = [sal["salary_slip"] for sal in additional_salaries] - - for salary in additional_salaries: - comp_type = "earnings" if salary["type"] == "Earning" else "deductions" - if salary["salary_slip"] and salary_slips.count(salary["salary_slip"]) == 1: + for leave_encashment in leave_encashments: frappe.db.sql( - """ - UPDATE `tabSalary Detail` - SET additional_salary = %s - WHERE parenttype = 'Salary Slip' - and parentfield = %s - and parent = %s - and salary_component = %s + """ UPDATE `tabAdditional Salary` + SET ref_doctype = 'Leave Encashment', ref_docname = %s + WHERE name = %s """, - (salary["name"], comp_type, salary["salary_slip"], salary["salary_component"]), + (leave_encashment["name"], leave_encashment["additional_salary"]), ) + + if frappe.db.has_column("Employee Incentive", "additional_salary"): + employee_incentives = frappe.get_all( + "Employee Incentive", + fields=["name", "additional_salary"], + filters={"additional_salary": ["!=", ""]}, + ) + + for incentive in employee_incentives: + frappe.db.sql( + """ UPDATE `tabAdditional Salary` + SET ref_doctype = 'Employee Incentive', ref_docname = %s + WHERE name = %s + """, + (incentive["name"], incentive["additional_salary"]), + ) + + if frappe.db.has_column("Additional Salary", "salary_slip"): + additional_salaries = frappe.get_all( + "Additional Salary", + fields=["name", "salary_slip", "type", "salary_component"], + filters={"salary_slip": ["!=", ""]}, + group_by="salary_slip", + ) + + salary_slips = [sal["salary_slip"] for sal in additional_salaries] + + for salary in additional_salaries: + comp_type = "earnings" if salary["type"] == "Earning" else "deductions" + if salary["salary_slip"] and salary_slips.count(salary["salary_slip"]) == 1: + frappe.db.sql( + """ + UPDATE `tabSalary Detail` + SET additional_salary = %s + WHERE parenttype = 'Salary Slip' + and parentfield = %s + and parent = %s + and salary_component = %s + """, + (salary["name"], comp_type, salary["salary_slip"], salary["salary_component"]), + ) From 98cccf221e5d1d191b78bb1cb5a879e461e71b71 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 19 Apr 2022 13:17:36 +0530 Subject: [PATCH 14/24] fix: shift fetching fails in Employee Checkin if shift assignment has an end date Co-authored-by: Ejaaz Khan --- erpnext/hr/doctype/shift_assignment/shift_assignment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/hr/doctype/shift_assignment/shift_assignment.py b/erpnext/hr/doctype/shift_assignment/shift_assignment.py index 0b21c00eac..cbf798e690 100644 --- a/erpnext/hr/doctype/shift_assignment/shift_assignment.py +++ b/erpnext/hr/doctype/shift_assignment/shift_assignment.py @@ -251,7 +251,7 @@ def get_shifts_for_date(employee: str, for_timestamp: datetime) -> List[Dict[str Criterion.any( [ assignment.end_date.isnull(), - (assignment.end_date.isnotnull() & (getdate(for_timestamp.date()) >= assignment.end_date)), + (assignment.end_date.isnotnull() & (getdate(for_timestamp.date()) <= assignment.end_date)), ] ) ) From 3cddc1e97e6831c508d309869e98cc53083f60ca Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 19 Apr 2022 13:39:03 +0530 Subject: [PATCH 15/24] test: shift fetching when assignment has an end date --- .../employee_checkin/test_employee_checkin.py | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/erpnext/hr/doctype/employee_checkin/test_employee_checkin.py b/erpnext/hr/doctype/employee_checkin/test_employee_checkin.py index 81b44f8fea..ced42bbc6e 100644 --- a/erpnext/hr/doctype/employee_checkin/test_employee_checkin.py +++ b/erpnext/hr/doctype/employee_checkin/test_employee_checkin.py @@ -153,6 +153,31 @@ class TestEmployeeCheckin(FrappeTestCase): log = make_checkin(employee, timestamp) self.assertIsNone(log.shift) + def test_fetch_shift_for_assignment_with_end_date(self): + employee = make_employee("test_employee_checkin@example.com", company="_Test Company") + + # shift setup for 8-12 + shift1 = setup_shift_type() + # 12:30 - 16:30 + shift2 = setup_shift_type(shift_type="Shift 2", start_time="12:30:00", end_time="16:30:00") + + date = getdate() + make_shift_assignment(shift1.name, employee, date, add_days(date, 15)) + make_shift_assignment(shift2.name, employee, date, add_days(date, 15)) + + timestamp = datetime.combine(date, get_time("08:45:00")) + log = make_checkin(employee, timestamp) + self.assertEqual(log.shift, shift1.name) + + timestamp = datetime.combine(date, get_time("12:45:00")) + log = make_checkin(employee, timestamp) + self.assertEqual(log.shift, shift2.name) + + # log after end date + timestamp = datetime.combine(add_days(date, 16), get_time("12:45:00")) + log = make_checkin(employee, timestamp) + self.assertIsNone(log.shift) + def test_shift_start_and_end_timings(self): employee = make_employee("test_employee_checkin@example.com", company="_Test Company") From 6f332f3669d81dd08828077ba91334450531dcfa Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 19 Apr 2022 15:28:56 +0530 Subject: [PATCH 16/24] fix: Update token to allow updates on protected branch --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 532485f21f..32ea02b1d7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,5 +21,5 @@ jobs: npm install @semantic-release/git @semantic-release/exec --no-save - name: Create Release env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} run: npx semantic-release \ No newline at end of file From 54a05075f34ca381964ec46a479001c292a07b21 Mon Sep 17 00:00:00 2001 From: "FinByz Tech Pvt. Ltd" Date: Tue, 19 Apr 2022 16:26:56 +0530 Subject: [PATCH 17/24] fix(india): transporter name is null while generating e-way bill (#30736) (cherry picked from commit 6291b28c37f02380001ab68b3a660b2804383ab2) --- erpnext/regional/india/e_invoice/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index f317569312..a97ad792f3 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -1074,7 +1074,7 @@ class GSPConnector: "Distance": cint(eway_bill_details.distance), "TransMode": eway_bill_details.mode_of_transport, "TransId": eway_bill_details.gstin, - "TransName": eway_bill_details.transporter, + "TransName": eway_bill_details.name, "TrnDocDt": eway_bill_details.document_date, "TrnDocNo": eway_bill_details.document_name, "VehNo": eway_bill_details.vehicle_no, From e4265ce814bb417ff5acd79e1bedae1b6a6d617b Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Tue, 19 Apr 2022 17:26:42 +0530 Subject: [PATCH 18/24] chore: Update creds to allow updates on protected branch (#30749) --- .github/workflows/release.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 32ea02b1d7..5a46002820 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,6 +12,7 @@ jobs: uses: actions/checkout@v2 with: fetch-depth: 0 + persist-credentials: false - name: Setup Node.js v14 uses: actions/setup-node@v2 with: @@ -21,5 +22,10 @@ jobs: npm install @semantic-release/git @semantic-release/exec --no-save - name: Create Release env: + GH_TOKEN: ${{ secrets.RELEASE_TOKEN }} GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} + GIT_AUTHOR_NAME: "Frappe PR Bot" + GIT_AUTHOR_EMAIL: "developers@frappe.io" + GIT_COMMITTER_NAME: "Frappe PR Bot" + GIT_COMMITTER_EMAIL: "developers@frappe.io" run: npx semantic-release \ No newline at end of file From 5660b335ba0f258552d4eea499d718fbcdc66383 Mon Sep 17 00:00:00 2001 From: marination Date: Wed, 20 Apr 2022 12:18:47 +0530 Subject: [PATCH 19/24] fix: Mistyped variable name in patch --- .../patches/v13_0/copy_custom_field_filters_to_website_item.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/patches/v13_0/copy_custom_field_filters_to_website_item.py b/erpnext/patches/v13_0/copy_custom_field_filters_to_website_item.py index 5f2125144f..3e7e52a401 100644 --- a/erpnext/patches/v13_0/copy_custom_field_filters_to_website_item.py +++ b/erpnext/patches/v13_0/copy_custom_field_filters_to_website_item.py @@ -6,7 +6,7 @@ def execute(): "Add Field Filters, that are not standard fields in Website Item, as Custom Fields." settings = frappe.get_doc("E Commerce Settings") - if not (settings.filter_fields or settings.field_filters): + if not (settings.enable_field_filters or settings.filter_fields): return item_meta = frappe.get_meta("Item") From 143786aaa077d2fb29d54836dcb8424ca247a8e8 Mon Sep 17 00:00:00 2001 From: HENRY Florian Date: Wed, 20 Apr 2022 08:59:52 +0200 Subject: [PATCH 20/24] fix: Must not be able to start Job Card if it is related to Work Order that is not started yet (#29072) * fix: Cannot start Job strat if related to Work Order not started yet * fix: Cannot start Job strat if related to Work Order not started yet * test * test * fix siders * PR review * chore: Code cleanup - Better short circuit for if condition (make it such that both conditions dont always have to be computed) - Remove `r.message` extraction by avoiding `then()` * chore: Remove unnecessary json change Co-authored-by: marination --- erpnext/manufacturing/doctype/job_card/job_card.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.js b/erpnext/manufacturing/doctype/job_card/job_card.js index b2824e139c..b6646b19f6 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.js +++ b/erpnext/manufacturing/doctype/job_card/job_card.js @@ -73,10 +73,22 @@ frappe.ui.form.on('Job Card', { if (frm.doc.docstatus == 0 && !frm.is_new() && (frm.doc.for_quantity > frm.doc.total_completed_qty || !frm.doc.for_quantity) && (frm.doc.items || !frm.doc.items.length || frm.doc.for_quantity == frm.doc.transferred_qty)) { - frm.trigger("prepare_timer_buttons"); + + // if Job Card is link to Work Order, the job card must not be able to start if Work Order not "Started" + // and if stock mvt for WIP is required + if (frm.doc.work_order) { + frappe.db.get_value('Work Order', frm.doc.work_order, ['skip_transfer', 'status'], (result) => { + if (result.skip_transfer === 1 || result.status == 'In Process') { + frm.trigger("prepare_timer_buttons"); + } + }); + } else { + frm.trigger("prepare_timer_buttons"); + } } frm.trigger("setup_quality_inspection"); + if (frm.doc.work_order) { frappe.db.get_value('Work Order', frm.doc.work_order, 'transfer_material_against').then((r) => { From 5411e69553fc7ee5765ea47c3cefeabbca4464e4 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 20 Apr 2022 14:56:06 +0530 Subject: [PATCH 21/24] fix: filters not working in Shift Assignment Calendar view (#30752) --- .../shift_assignment/shift_assignment.py | 20 ++++++++++--------- .../shift_assignment/test_shift_assignment.py | 17 +++++++++++++++- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/erpnext/hr/doctype/shift_assignment/shift_assignment.py b/erpnext/hr/doctype/shift_assignment/shift_assignment.py index cbf798e690..51298deddb 100644 --- a/erpnext/hr/doctype/shift_assignment/shift_assignment.py +++ b/erpnext/hr/doctype/shift_assignment/shift_assignment.py @@ -121,7 +121,7 @@ def has_overlapping_timings(shift_1: str, shift_2: str) -> bool: @frappe.whitelist() def get_events(start, end, filters=None): - events = [] + from frappe.desk.calendar import get_event_conditions employee = frappe.db.get_value( "Employee", {"user_id": frappe.session.user}, ["name", "company"], as_dict=True @@ -132,20 +132,22 @@ def get_events(start, end, filters=None): employee = "" company = frappe.db.get_value("Global Defaults", None, "default_company") - from frappe.desk.reportview import get_filters_cond - - conditions = get_filters_cond("Shift Assignment", filters, []) - add_assignments(events, start, end, conditions=conditions) + conditions = get_event_conditions("Shift Assignment", filters) + events = add_assignments(start, end, conditions=conditions) return events -def add_assignments(events, start, end, conditions=None): +def add_assignments(start, end, conditions=None): + events = [] + query = """select name, start_date, end_date, employee_name, employee, docstatus, shift_type from `tabShift Assignment` where - start_date >= %(start_date)s - or end_date <= %(end_date)s - or (%(start_date)s between start_date and end_date and %(end_date)s between start_date and end_date) + ( + start_date >= %(start_date)s + or end_date <= %(end_date)s + or (%(start_date)s between start_date and end_date and %(end_date)s between start_date and end_date) + ) and docstatus = 1""" if conditions: query += conditions diff --git a/erpnext/hr/doctype/shift_assignment/test_shift_assignment.py b/erpnext/hr/doctype/shift_assignment/test_shift_assignment.py index 0fe9108168..de82a2432b 100644 --- a/erpnext/hr/doctype/shift_assignment/test_shift_assignment.py +++ b/erpnext/hr/doctype/shift_assignment/test_shift_assignment.py @@ -8,7 +8,7 @@ from frappe.tests.utils import FrappeTestCase from frappe.utils import add_days, getdate, nowdate from erpnext.hr.doctype.employee.test_employee import make_employee -from erpnext.hr.doctype.shift_assignment.shift_assignment import OverlappingShiftError +from erpnext.hr.doctype.shift_assignment.shift_assignment import OverlappingShiftError, get_events from erpnext.hr.doctype.shift_type.test_shift_type import make_shift_assignment, setup_shift_type test_dependencies = ["Shift Type"] @@ -154,3 +154,18 @@ class TestShiftAssignment(FrappeTestCase): shift_type = setup_shift_type(shift_type="Shift 2", start_time="13:00:00", end_time="15:00:00") date = getdate() make_shift_assignment(shift_type.name, employee, date) + + def test_shift_assignment_calendar(self): + employee1 = make_employee("test_shift_assignment1@example.com", company="_Test Company") + employee2 = make_employee("test_shift_assignment2@example.com", company="_Test Company") + + shift_type = setup_shift_type(shift_type="Shift 1", start_time="08:00:00", end_time="12:00:00") + date = getdate() + shift1 = make_shift_assignment(shift_type.name, employee1, date) + make_shift_assignment(shift_type.name, employee2, date) + + events = get_events( + start=date, end=date, filters=[["Shift Assignment", "employee", "=", employee1, False]] + ) + self.assertEqual(len(events), 1) + self.assertEqual(events[0]["name"], shift1.name) From 6d750e185e34e4e287c6fe4055c212246a041678 Mon Sep 17 00:00:00 2001 From: marination Date: Wed, 20 Apr 2022 17:34:51 +0530 Subject: [PATCH 22/24] fix: Handle Multiselect field mapping separately - Map Multiselect child table to Website Item (copy rows) --- erpnext/patches.txt | 2 +- ...py_custom_field_filters_to_website_item.py | 56 ++++++++++++++++--- 2 files changed, 49 insertions(+), 9 deletions(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index fb1020e223..c290551b73 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -364,4 +364,4 @@ erpnext.patches.v13_0.set_return_against_in_pos_invoice_references erpnext.patches.v13_0.remove_unknown_links_to_prod_plan_items # 24-03-2022 erpnext.patches.v13_0.update_expense_claim_status_for_paid_advances erpnext.patches.v13_0.create_gst_custom_fields_in_quotation -erpnext.patches.v13_0.copy_custom_field_filters_to_website_item #22 +erpnext.patches.v13_0.copy_custom_field_filters_to_website_item diff --git a/erpnext/patches/v13_0/copy_custom_field_filters_to_website_item.py b/erpnext/patches/v13_0/copy_custom_field_filters_to_website_item.py index 3e7e52a401..e8d0b593e6 100644 --- a/erpnext/patches/v13_0/copy_custom_field_filters_to_website_item.py +++ b/erpnext/patches/v13_0/copy_custom_field_filters_to_website_item.py @@ -4,6 +4,43 @@ from frappe.custom.doctype.custom_field.custom_field import create_custom_field def execute(): "Add Field Filters, that are not standard fields in Website Item, as Custom Fields." + + def move_table_multiselect_data(docfield): + "Copy child table data (Table Multiselect) from Item to Website Item for a docfield." + table_multiselect_data = get_table_multiselect_data(docfield) + field = docfield.fieldname + + for row in table_multiselect_data: + # add copied multiselect data rows in Website Item + web_item = frappe.db.get_value("Website Item", {"item_code": row.parent}) + web_item_doc = frappe.get_doc("Website Item", web_item) + + child_doc = frappe.new_doc(docfield.options, web_item_doc, field) + + for field in ["name", "creation", "modified", "idx"]: + row[field] = None + + child_doc.update(row) + + child_doc.parenttype = "Website Item" + child_doc.parent = web_item + + child_doc.insert() + + def get_table_multiselect_data(docfield): + child_table = frappe.qb.DocType(docfield.options) + item = frappe.qb.DocType("Item") + + table_multiselect_data = ( # query table data for field + frappe.qb.from_(child_table) + .join(item) + .on(item.item_code == child_table.parent) + .select(child_table.star) + .where((child_table.parentfield == docfield.fieldname) & (item.published_in_website == 1)) + ).run(as_dict=True) + + return table_multiselect_data + settings = frappe.get_doc("E Commerce Settings") if not (settings.enable_field_filters or settings.filter_fields): @@ -43,12 +80,15 @@ def execute(): ) # map field values - frappe.db.sql( - """ - UPDATE `tabWebsite Item` wi, `tabItem` i - SET wi.{0} = i.{0} - WHERE wi.item_code = i.item_code - """.format( - row.fieldname + if df.fieldtype == "Table MultiSelect": + move_table_multiselect_data(df) + else: + frappe.db.sql( # nosemgrep + """ + UPDATE `tabWebsite Item` wi, `tabItem` i + SET wi.{0} = i.{0} + WHERE wi.item_code = i.item_code + """.format( + row.fieldname + ) ) - ) From 8981405a6213a6a8a9574f181064f8eac8ec9550 Mon Sep 17 00:00:00 2001 From: marination Date: Wed, 20 Apr 2022 18:50:47 +0530 Subject: [PATCH 23/24] test: Field filter validation and Custom field as field filter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Test to block Item fields (which aren’t in Website Item) in E Commerce Settings as filters - Removed unnecessary function and setup in E Commerce Settings test - Removed commented useless test - Test to check custom field as filter --- .../test_e_commerce_settings.py | 46 +++++++----------- .../test_product_data_engine.py | 48 +++++++++++++++++++ 2 files changed, 65 insertions(+), 29 deletions(-) diff --git a/erpnext/e_commerce/doctype/e_commerce_settings/test_e_commerce_settings.py b/erpnext/e_commerce/doctype/e_commerce_settings/test_e_commerce_settings.py index c4c958bd44..1f461c8c16 100644 --- a/erpnext/e_commerce/doctype/e_commerce_settings/test_e_commerce_settings.py +++ b/erpnext/e_commerce/doctype/e_commerce_settings/test_e_commerce_settings.py @@ -1,5 +1,4 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors +# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt import unittest @@ -11,44 +10,33 @@ from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import ( class TestECommerceSettings(unittest.TestCase): - def setUp(self): - frappe.db.sql("""delete from `tabSingles` where doctype="Shipping Cart Settings" """) - - def get_cart_settings(self): - return frappe.get_doc({"doctype": "E Commerce Settings", "company": "_Test Company"}) - - # NOTE: Exchangrate API has all enabled currencies that ERPNext supports. - # We aren't checking just currency exchange record anymore - # while validating price list currency exchange rate to that of company. - # The API is being used to fetch the rate which again almost always - # gives back a valid value (for valid currencies). - # This makes the test obsolete. - # Commenting because im not sure if there's a better test we can write - - # def test_exchange_rate_exists(self): - # frappe.db.sql("""delete from `tabCurrency Exchange`""") - - # cart_settings = self.get_cart_settings() - # cart_settings.price_list = "_Test Price List Rest of the World" - # self.assertRaises(ShoppingCartSetupError, cart_settings.validate_exchange_rates_exist) - - # from erpnext.setup.doctype.currency_exchange.test_currency_exchange import ( - # test_records as currency_exchange_records, - # ) - # frappe.get_doc(currency_exchange_records[0]).insert() - # cart_settings.validate_exchange_rates_exist() + def tearDown(self): + frappe.db.rollback() def test_tax_rule_validation(self): frappe.db.sql("update `tabTax Rule` set use_for_shopping_cart = 0") frappe.db.commit() # nosemgrep - cart_settings = self.get_cart_settings() + cart_settings = frappe.get_doc("E Commerce Settings") cart_settings.enabled = 1 if not frappe.db.get_value("Tax Rule", {"use_for_shopping_cart": 1}, "name"): self.assertRaises(ShoppingCartSetupError, cart_settings.validate_tax_rule) frappe.db.sql("update `tabTax Rule` set use_for_shopping_cart = 1") + def test_invalid_filter_fields(self): + "Check if Item fields are blocked in E Commerce Settings filter fields." + from frappe.custom.doctype.custom_field.custom_field import create_custom_field + + create_custom_field( + "Item", + dict(owner="Administrator", fieldname="test_data", label="Test", fieldtype="Data"), + ) + settings = frappe.get_doc("E Commerce Settings") + settings.append("filter_fields", {"fieldname": "test_data"}) + + self.assertRaises(frappe.ValidationError, settings.save) + def setup_e_commerce_settings(values_dict): "Accepts a dict of values that updates E Commerce Settings." diff --git a/erpnext/e_commerce/product_data_engine/test_product_data_engine.py b/erpnext/e_commerce/product_data_engine/test_product_data_engine.py index ab958d1486..c3b6ed5da2 100644 --- a/erpnext/e_commerce/product_data_engine/test_product_data_engine.py +++ b/erpnext/e_commerce/product_data_engine/test_product_data_engine.py @@ -277,6 +277,54 @@ class TestProductDataEngine(unittest.TestCase): # tear down setup_e_commerce_settings({"enable_attribute_filters": 1, "hide_variants": 0}) + def test_custom_field_as_filter(self): + "Test if custom field functions as filter correctly." + from frappe.custom.doctype.custom_field.custom_field import create_custom_field + + create_custom_field( + "Website Item", + dict( + owner="Administrator", + fieldname="supplier", + label="Supplier", + fieldtype="Link", + options="Supplier", + insert_after="on_backorder", + ), + ) + + frappe.db.set_value( + "Website Item", {"item_code": "Test 11I Laptop"}, "supplier", "_Test Supplier" + ) + frappe.db.set_value( + "Website Item", {"item_code": "Test 12I Laptop"}, "supplier", "_Test Supplier 1" + ) + + settings = frappe.get_doc("E Commerce Settings") + settings.append("filter_fields", {"fieldname": "supplier"}) + settings.save() + + filter_engine = ProductFiltersBuilder() + field_filters = filter_engine.get_field_filters() + custom_filter = field_filters[1] + filter_values = custom_filter[1] + + self.assertEqual(custom_filter[0].options, "Supplier") + self.assertEqual(len(filter_values), 2) + self.assertIn("_Test Supplier", filter_values) + + # test if custom filter works in query + field_filters = {"supplier": "_Test Supplier 1"} + engine = ProductQuery() + result = engine.query( + attributes={}, fields=field_filters, search_term=None, start=0, item_group=None + ) + items = result.get("items") + + # check if only 'Raw Material' are fetched in the right order + self.assertEqual(len(items), 1) + self.assertEqual(items[0].get("item_code"), "Test 12I Laptop") + def create_variant_web_item(): "Create Variant and Template Website Items." From c5d4bed9322ce1c82b9165261a9ca025b755bda4 Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 21 Apr 2022 12:29:30 +0530 Subject: [PATCH 24/24] test: setup e commerce settings before running invalid filtrs test --- .../doctype/e_commerce_settings/test_e_commerce_settings.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/e_commerce/doctype/e_commerce_settings/test_e_commerce_settings.py b/erpnext/e_commerce/doctype/e_commerce_settings/test_e_commerce_settings.py index 1f461c8c16..662db4d7ae 100644 --- a/erpnext/e_commerce/doctype/e_commerce_settings/test_e_commerce_settings.py +++ b/erpnext/e_commerce/doctype/e_commerce_settings/test_e_commerce_settings.py @@ -28,6 +28,8 @@ class TestECommerceSettings(unittest.TestCase): "Check if Item fields are blocked in E Commerce Settings filter fields." from frappe.custom.doctype.custom_field.custom_field import create_custom_field + setup_e_commerce_settings({"enable_field_filters": 1}) + create_custom_field( "Item", dict(owner="Administrator", fieldname="test_data", label="Test", fieldtype="Data"),