From 5b2ba245637befb7c6a01abcd9fbc5d83318a551 Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Wed, 24 Nov 2021 19:19:45 +0530 Subject: [PATCH 01/28] fix: company tour --- .../selling/form_tour/customer/customer.json | 29 ++++++++ .../form_tour/quotation/quotation.json | 67 +++++++++++++++++++ erpnext/setup/form_tour/company/company.json | 67 +++++++++++++++++++ .../setup/module_onboarding/home/home.json | 62 +++++++++++++++++ .../company_set_up/company_set_up.json | 21 ++++++ .../create_a_customer/create_a_customer.json | 21 ++++++ .../create_a_quotation.json | 21 ++++++ .../create_a_supplier/create_a_supplier.json | 21 ++++++ .../create_an_item/create_an_item.json | 22 ++++++ .../data_import/data_import.json | 21 ++++++ .../letterhead/letterhead.json | 21 ++++++ .../navigation_help/navigation_help.json | 21 ++++++ erpnext/setup/workspace/home/home.json | 13 +++- erpnext/stock/form_tour/item/item.json | 30 +++++++-- 14 files changed, 427 insertions(+), 10 deletions(-) create mode 100644 erpnext/selling/form_tour/customer/customer.json create mode 100644 erpnext/selling/form_tour/quotation/quotation.json create mode 100644 erpnext/setup/form_tour/company/company.json create mode 100644 erpnext/setup/module_onboarding/home/home.json create mode 100644 erpnext/setup/onboarding_step/company_set_up/company_set_up.json create mode 100644 erpnext/setup/onboarding_step/create_a_customer/create_a_customer.json create mode 100644 erpnext/setup/onboarding_step/create_a_quotation/create_a_quotation.json create mode 100644 erpnext/setup/onboarding_step/create_a_supplier/create_a_supplier.json create mode 100644 erpnext/setup/onboarding_step/create_an_item/create_an_item.json create mode 100644 erpnext/setup/onboarding_step/data_import/data_import.json create mode 100644 erpnext/setup/onboarding_step/letterhead/letterhead.json create mode 100644 erpnext/setup/onboarding_step/navigation_help/navigation_help.json diff --git a/erpnext/selling/form_tour/customer/customer.json b/erpnext/selling/form_tour/customer/customer.json new file mode 100644 index 0000000000..1de45b7f5d --- /dev/null +++ b/erpnext/selling/form_tour/customer/customer.json @@ -0,0 +1,29 @@ +{ + "creation": "2021-11-23 10:44:13.185982", + "docstatus": 0, + "doctype": "Form Tour", + "idx": 0, + "is_standard": 1, + "modified": "2021-11-23 10:54:09.602358", + "modified_by": "Administrator", + "module": "Selling", + "name": "Customer", + "owner": "Administrator", + "reference_doctype": "Customer", + "save_on_complete": 1, + "steps": [ + { + "description": "Enter the Full Name of the Customer", + "field": "", + "fieldname": "customer_name", + "fieldtype": "Data", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Full Name", + "parent_field": "", + "position": "Left", + "title": "Full Name" + } + ], + "title": "Customer" +} \ No newline at end of file diff --git a/erpnext/selling/form_tour/quotation/quotation.json b/erpnext/selling/form_tour/quotation/quotation.json new file mode 100644 index 0000000000..2a2aa5e63e --- /dev/null +++ b/erpnext/selling/form_tour/quotation/quotation.json @@ -0,0 +1,67 @@ +{ + "creation": "2021-11-23 12:00:36.138824", + "docstatus": 0, + "doctype": "Form Tour", + "idx": 0, + "is_standard": 1, + "modified": "2021-11-23 12:02:48.010298", + "modified_by": "Administrator", + "module": "Selling", + "name": "Quotation", + "owner": "Administrator", + "reference_doctype": "Quotation", + "save_on_complete": 1, + "steps": [ + { + "description": "Select a customer or lead for whom this quotation is being prepared. Let's select a Customer.", + "field": "", + "fieldname": "quotation_to", + "fieldtype": "Link", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Quotation To", + "parent_field": "", + "position": "Right", + "title": "Quotation To" + }, + { + "description": "Select a specific Customer to whom this quotation will be sent.", + "field": "", + "fieldname": "party_name", + "fieldtype": "Dynamic Link", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Party", + "parent_field": "", + "position": "Right", + "title": "Party" + }, + { + "child_doctype": "Quotation Item", + "description": "Select an item for which you will be quoting a price.", + "field": "", + "fieldname": "items", + "fieldtype": "Table", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Items", + "parent_field": "", + "parent_fieldname": "items", + "position": "Bottom", + "title": "Items" + }, + { + "description": "You can select pre-populated Sales Taxes and Charges from here.", + "field": "", + "fieldname": "taxes", + "fieldtype": "Table", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Sales Taxes and Charges", + "parent_field": "", + "position": "Bottom", + "title": "Sales Taxes and Charges" + } + ], + "title": "Quotation" +} \ No newline at end of file diff --git a/erpnext/setup/form_tour/company/company.json b/erpnext/setup/form_tour/company/company.json new file mode 100644 index 0000000000..c66abc0a72 --- /dev/null +++ b/erpnext/setup/form_tour/company/company.json @@ -0,0 +1,67 @@ +{ + "creation": "2021-11-24 10:17:18.534917", + "docstatus": 0, + "doctype": "Form Tour", + "first_document": 1, + "idx": 0, + "include_name_field": 0, + "is_standard": 1, + "modified": "2021-11-24 15:38:21.026582", + "modified_by": "Administrator", + "module": "Setup", + "name": "Company", + "owner": "Administrator", + "reference_doctype": "Company", + "save_on_complete": 0, + "steps": [ + { + "description": "This is the default currency for this company.", + "field": "", + "fieldname": "default_currency", + "fieldtype": "Link", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Default Currency", + "parent_field": "", + "position": "Right", + "title": "Default Currency" + }, + { + "description": "Here, you can add multiple addresses of the company", + "field": "", + "fieldname": "company_info", + "fieldtype": "Section Break", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Address & Contact", + "parent_field": "", + "position": "Top", + "title": "Address & Contact" + }, + { + "description": "Here, you can set default Accounts, which will ease the creation of accounting entries.", + "field": "", + "fieldname": "default_settings", + "fieldtype": "Section Break", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Accounts Settings", + "parent_field": "", + "position": "Top", + "title": "Accounts Settings" + }, + { + "description": "This setting is recommended if you wish to track the real-time stock balance in your books of account. This will allow the creation of a General Ledger entry for every stock transaction.", + "field": "", + "fieldname": "enable_perpetual_inventory", + "fieldtype": "Check", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Enable Perpetual Inventory", + "parent_field": "", + "position": "Right", + "title": "Enable Perpetual Inventory" + } + ], + "title": "Company" +} \ No newline at end of file diff --git a/erpnext/setup/module_onboarding/home/home.json b/erpnext/setup/module_onboarding/home/home.json new file mode 100644 index 0000000000..fbff98ff8a --- /dev/null +++ b/erpnext/setup/module_onboarding/home/home.json @@ -0,0 +1,62 @@ +{ + "allow_roles": [ + { + "role": "Accounts Manager" + }, + { + "role": "Stock Manager" + }, + { + "role": "Sales Manager" + }, + { + "role": "Purchase Manager" + }, + { + "role": "Manufacturing Manager" + }, + { + "role": "Item Manager" + } + ], + "creation": "2021-11-22 12:19:15.888642", + "docstatus": 0, + "doctype": "Module Onboarding", + "documentation_url": "https://docs.erpnext.com/docs/v13/user/manual/en/setting-up/company-setup", + "idx": 0, + "is_complete": 0, + "modified": "2021-11-24 16:40:15.154081", + "modified_by": "Administrator", + "module": "Setup", + "name": "Home", + "owner": "Administrator", + "steps": [ + { + "step": "Company Set Up" + }, + { + "step": "Create an Item" + }, + { + "step": "Create a Customer" + }, + { + "step": "Create a Supplier" + }, + { + "step": "Navigation Help" + }, + { + "step": "Data import" + }, + { + "step": "Create a Quotation" + }, + { + "step": "Letterhead" + } + ], + "subtitle": "Company, Item, Customer, Supplier, Navigation Help, Data Import, Letter Head, Quotation", + "success_message": "Masters are all set up!", + "title": "Let's Set Up Some Masters" +} \ No newline at end of file diff --git a/erpnext/setup/onboarding_step/company_set_up/company_set_up.json b/erpnext/setup/onboarding_step/company_set_up/company_set_up.json new file mode 100644 index 0000000000..dc85189764 --- /dev/null +++ b/erpnext/setup/onboarding_step/company_set_up/company_set_up.json @@ -0,0 +1,21 @@ +{ + "action": "Create Entry", + "action_label": "Let's create a Company", + "creation": "2021-11-22 11:55:48.931427", + "description": "# Set Up a Company\n\nA company is a legal entity for which you will set up your books of account and create accounting transactions. In ERPNext, you can create multiple companies, and establish relationships (group/subsidiary) among them.\n\nWithin the company master, you can capture various default accounts for that Company and set crucial settings related to the accounting methodology followed for a company.\n", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "is_complete": 0, + "is_single": 0, + "is_skipped": 0, + "modified": "2021-11-24 15:41:01.620963", + "modified_by": "Administrator", + "name": "Company Set Up", + "owner": "Administrator", + "reference_document": "Company", + "show_form_tour": 1, + "show_full_form": 1, + "title": "Set Up a Company", + "validate_action": 1 +} \ No newline at end of file diff --git a/erpnext/setup/onboarding_step/create_a_customer/create_a_customer.json b/erpnext/setup/onboarding_step/create_a_customer/create_a_customer.json new file mode 100644 index 0000000000..c3162f2b87 --- /dev/null +++ b/erpnext/setup/onboarding_step/create_a_customer/create_a_customer.json @@ -0,0 +1,21 @@ +{ + "action": "Create Entry", + "action_label": "Let\u2019s create your first Customer", + "creation": "2020-05-14 17:46:41.831517", + "description": "The Customer master is at the heart of your sales transactions. Customers are linked in Quotations, Sales Orders, Invoices, and Payments. Customers can be either numbered or identified by name (you would typically do this based on the number of customers you have).\n\nThrough Customer\u2019s master, you can effectively track essentials like:\n - Customer\u2019s multiple address and contacts\n - Account Receivables\n - Credit Limit and Credit Period\n", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "is_complete": 0, + "is_single": 0, + "is_skipped": 0, + "modified": "2021-11-24 15:52:20.882675", + "modified_by": "Administrator", + "name": "Create a Customer", + "owner": "Administrator", + "reference_document": "Customer", + "show_form_tour": 0, + "show_full_form": 0, + "title": "Manage Customers", + "validate_action": 1 +} \ No newline at end of file diff --git a/erpnext/setup/onboarding_step/create_a_quotation/create_a_quotation.json b/erpnext/setup/onboarding_step/create_a_quotation/create_a_quotation.json new file mode 100644 index 0000000000..12d4372470 --- /dev/null +++ b/erpnext/setup/onboarding_step/create_a_quotation/create_a_quotation.json @@ -0,0 +1,21 @@ +{ + "action": "Create Entry", + "action_label": "Let\u2019s create your first Quotation", + "creation": "2020-06-01 13:34:58.958641", + "description": "# Quotation\n\nLet\u2019s get started with business transactions by creating your first Quotation. You can create a Quotation for an existing customer or a prospect. It will be an approved document, with items you sell and the proposed price + taxes applied. After completing the instructions, you will get a Quotation in a ready to share print format.", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "is_complete": 0, + "is_single": 0, + "is_skipped": 0, + "modified": "2021-11-24 15:55:28.950596", + "modified_by": "Administrator", + "name": "Create a Quotation", + "owner": "Administrator", + "reference_document": "Quotation", + "show_form_tour": 1, + "show_full_form": 1, + "title": "Create your first Quotation", + "validate_action": 1 +} \ No newline at end of file diff --git a/erpnext/setup/onboarding_step/create_a_supplier/create_a_supplier.json b/erpnext/setup/onboarding_step/create_a_supplier/create_a_supplier.json new file mode 100644 index 0000000000..cc87d97ae4 --- /dev/null +++ b/erpnext/setup/onboarding_step/create_a_supplier/create_a_supplier.json @@ -0,0 +1,21 @@ +{ + "action": "Create Entry", + "action_label": "Let\u2019s create your first Supplier", + "creation": "2020-05-14 22:09:10.043554", + "description": "# Supplier\n\nAlso known as Vendor, is a master at the center of your purchase transactions. Suppliers are linked in Request for Quotation, Purchase Orders, Receipts, and Payments. Suppliers can be either numbered or identified by name.\n\nThrough Supplier\u2019s master, you can effectively track essentials like:\n - Supplier\u2019s multiple address and contacts\n - Account Receivables\n - Credit Limit and Credit Period\n", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "is_complete": 0, + "is_single": 0, + "is_skipped": 0, + "modified": "2021-11-24 15:52:36.656949", + "modified_by": "Administrator", + "name": "Create a Supplier", + "owner": "Administrator", + "reference_document": "Supplier", + "show_form_tour": 0, + "show_full_form": 0, + "title": "Manage Suppliers", + "validate_action": 1 +} \ No newline at end of file diff --git a/erpnext/setup/onboarding_step/create_an_item/create_an_item.json b/erpnext/setup/onboarding_step/create_an_item/create_an_item.json new file mode 100644 index 0000000000..0d650de665 --- /dev/null +++ b/erpnext/setup/onboarding_step/create_an_item/create_an_item.json @@ -0,0 +1,22 @@ +{ + "action": "Create Entry", + "action_label": "Create a new Item", + "creation": "2021-05-17 13:47:18.515052", + "description": "Item is a product, of a or service offered by your company, or something you buy as a part of your supplies or raw materials.\n\nItems are integral to everything you do in ERPNext - from billing, purchasing to managing inventory. Everything you buy or sell, whether it is a physical product or a service is an Item. Items can be stock, non-stock, variants, serialized, batched, assets etc.\n", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "intro_video_url": "", + "is_complete": 0, + "is_single": 0, + "is_skipped": 0, + "modified": "2021-11-24 15:51:21.446969", + "modified_by": "Administrator", + "name": "Create an Item", + "owner": "Administrator", + "reference_document": "Item", + "show_form_tour": 1, + "show_full_form": 1, + "title": "Manage Items", + "validate_action": 1 +} \ No newline at end of file diff --git a/erpnext/setup/onboarding_step/data_import/data_import.json b/erpnext/setup/onboarding_step/data_import/data_import.json new file mode 100644 index 0000000000..5f4e8db4ab --- /dev/null +++ b/erpnext/setup/onboarding_step/data_import/data_import.json @@ -0,0 +1,21 @@ +{ + "action": "Watch Video", + "action_label": "Learn more about data migration", + "creation": "2021-05-19 05:29:16.809610", + "description": "# Import Data from Spreadsheet\n\nIn ERPNext, you can easily migrate your historical data using spreadsheets. You can use it for migrating not just masters (like Customer, Supplier, Items), but also for transactions like (outstanding invoices, opening stock and accounting entries, etc). If you are migrating from [Tally](https://tallysolutions.com/) or [Quickbooks](https://quickbooks.intuit.com/in/), we got special migration tools for you.", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "is_complete": 0, + "is_single": 0, + "is_skipped": 0, + "modified": "2021-11-24 15:54:37.622063", + "modified_by": "Administrator", + "name": "Data import", + "owner": "Administrator", + "show_form_tour": 0, + "show_full_form": 0, + "title": "Import Data from Spreadsheet", + "validate_action": 1, + "video_url": "https://youtu.be/DQyqeurPI64" +} \ No newline at end of file diff --git a/erpnext/setup/onboarding_step/letterhead/letterhead.json b/erpnext/setup/onboarding_step/letterhead/letterhead.json new file mode 100644 index 0000000000..b8d430365f --- /dev/null +++ b/erpnext/setup/onboarding_step/letterhead/letterhead.json @@ -0,0 +1,21 @@ +{ + "action": "Create Entry", + "action_label": "Let\u2019s setup your first Letter Head", + "creation": "2021-11-22 12:36:34.583783", + "description": "# Letter Head\n\nA Letter Head contains your organization's name, logo, address, etc which appears at the header and footer portion in documents. You can learn more about Setting up Letter Head in ERPNext here.\n", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "is_complete": 0, + "is_single": 0, + "is_skipped": 0, + "modified": "2021-11-24 16:19:25.379798", + "modified_by": "Administrator", + "name": "Letterhead", + "owner": "Administrator", + "reference_document": "Letter Head", + "show_form_tour": 1, + "show_full_form": 1, + "title": "Setup Your Letterhead", + "validate_action": 1 +} \ No newline at end of file diff --git a/erpnext/setup/onboarding_step/navigation_help/navigation_help.json b/erpnext/setup/onboarding_step/navigation_help/navigation_help.json new file mode 100644 index 0000000000..1271a86cb9 --- /dev/null +++ b/erpnext/setup/onboarding_step/navigation_help/navigation_help.json @@ -0,0 +1,21 @@ +{ + "action": "Watch Video", + "action_label": "Learn about Navigation options", + "creation": "2021-11-22 12:09:52.233872", + "description": "Ease of navigating and browsing around the ERPNext is one of our core strengths. In the following video, you will learn how to reach a specific feature in ERPNext via module page or awesome bar\u2019s shortcut.\n", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "is_complete": 0, + "is_single": 0, + "is_skipped": 0, + "modified": "2021-11-24 15:52:40.641949", + "modified_by": "Administrator", + "name": "Navigation Help", + "owner": "Administrator", + "show_form_tour": 0, + "show_full_form": 0, + "title": "How to Navigate in ERPNext", + "validate_action": 1, + "video_url": "https://youtu.be/j60xyNFqX_A" +} \ No newline at end of file diff --git a/erpnext/setup/workspace/home/home.json b/erpnext/setup/workspace/home/home.json index 4e1ccf9b94..f9c585c015 100644 --- a/erpnext/setup/workspace/home/home.json +++ b/erpnext/setup/workspace/home/home.json @@ -1,13 +1,18 @@ { "charts": [], - "content": "[{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\",\"level\":4,\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Item\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Customer\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Supplier\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Sales Invoice\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Leaderboard\",\"col\":4}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\",\"level\":4,\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Accounting\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Stock\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Human Resources\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"CRM\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Data Import and Settings\",\"col\":4}}]", + "content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Home\",\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\",\"level\":4,\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Item\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Customer\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Supplier\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Sales Invoice\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Leaderboard\",\"col\":4}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\",\"level\":4,\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Accounting\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Stock\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Human Resources\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"CRM\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Data Import and Settings\",\"col\":4}}]", "creation": "2020-01-23 13:46:38.833076", + "developer_mode_only": 0, + "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", + "extends_another_page": 0, "for_user": "", "hide_custom": 0, "icon": "getting-started", "idx": 0, + "is_default": 0, + "is_standard": 0, "label": "Home", "links": [ { @@ -271,12 +276,14 @@ "type": "Link" } ], - "modified": "2021-08-10 15:33:20.704741", + "modified": "2021-11-22 12:50:15.771366", "modified_by": "Administrator", "module": "Setup", "name": "Home", "owner": "Administrator", "parent_page": "", + "pin_to_bottom": 0, + "pin_to_top": 0, "public": 1, "restrict_to_domain": "", "roles": [], @@ -309,4 +316,4 @@ } ], "title": "Home" -} \ No newline at end of file +} diff --git a/erpnext/stock/form_tour/item/item.json b/erpnext/stock/form_tour/item/item.json index 821e91b28d..5369366edb 100644 --- a/erpnext/stock/form_tour/item/item.json +++ b/erpnext/stock/form_tour/item/item.json @@ -2,15 +2,17 @@ "creation": "2021-08-24 17:56:40.754909", "docstatus": 0, "doctype": "Form Tour", + "first_document": 0, "idx": 0, + "include_name_field": 0, "is_standard": 1, - "modified": "2021-08-24 18:04:50.928431", + "modified": "2021-11-24 17:59:44.559001", "modified_by": "Administrator", "module": "Stock", "name": "Item", "owner": "Administrator", "reference_doctype": "Item", - "save_on_complete": 0, + "save_on_complete": 1, "steps": [ { "description": "Enter code for Asset Item", @@ -36,14 +38,27 @@ "position": "Bottom", "title": "Asset Item Name" }, + { + "description": "Select an Item Group", + "field": "", + "fieldname": "item_group", + "fieldtype": "Link", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Item Group", + "parent_field": "", + "position": "Right", + "title": "Item Group" + }, { "description": "Check this field to make this an Asset Item", "field": "", "fieldname": "is_fixed_asset", "fieldtype": "Check", - "has_next_condition": 0, + "has_next_condition": 1, "is_table_field": 0, "label": "Is Fixed Asset", + "next_step_condition": "eval:doc.is_fixed_asset", "parent_field": "", "position": "Bottom", "title": "Is this a Fixed Asset?" @@ -53,9 +68,10 @@ "field": "", "fieldname": "auto_create_assets", "fieldtype": "Check", - "has_next_condition": 0, + "has_next_condition": 1, "is_table_field": 0, "label": "Auto Create Assets on Purchase", + "next_step_condition": "eval:doc.auto_create_assets", "parent_field": "", "position": "Bottom", "title": "Auto Create Asset on Purchase" @@ -69,7 +85,7 @@ "is_table_field": 0, "label": "Asset Category", "parent_field": "", - "position": "Bottom", + "position": "Left", "title": "Asset Category" }, { @@ -81,9 +97,9 @@ "is_table_field": 0, "label": "Asset Naming Series", "parent_field": "", - "position": "Bottom", + "position": "Left", "title": "Asset Naming Series" } ], "title": "Item" -} +} \ No newline at end of file From 427938f87bd06ff0803ce7c1a079cb455fd4f96d Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Thu, 2 Dec 2021 11:25:47 +0530 Subject: [PATCH 02/28] fix: new item form tour for home onboarding --- .../form_tour/item_general/item_general.json | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 erpnext/stock/form_tour/item_general/item_general.json diff --git a/erpnext/stock/form_tour/item_general/item_general.json b/erpnext/stock/form_tour/item_general/item_general.json new file mode 100644 index 0000000000..b468d270de --- /dev/null +++ b/erpnext/stock/form_tour/item_general/item_general.json @@ -0,0 +1,79 @@ +{ + "creation": "2021-12-02 10:37:55.433087", + "docstatus": 0, + "doctype": "Form Tour", + "first_document": 0, + "idx": 0, + "include_name_field": 0, + "is_standard": 1, + "modified": "2021-12-02 10:37:55.433087", + "modified_by": "Administrator", + "module": "Stock", + "name": "Item General", + "owner": "Administrator", + "reference_doctype": "Item", + "save_on_complete": 1, + "steps": [ + { + "description": "Enter code for the Item", + "field": "", + "fieldname": "item_code", + "fieldtype": "Data", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Item Code", + "parent_field": "", + "position": "Right", + "title": "Item Code" + }, + { + "description": "Enter name for the Item", + "field": "", + "fieldname": "item_name", + "fieldtype": "Data", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Item Name", + "parent_field": "", + "position": "Right", + "title": "Item Name" + }, + { + "description": "Select an Item Group", + "field": "", + "fieldname": "item_group", + "fieldtype": "Link", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Item Group", + "parent_field": "", + "position": "Right", + "title": "Item Group" + }, + { + "description": "This is the default measuring unit that you will use for your product. It could be Nos, Kgs, Meters, etc.", + "field": "", + "fieldname": "stock_uom", + "fieldtype": "Link", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Default Unit of Measure", + "parent_field": "", + "position": "Right", + "title": "Default Unit of Measurement" + }, + { + "description": "When creating an Item, entering a value for this field will automatically create an Item Price at the backend. Entering a value after the Item has been saved will not work. In this case, the Item Price is created from any transactions with the Item.", + "field": "", + "fieldname": "standard_rate", + "fieldtype": "Currency", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Standard Selling Rate", + "parent_field": "", + "position": "Left", + "title": "Standard Selling Rate" + } + ], + "title": "Item General" +} \ No newline at end of file From a108df8efc9e26fcece1f2f742902e20c9dd30c4 Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Thu, 2 Dec 2021 11:27:37 +0530 Subject: [PATCH 03/28] fix: assigned proper form tour to onboarding steps --- erpnext/assets/module_onboarding/assets/assets.json | 2 +- .../onboarding_step/asset_category/asset_category.json | 2 +- .../assets/onboarding_step/asset_item/asset_item.json | 9 +++++---- .../onboarding_step/asset_purchase/asset_purchase.json | 2 +- .../fixed_asset_accounts/fixed_asset_accounts.json | 2 +- erpnext/setup/module_onboarding/home/home.json | 2 +- .../onboarding_step/company_set_up/company_set_up.json | 2 +- .../create_a_customer/create_a_customer.json | 2 +- .../create_a_quotation/create_a_quotation.json | 2 +- .../create_a_supplier/create_a_supplier.json | 2 +- .../onboarding_step/create_an_item/create_an_item.json | 3 ++- .../setup/onboarding_step/data_import/data_import.json | 2 +- erpnext/setup/onboarding_step/letterhead/letterhead.json | 2 +- .../onboarding_step/navigation_help/navigation_help.json | 2 +- 14 files changed, 19 insertions(+), 17 deletions(-) diff --git a/erpnext/assets/module_onboarding/assets/assets.json b/erpnext/assets/module_onboarding/assets/assets.json index e6df88b000..796245df0c 100644 --- a/erpnext/assets/module_onboarding/assets/assets.json +++ b/erpnext/assets/module_onboarding/assets/assets.json @@ -13,7 +13,7 @@ "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/asset", "idx": 0, "is_complete": 0, - "modified": "2021-08-24 17:50:41.573281", + "modified": "2021-12-02 11:24:37.963746", "modified_by": "Administrator", "module": "Assets", "name": "Assets", diff --git a/erpnext/assets/onboarding_step/asset_category/asset_category.json b/erpnext/assets/onboarding_step/asset_category/asset_category.json index 033e86669c..58f322eecf 100644 --- a/erpnext/assets/onboarding_step/asset_category/asset_category.json +++ b/erpnext/assets/onboarding_step/asset_category/asset_category.json @@ -9,7 +9,7 @@ "is_complete": 0, "is_single": 0, "is_skipped": 0, - "modified": "2021-08-24 12:49:37.665239", + "modified": "2021-11-23 10:02:03.242127", "modified_by": "Administrator", "name": "Asset Category", "owner": "Administrator", diff --git a/erpnext/assets/onboarding_step/asset_item/asset_item.json b/erpnext/assets/onboarding_step/asset_item/asset_item.json index 8a174c5b77..13e3e2e838 100644 --- a/erpnext/assets/onboarding_step/asset_item/asset_item.json +++ b/erpnext/assets/onboarding_step/asset_item/asset_item.json @@ -1,21 +1,22 @@ { - "action": "Show Form Tour", + "action": "Create Entry", "action_label": "Let's create a new Asset item", "creation": "2021-08-13 14:27:07.277167", "description": "# Asset Item\n\nAsset items are created based on Asset Category. You can create one or multiple items against once Asset Category. The sales and purchase transaction for Asset is done via Asset Item. ", "docstatus": 0, "doctype": "Onboarding Step", + "form_tour": "Item", "idx": 0, "is_complete": 0, "is_single": 0, "is_skipped": 0, - "modified": "2021-08-16 13:59:18.362233", + "modified": "2021-12-02 11:23:48.158504", "modified_by": "Administrator", "name": "Asset Item", "owner": "Administrator", "reference_document": "Item", - "show_form_tour": 0, - "show_full_form": 0, + "show_form_tour": 1, + "show_full_form": 1, "title": "Create an Asset Item", "validate_action": 1 } \ No newline at end of file diff --git a/erpnext/assets/onboarding_step/asset_purchase/asset_purchase.json b/erpnext/assets/onboarding_step/asset_purchase/asset_purchase.json index 54611edc29..69fa337ae0 100644 --- a/erpnext/assets/onboarding_step/asset_purchase/asset_purchase.json +++ b/erpnext/assets/onboarding_step/asset_purchase/asset_purchase.json @@ -9,7 +9,7 @@ "is_complete": 0, "is_single": 0, "is_skipped": 0, - "modified": "2021-08-24 17:26:57.180637", + "modified": "2021-11-23 10:02:03.235498", "modified_by": "Administrator", "name": "Asset Purchase", "owner": "Administrator", diff --git a/erpnext/assets/onboarding_step/fixed_asset_accounts/fixed_asset_accounts.json b/erpnext/assets/onboarding_step/fixed_asset_accounts/fixed_asset_accounts.json index cebee7a7ea..2fc6c46c14 100644 --- a/erpnext/assets/onboarding_step/fixed_asset_accounts/fixed_asset_accounts.json +++ b/erpnext/assets/onboarding_step/fixed_asset_accounts/fixed_asset_accounts.json @@ -9,7 +9,7 @@ "is_complete": 0, "is_single": 0, "is_skipped": 0, - "modified": "2021-08-24 17:46:37.646174", + "modified": "2021-11-23 10:02:03.229566", "modified_by": "Administrator", "name": "Fixed Asset Accounts", "owner": "Administrator", diff --git a/erpnext/setup/module_onboarding/home/home.json b/erpnext/setup/module_onboarding/home/home.json index fbff98ff8a..e3868163cf 100644 --- a/erpnext/setup/module_onboarding/home/home.json +++ b/erpnext/setup/module_onboarding/home/home.json @@ -25,7 +25,7 @@ "documentation_url": "https://docs.erpnext.com/docs/v13/user/manual/en/setting-up/company-setup", "idx": 0, "is_complete": 0, - "modified": "2021-11-24 16:40:15.154081", + "modified": "2021-12-02 11:24:49.108119", "modified_by": "Administrator", "module": "Setup", "name": "Home", diff --git a/erpnext/setup/onboarding_step/company_set_up/company_set_up.json b/erpnext/setup/onboarding_step/company_set_up/company_set_up.json index dc85189764..57a3a3c2ed 100644 --- a/erpnext/setup/onboarding_step/company_set_up/company_set_up.json +++ b/erpnext/setup/onboarding_step/company_set_up/company_set_up.json @@ -9,7 +9,7 @@ "is_complete": 0, "is_single": 0, "is_skipped": 0, - "modified": "2021-11-24 15:41:01.620963", + "modified": "2021-11-24 18:01:53.528032", "modified_by": "Administrator", "name": "Company Set Up", "owner": "Administrator", diff --git a/erpnext/setup/onboarding_step/create_a_customer/create_a_customer.json b/erpnext/setup/onboarding_step/create_a_customer/create_a_customer.json index c3162f2b87..3f67809773 100644 --- a/erpnext/setup/onboarding_step/create_a_customer/create_a_customer.json +++ b/erpnext/setup/onboarding_step/create_a_customer/create_a_customer.json @@ -9,7 +9,7 @@ "is_complete": 0, "is_single": 0, "is_skipped": 0, - "modified": "2021-11-24 15:52:20.882675", + "modified": "2021-11-24 18:01:53.508901", "modified_by": "Administrator", "name": "Create a Customer", "owner": "Administrator", diff --git a/erpnext/setup/onboarding_step/create_a_quotation/create_a_quotation.json b/erpnext/setup/onboarding_step/create_a_quotation/create_a_quotation.json index 12d4372470..039d32b69e 100644 --- a/erpnext/setup/onboarding_step/create_a_quotation/create_a_quotation.json +++ b/erpnext/setup/onboarding_step/create_a_quotation/create_a_quotation.json @@ -9,7 +9,7 @@ "is_complete": 0, "is_single": 0, "is_skipped": 0, - "modified": "2021-11-24 15:55:28.950596", + "modified": "2021-12-02 11:16:01.553277", "modified_by": "Administrator", "name": "Create a Quotation", "owner": "Administrator", diff --git a/erpnext/setup/onboarding_step/create_a_supplier/create_a_supplier.json b/erpnext/setup/onboarding_step/create_a_supplier/create_a_supplier.json index cc87d97ae4..02e8a4021a 100644 --- a/erpnext/setup/onboarding_step/create_a_supplier/create_a_supplier.json +++ b/erpnext/setup/onboarding_step/create_a_supplier/create_a_supplier.json @@ -9,7 +9,7 @@ "is_complete": 0, "is_single": 0, "is_skipped": 0, - "modified": "2021-11-24 15:52:36.656949", + "modified": "2021-11-24 18:01:53.499200", "modified_by": "Administrator", "name": "Create a Supplier", "owner": "Administrator", diff --git a/erpnext/setup/onboarding_step/create_an_item/create_an_item.json b/erpnext/setup/onboarding_step/create_an_item/create_an_item.json index 0d650de665..177525e5fb 100644 --- a/erpnext/setup/onboarding_step/create_an_item/create_an_item.json +++ b/erpnext/setup/onboarding_step/create_an_item/create_an_item.json @@ -5,12 +5,13 @@ "description": "Item is a product, of a or service offered by your company, or something you buy as a part of your supplies or raw materials.\n\nItems are integral to everything you do in ERPNext - from billing, purchasing to managing inventory. Everything you buy or sell, whether it is a physical product or a service is an Item. Items can be stock, non-stock, variants, serialized, batched, assets etc.\n", "docstatus": 0, "doctype": "Onboarding Step", + "form_tour": "Item General", "idx": 0, "intro_video_url": "", "is_complete": 0, "is_single": 0, "is_skipped": 0, - "modified": "2021-11-24 15:51:21.446969", + "modified": "2021-12-02 11:16:01.451477", "modified_by": "Administrator", "name": "Create an Item", "owner": "Administrator", diff --git a/erpnext/setup/onboarding_step/data_import/data_import.json b/erpnext/setup/onboarding_step/data_import/data_import.json index 5f4e8db4ab..f4628b7f08 100644 --- a/erpnext/setup/onboarding_step/data_import/data_import.json +++ b/erpnext/setup/onboarding_step/data_import/data_import.json @@ -9,7 +9,7 @@ "is_complete": 0, "is_single": 0, "is_skipped": 0, - "modified": "2021-11-24 15:54:37.622063", + "modified": "2021-11-24 18:01:53.480435", "modified_by": "Administrator", "name": "Data import", "owner": "Administrator", diff --git a/erpnext/setup/onboarding_step/letterhead/letterhead.json b/erpnext/setup/onboarding_step/letterhead/letterhead.json index b8d430365f..05ca5ca628 100644 --- a/erpnext/setup/onboarding_step/letterhead/letterhead.json +++ b/erpnext/setup/onboarding_step/letterhead/letterhead.json @@ -9,7 +9,7 @@ "is_complete": 0, "is_single": 0, "is_skipped": 0, - "modified": "2021-11-24 16:19:25.379798", + "modified": "2021-11-24 18:01:53.459675", "modified_by": "Administrator", "name": "Letterhead", "owner": "Administrator", diff --git a/erpnext/setup/onboarding_step/navigation_help/navigation_help.json b/erpnext/setup/onboarding_step/navigation_help/navigation_help.json index 1271a86cb9..1cc58cb08b 100644 --- a/erpnext/setup/onboarding_step/navigation_help/navigation_help.json +++ b/erpnext/setup/onboarding_step/navigation_help/navigation_help.json @@ -9,7 +9,7 @@ "is_complete": 0, "is_single": 0, "is_skipped": 0, - "modified": "2021-11-24 15:52:40.641949", + "modified": "2021-11-24 18:01:53.490470", "modified_by": "Administrator", "name": "Navigation Help", "owner": "Administrator", From c3453cd73c14d744264373bab4b2380b63470021 Mon Sep 17 00:00:00 2001 From: ruthra Date: Fri, 10 Dec 2021 19:30:43 +0530 Subject: [PATCH 04/28] feat: Deferred Revenue and Expense report - show deferred revenue and expense with actual and expected postings - unit tests added --- .../deferred_revenue_and_expense/__init__.py | 0 .../deferred_revenue_and_expense.js | 114 +++++ .../deferred_revenue_and_expense.json | 32 ++ .../deferred_revenue_and_expense.py | 436 ++++++++++++++++++ .../test_deferred_revenue_and_expense.py | 253 ++++++++++ 5 files changed, 835 insertions(+) create mode 100644 erpnext/accounts/report/deferred_revenue_and_expense/__init__.py create mode 100644 erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.js create mode 100644 erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.json create mode 100644 erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py create mode 100644 erpnext/accounts/report/deferred_revenue_and_expense/test_deferred_revenue_and_expense.py diff --git a/erpnext/accounts/report/deferred_revenue_and_expense/__init__.py b/erpnext/accounts/report/deferred_revenue_and_expense/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.js b/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.js new file mode 100644 index 0000000000..0056b9e8f5 --- /dev/null +++ b/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.js @@ -0,0 +1,114 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +function get_filters() { + let filters = [ + { + "fieldname":"company", + "label": __("Company"), + "fieldtype": "Link", + "options": "Company", + "default": frappe.defaults.get_user_default("Company"), + "reqd": 1 + }, + { + "fieldname":"filter_based_on", + "label": __("Filter Based On"), + "fieldtype": "Select", + "options": ["Fiscal Year", "Date Range"], + "default": ["Fiscal Year"], + "reqd": 1, + on_change: function() { + let filter_based_on = frappe.query_report.get_filter_value('filter_based_on'); + frappe.query_report.toggle_filter_display('from_fiscal_year', filter_based_on === 'Date Range'); + frappe.query_report.toggle_filter_display('to_fiscal_year', filter_based_on === 'Date Range'); + frappe.query_report.toggle_filter_display('period_start_date', filter_based_on === 'Fiscal Year'); + frappe.query_report.toggle_filter_display('period_end_date', filter_based_on === 'Fiscal Year'); + + frappe.query_report.refresh(); + } + }, + { + "fieldname":"period_start_date", + "label": __("Start Date"), + "fieldtype": "Date", + "hidden": 1, + "reqd": 1 + }, + { + "fieldname":"period_end_date", + "label": __("End Date"), + "fieldtype": "Date", + "hidden": 1, + "reqd": 1 + }, + { + "fieldname":"from_fiscal_year", + "label": __("Start Year"), + "fieldtype": "Link", + "options": "Fiscal Year", + "default": frappe.defaults.get_user_default("fiscal_year"), + "reqd": 1 + }, + { + "fieldname":"to_fiscal_year", + "label": __("End Year"), + "fieldtype": "Link", + "options": "Fiscal Year", + "default": frappe.defaults.get_user_default("fiscal_year"), + "reqd": 1 + }, + { + "fieldname": "periodicity", + "label": __("Periodicity"), + "fieldtype": "Select", + "options": [ + { "value": "Monthly", "label": __("Monthly") }, + { "value": "Quarterly", "label": __("Quarterly") }, + { "value": "Half-Yearly", "label": __("Half-Yearly") }, + { "value": "Yearly", "label": __("Yearly") } + ], + "default": "Monthly", + "reqd": 1 + }, + { + "fieldname": "type", + "label": __("Invoice Type"), + "fieldtype": "Select", + "options": [ + { "value": "Revenue", "label": __("Revenue") }, + { "value": "Expense", "label": __("Expense") } + ], + "default": "Revenue", + "reqd": 1 + }, + { + "fieldname" : "with_upcoming_postings", + "label": __("Show with upcoming revenue/expense"), + "fieldtype": "Check", + "default": 1 + } + ] + + return filters; +} + +frappe.query_reports["Deferred Revenue and Expense"] = { + "filters": get_filters(), + "formatter": function(value, row, column, data, default_formatter){ + return default_formatter(value, row, column, data); + }, + onload: function(report){ + let fiscal_year = frappe.defaults.get_user_default("fiscal_year"); + + frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) { + var fy = frappe.model.get_doc("Fiscal Year", fiscal_year); + frappe.query_report.set_filter_value({ + period_start_date: fy.year_start_date, + period_end_date: fy.year_end_date + }); + }); + } +}; + diff --git a/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.json b/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.json new file mode 100644 index 0000000000..c7dfb3b714 --- /dev/null +++ b/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.json @@ -0,0 +1,32 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2021-12-10 19:27:14.654220", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "modified": "2021-12-10 19:27:14.654220", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Deferred Revenue and Expense", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "GL Entry", + "report_name": "Deferred Revenue and Expense", + "report_type": "Script Report", + "roles": [ + { + "role": "Accounts User" + }, + { + "role": "Accounts Manager" + }, + { + "role": "Auditor" + } + ] +} \ No newline at end of file diff --git a/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py b/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py new file mode 100644 index 0000000000..91c5bb4965 --- /dev/null +++ b/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py @@ -0,0 +1,436 @@ +# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# License: MIT. See LICENSE + +import frappe +from frappe import qb +from frappe.query_builder import Column, functions +from frappe.utils import add_days, date_diff, flt, get_first_day, get_last_day, rounded + +from erpnext.accounts.report.financial_statements import get_period_list + + +class Deferred_Item(object): + """ + Helper class for processing items with deferred revenue/expense + """ + + def __init__(self, item, inv, gle_entries): + self.name = item + self.parent = inv.name + self.item_name = gle_entries[0].item_name + self.service_start_date = gle_entries[0].service_start_date + self.service_end_date = gle_entries[0].service_end_date + self.base_net_amount = gle_entries[0].base_net_amount + self.filters = inv.filters + self.period_list = inv.period_list + + if gle_entries[0].deferred_revenue_account: + self.type = "Deferred Sale Item" + self.deferred_account = gle_entries[0].deferred_revenue_account + elif gle_entries[0].deferred_expense_account: + self.type = "Deferred Purchase Item" + self.deferred_account = gle_entries[0].deferred_expense_account + + self.gle_entries = [] + # holds period wise total for item + self.period_total = [] + self.last_entry_date = self.service_start_date + + if gle_entries: + self.gle_entries = gle_entries + for x in self.gle_entries: + if self.get_amount(x): + self.last_entry_date = x.gle_posting_date + + def report_data(self): + """ + Generate report data for output + """ + ret_data = frappe._dict({"name": self.item_name}) + for period in self.period_total: + ret_data[period.key] = period.total + ret_data.indent = 1 + return ret_data + + def get_amount(self, entry): + """ + For a given GL/Journal posting, get balance based on item type + """ + if self.type == "Deferred Sale Item": + return entry.debit - entry.credit + elif self.type == "Deferred Purchase Item": + return -(entry.credit - entry.debit) + return 0 + + def get_item_total(self): + """ + Helper method - calculate booked amount. Includes simulated postings as well + """ + total = 0 + for gle_posting in self.gle_entries: + total += self.get_amount(gle_posting) + + return total + + def calculate_amount(self, start_date, end_date): + """ + start_date, end_date - datetime.datetime.date + return - estimated amount to post for given period + Calculated based on already booked amount and item service period + """ + total_months = ( + (self.service_end_date.year - self.service_start_date.year) * 12 + + (self.service_end_date.month - self.service_start_date.month) + + 1 + ) + + prorate = date_diff(self.service_end_date, self.service_start_date) / date_diff( + get_last_day(self.service_end_date), get_first_day(self.service_start_date) + ) + + actual_months = rounded(total_months * prorate, 1) + + already_booked_amount = self.get_item_total() + base_amount = self.base_net_amount / actual_months + + if base_amount + already_booked_amount > self.base_net_amount: + base_amount = self.base_net_amount - already_booked_amount + + if not (get_first_day(start_date) == start_date and get_last_day(end_date) == end_date): + partial_month = flt(date_diff(end_date, start_date)) / flt( + date_diff(get_last_day(end_date), get_first_day(start_date)) + ) + base_amount *= rounded(partial_month, 1) + + return base_amount + + def make_dummy_gle(self, name, date, amount): + """ + return - frappe._dict() of a dummy gle entry + """ + entry = frappe._dict( + {"name": name, "gle_posting_date": date, "debit": 0, "credit": 0, "posted": "not"} + ) + if self.type == "Deferred Sale Item": + entry.debit = amount + elif self.type == "Deferred Purchase Item": + entry.credit = amount + return entry + + def simulate_future_posting(self): + """ + simulate future posting by creating dummy gl entries. starts from the last posting date. + """ + if add_days(self.last_entry_date, 1) < self.period_list[-1].to_date: + self.estimate_for_period_list = get_period_list( + self.filters.from_fiscal_year, + self.filters.to_fiscal_year, + add_days(self.last_entry_date, 1), + self.period_list[-1].to_date, + "Date Range", + "Monthly", + company=self.filters.company, + ) + for period in self.estimate_for_period_list: + amount = self.calculate_amount(period.from_date, period.to_date) + gle = self.make_dummy_gle(period.key, period.to_date, amount) + self.gle_entries.append(gle) + + def calculate_item_revenue_expense_for_period(self): + """ + calculate item postings for each period and update period_total list + """ + for period in self.period_list: + period_sum = 0 + actual = 0 + for posting in self.gle_entries: + # if period.from_date <= posting.posting_date <= period.to_date: + if period.from_date <= posting.gle_posting_date <= period.to_date: + period_sum += self.get_amount(posting) + if posting.posted == "posted": + actual += self.get_amount(posting) + + self.period_total.append( + frappe._dict({"key": period.key, "total": period_sum, "actual": actual}) + ) + return self.period_total + + +class Deferred_Invoice(object): + def __init__(self, invoice, items, filters, period_list): + """ + Helper class for processing invoices with deferred revenue/expense items + invoice - string : invoice name + items - list : frappe._dict() with item details. Refer Deferred_Item for required fields + """ + self.name = invoice + self.posting_date = items[0].posting_date + self.filters = filters + self.period_list = period_list + # holds period wise total for invoice + self.period_total = [] + + if items[0].deferred_revenue_account: + self.type = "Sales" + elif items[0].deferred_expense_account: + self.type = "Purchase" + + self.items = [] + # for each uniq items + self.uniq_items = set([x.item for x in items]) + for item in self.uniq_items: + self.items.append(Deferred_Item(item, self, [x for x in items if x.item == item])) + + def get_postings(self): + """ + get GL/Journal postings for deferred items in invoice + """ + [item.get_gl_and_journal_postings() for item in self.items] + + def calculate_invoice_revenue_expense_for_period(self): + """ + calculate deferred revenue/expense for all items in invoice + """ + # initialize period_total list for invoice + for period in self.period_list: + self.period_total.append(frappe._dict({"key": period.key, "total": 0, "actual": 0})) + + for item in self.items: + item_total = item.calculate_item_revenue_expense_for_period() + # update invoice total + for idx, period in enumerate(self.period_list, 0): + self.period_total[idx].total += item_total[idx].total + self.period_total[idx].actual += item_total[idx].actual + return self.period_total + + def estimate_future(self): + """ + create dummy GL entries for upcoming months for all items in invoice + """ + [item.simulate_future_posting() for item in self.items] + + def report_data(self): + """ + generate report data for invoice, includes invoice total + """ + ret_data = [] + inv_total = frappe._dict({"name": self.name}) + for x in self.period_total: + inv_total[x.key] = x.total + inv_total.indent = 0 + ret_data.append(inv_total) + list(map(lambda item: ret_data.append(item.report_data()), self.items)) + return ret_data + + +class Deferred_Revenue_and_Expense_Report(object): + def __init__(self, filters=None): + """ + Initialize deferred revenue/expense report with user provided filters or system defaults, if none is provided + """ + + # If no filters are provided, get user defaults + if not filters: + fiscal_year = frappe.get_doc("Fiscal Year", frappe.defaults.get_user_default("fiscal_year")) + self.filters = frappe._dict( + { + "company": frappe.defaults.get_user_default("Company"), + "filter_based_on": "Fiscal Year", + "period_start_date": fiscal_year.year_start_date, + "period_end_date": fiscal_year.year_end_date, + "from_fiscal_year": fiscal_year.year, + "to_fiscal_year": fiscal_year.year, + "periodicity": "Monthly", + "type": "Revenue", + "with_upcoming_postings": True, + } + ) + else: + self.filters = frappe._dict(filters) + + self.period_list = None + self.deferred_invoices = [] + # holds period wise total for report + self.period_total = [] + + def get_period_list(self): + """ + Figure out selected period based on filters + """ + self.period_list = get_period_list( + self.filters.from_fiscal_year, + self.filters.to_fiscal_year, + self.filters.period_start_date, + self.filters.period_end_date, + self.filters.filter_based_on, + self.filters.periodicity, + company=self.filters.company, + ) + + def get_invoices(self): + """ + Get all sales and purchase invoices which has deferred revenue/expense items + """ + gle = qb.DocType("GL Entry") + # column doesn't have an alias option + posted = Column("posted") + + if self.filters.type == "Revenue": + inv = qb.DocType("Sales Invoice") + inv_item = qb.DocType("Sales Invoice Item") + deferred_flag_field = inv_item["enable_deferred_revenue"] + deferred_account_field = inv_item["deferred_revenue_account"] + + elif self.filters.type == "Expense": + inv = qb.DocType("Purchase Invoice") + inv_item = qb.DocType("Purchase Invoice Item") + deferred_flag_field = inv_item["enable_deferred_expense"] + deferred_account_field = inv_item["deferred_expense_account"] + + query = ( + qb.from_(inv_item) + .join(inv) + .on(inv.name == inv_item.parent) + .join(gle) + .on((inv_item.name == gle.voucher_detail_no) & (deferred_account_field == gle.account)) + .select( + inv.name.as_("doc"), + inv.posting_date, + inv_item.name.as_("item"), + inv_item.item_name, + inv_item.service_start_date, + inv_item.service_end_date, + inv_item.base_net_amount, + deferred_account_field, + gle.posting_date.as_("gle_posting_date"), + functions.Sum(gle.debit).as_("debit"), + functions.Sum(gle.credit).as_("credit"), + posted, + ) + .where( + (inv.docstatus == 1) + & (deferred_flag_field == 1) + & ( + ( + (self.period_list[0].from_date >= inv_item.service_start_date) + & (inv_item.service_end_date >= self.period_list[0].from_date) + ) + | ( + (inv_item.service_start_date >= self.period_list[0].from_date) + & (inv_item.service_start_date <= self.period_list[-1].to_date) + ) + ) + ) + .groupby(inv.name, inv_item.name, gle.posting_date) + .orderby(gle.posting_date) + ) + self.invoices = query.run(as_dict=True) + + uniq_invoice = set([x.doc for x in self.invoices]) + for inv in uniq_invoice: + self.deferred_invoices.append( + Deferred_Invoice( + inv, [x for x in self.invoices if x.doc == inv], self.filters, self.period_list + ) + ) + + def estimate_future(self): + """ + For all Invoices estimate upcoming postings + """ + for x in self.deferred_invoices: + x.estimate_future() + + def calculate_revenue_and_expense(self): + """ + calculate the deferred revenue/expense for all invoices + """ + # initialize period_total list for report + for period in self.period_list: + self.period_total.append(frappe._dict({"key": period.key, "total": 0, "actual": 0})) + + for inv in self.deferred_invoices: + inv_total = inv.calculate_invoice_revenue_expense_for_period() + # calculate total for whole report + for idx, period in enumerate(self.period_list, 0): + self.period_total[idx].total += inv_total[idx].total + self.period_total[idx].actual += inv_total[idx].actual + + def get_columns(self): + columns = [] + columns.append({"label": "Name", "fieldname": "name", "fieldtype": "Data", "read_only": 1}) + for period in self.period_list: + columns.append( + {"label": period.label, "fieldname": period.key, "fieldtype": "Currency", "read_only": 1,} + ) + return columns + + def generate_report_data(self): + """ + Generate report data for all invoices. Adds total rows for revenue and expense + """ + ret = [] + + for inv in self.deferred_invoices: + ret += inv.report_data() + + # empty row for padding + ret += [{}] + + # add total row + if ret is not []: + if self.filters.type == "Revenue": + total_row = frappe._dict({"name": "Total Deferred Income"}) + elif self.filters.type == "Expense": + total_row = frappe._dict({"name": "Total Deferred Expense"}) + + for idx, period in enumerate(self.period_list, 0): + total_row[period.key] = self.period_total[idx].total + ret.append(total_row) + + return ret + + def prepare_chart(self): + chart = { + "data": { + "labels": [period.label for period in self.period_list], + "datasets": [ + { + "name": "Actual Posting", + "chartType": "bar", + "values": [x.actual for x in self.period_total], + }, + {"name": "Expected", "chartType": "line", "values": [x.total for x in self.period_total],}, + ], + }, + "type": "axis-mixed", + "height": 500, + "axisOptions": {"xAxisMode": "Tick", "xIsSeries": True}, + "barOptions": {"stacked": False, "spaceRatio": 0.5}, + } + + return chart + + def run(self, *args, **kwargs): + """ + Run report and generate data + """ + self.deferred_invoices.clear() + self.get_period_list() + self.get_invoices() + + if self.filters.with_upcoming_postings: + self.estimate_future() + self.calculate_revenue_and_expense() + + +def execute(filters=None): + report = Deferred_Revenue_and_Expense_Report(filters=filters) + report.run() + + columns = report.get_columns() + data = report.generate_report_data() + message = [] + chart = report.prepare_chart() + + return columns, data, message, chart diff --git a/erpnext/accounts/report/deferred_revenue_and_expense/test_deferred_revenue_and_expense.py b/erpnext/accounts/report/deferred_revenue_and_expense/test_deferred_revenue_and_expense.py new file mode 100644 index 0000000000..379bf0d272 --- /dev/null +++ b/erpnext/accounts/report/deferred_revenue_and_expense/test_deferred_revenue_and_expense.py @@ -0,0 +1,253 @@ +import datetime +import unittest + +import frappe +from frappe import qb +from frappe.utils import add_months, nowdate + +from erpnext.accounts.doctype.account.test_account import create_account +from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice +from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice +from erpnext.accounts.report.deferred_revenue_and_expense.deferred_revenue_and_expense import ( + Deferred_Revenue_and_Expense_Report, +) +from erpnext.buying.doctype.supplier.test_supplier import create_supplier +from erpnext.stock.doctype.item.test_item import create_item + + +class TestDeferredRevenueAndExpense(unittest.TestCase): + @classmethod + def setUpClass(self): + clear_old_entries() + create_company() + + def test_deferred_revenue(self): + # created deferred expense accounts, if not found + deferred_revenue_account = create_account( + account_name="Deferred Revenue", + parent_account="Current Liabilities - _CD", + company="_Test Company DR", + ) + + acc_settings = frappe.get_doc("Accounts Settings", "Accounts Settings") + acc_settings.book_deferred_entries_based_on = "Months" + acc_settings.save() + + customer = frappe.new_doc("Customer") + customer.customer_name = "_Test Customer DR" + customer.type = "Individual" + customer.insert() + + item = create_item( + "_Test Internet Subscription", + is_stock_item=0, + warehouse="All Warehouses - _CD", + company="_Test Company DR", + ) + item.enable_deferred_revenue = 1 + item.deferred_revenue_account = deferred_revenue_account + item.no_of_months = 3 + item.save() + + si = create_sales_invoice( + item=item.name, + company="_Test Company DR", + customer="_Test Customer DR", + debit_to="Debtors - _CD", + posting_date="2021-05-01", + parent_cost_center="Main - _CD", + cost_center="Main - _CD", + do_not_submit=True, + rate=300, + price_list_rate=300, + ) + si.items[0].enable_deferred_revenue = 1 + si.items[0].service_start_date = "2021-05-01" + si.items[0].service_end_date = "2021-08-01" + si.items[0].deferred_revenue_account = deferred_revenue_account + si.items[0].income_account = "Sales - _CD" + si.save() + si.submit() + + pda = frappe.get_doc( + dict( + doctype="Process Deferred Accounting", + posting_date=nowdate(), + start_date="2021-05-01", + end_date="2021-08-01", + type="Income", + company="_Test Company DR", + ) + ) + pda.insert() + pda.submit() + + # execute report + fiscal_year = frappe.get_doc("Fiscal Year", frappe.defaults.get_user_default("fiscal_year")) + self.filters = frappe._dict( + { + "company": frappe.defaults.get_user_default("Company"), + "filter_based_on": "Date Range", + "period_start_date": "2021-05-01", + "period_end_date": "2021-08-01", + "from_fiscal_year": fiscal_year.year, + "to_fiscal_year": fiscal_year.year, + "periodicity": "Monthly", + "type": "Revenue", + "with_upcoming_postings": False, + } + ) + + report = Deferred_Revenue_and_Expense_Report(filters=self.filters) + report.run() + expected = [ + {"key": "may_2021", "total": 100.0, "actual": 100.0}, + {"key": "jun_2021", "total": 100.0, "actual": 100.0}, + {"key": "jul_2021", "total": 100.0, "actual": 100.0}, + {"key": "aug_2021", "total": 0, "actual": 0}, + ] + self.assertEqual(report.period_total, expected) + + def test_deferred_expense(self): + # created deferred expense accounts, if not found + deferred_expense_account = create_account( + account_name="Deferred Expense", + parent_account="Current Assets - _CD", + company="_Test Company DR", + ) + + acc_settings = frappe.get_doc("Accounts Settings", "Accounts Settings") + acc_settings.book_deferred_entries_based_on = "Months" + acc_settings.save() + + supplier = create_supplier( + supplier_name="_Test Furniture Supplier", supplier_group="Local", supplier_type="Company" + ) + + item = create_item( + "_Test Office Desk", + is_stock_item=0, + warehouse="All Warehouses - _CD", + company="_Test Company DR", + ) + item.enable_deferred_expense = 1 + item.deferred_expense_account = deferred_expense_account + item.no_of_months_exp = 3 + item.save() + + pi = make_purchase_invoice( + item=item.name, + company="_Test Company DR", + supplier="_Test Furniture Supplier", + is_return=False, + update_stock=False, + posting_date=frappe.utils.datetime.date(2021, 5, 1), + parent_cost_center="Main - _CD", + cost_center="Main - _CD", + do_not_save=True, + rate=300, + price_list_rate=300, + warehouse="All Warehouses - _CD", + qty=1, + ) + pi.set_posting_time = True + pi.items[0].enable_deferred_expense = 1 + pi.items[0].service_start_date = "2021-05-01" + pi.items[0].service_end_date = "2021-08-01" + pi.items[0].deferred_expense_account = deferred_expense_account + pi.items[0].expense_account = "Office Maintenance Expenses - _CD" + pi.save() + pi.submit() + + pda = frappe.get_doc( + dict( + doctype="Process Deferred Accounting", + posting_date=nowdate(), + start_date="2021-05-01", + end_date="2021-08-01", + type="Expense", + company="_Test Company DR", + ) + ) + pda.insert() + pda.submit() + + # execute report + fiscal_year = frappe.get_doc("Fiscal Year", frappe.defaults.get_user_default("fiscal_year")) + self.filters = frappe._dict( + { + "company": frappe.defaults.get_user_default("Company"), + "filter_based_on": "Date Range", + "period_start_date": "2021-05-01", + "period_end_date": "2021-08-01", + "from_fiscal_year": fiscal_year.year, + "to_fiscal_year": fiscal_year.year, + "periodicity": "Monthly", + "type": "Expense", + "with_upcoming_postings": False, + } + ) + + report = Deferred_Revenue_and_Expense_Report(filters=self.filters) + report.run() + expected = [ + {"key": "may_2021", "total": -100.0, "actual": -100.0}, + {"key": "jun_2021", "total": -100.0, "actual": -100.0}, + {"key": "jul_2021", "total": -100.0, "actual": -100.0}, + {"key": "aug_2021", "total": 0, "actual": 0}, + ] + self.assertEqual(report.period_total, expected) + + +def create_company(): + company = frappe.db.exists("Company", "_Test Company DR") + if not company: + company = frappe.new_doc("Company") + company.company_name = "_Test Company DR" + company.default_currency = "INR" + company.chart_of_accounts = "Standard" + company.insert() + + +def clear_old_entries(): + item = qb.DocType("Item") + account = qb.DocType("Account") + customer = qb.DocType("Customer") + supplier = qb.DocType("Supplier") + sinv = qb.DocType("Sales Invoice") + sinv_item = qb.DocType("Sales Invoice Item") + pinv = qb.DocType("Purchase Invoice") + pinv_item = qb.DocType("Purchase Invoice Item") + + qb.from_(account).delete().where( + (account.account_name == "Deferred Revenue") + | (account.account_name == "Deferred Expense") & (account.company == "_Test Company DR") + ).run() + qb.from_(item).delete().where( + (item.item_code == "_Test Internet Subscription") | (item.item_code == "_Test Office Rent") + ).run() + qb.from_(customer).delete().where(customer.customer_name == "_Test Customer DR").run() + qb.from_(supplier).delete().where(supplier.supplier_name == "_Test Furniture Supplier").run() + + # delete existing invoices with deferred items + deferred_invoices = ( + qb.from_(sinv) + .join(sinv_item) + .on(sinv.name == sinv_item.parent) + .select(sinv.name) + .where(sinv_item.enable_deferred_revenue == 1) + .run() + ) + if deferred_invoices: + qb.from_(sinv).delete().where(sinv.name.isin(deferred_invoices)).run() + + deferred_invoices = ( + qb.from_(pinv) + .join(pinv_item) + .on(pinv.name == pinv_item.parent) + .select(pinv.name) + .where(pinv_item.enable_deferred_expense == 1) + .run() + ) + if deferred_invoices: + qb.from_(pinv).delete().where(pinv.name.isin(deferred_invoices)).run() From 2137e56947f9b1d3eca281866227aa996e8e7e93 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Mon, 13 Dec 2021 22:34:54 +0530 Subject: [PATCH 05/28] revert: "fix: expected value after useful life validation" --- erpnext/assets/doctype/asset/asset.py | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 65beac0af8..a18b03a888 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -495,7 +495,6 @@ class Asset(AccountsController): asset_value_after_full_schedule = flt( flt(self.gross_purchase_amount) - - flt(self.opening_accumulated_depreciation) - flt(accumulated_depreciation_after_full_schedule), self.precision('gross_purchase_amount')) if (row.expected_value_after_useful_life and From a8a598ace7d626c18527a65be618c1bad5b7bdf6 Mon Sep 17 00:00:00 2001 From: ruthra Date: Tue, 14 Dec 2021 16:34:51 +0530 Subject: [PATCH 06/28] refactor: remove unused function 'get_postings' --- .../deferred_revenue_and_expense.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py b/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py index 91c5bb4965..5bd97c6e29 100644 --- a/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py +++ b/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py @@ -181,12 +181,6 @@ class Deferred_Invoice(object): for item in self.uniq_items: self.items.append(Deferred_Item(item, self, [x for x in items if x.item == item])) - def get_postings(self): - """ - get GL/Journal postings for deferred items in invoice - """ - [item.get_gl_and_journal_postings() for item in self.items] - def calculate_invoice_revenue_expense_for_period(self): """ calculate deferred revenue/expense for all items in invoice From 80f1a8c645019f0587acd8116117d37e58e680e3 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 14 Dec 2021 17:02:42 +0530 Subject: [PATCH 07/28] fix: incorrect logic for "Reserved Qty for Production" (#28880) * fix: reservation for production incorrect The query uses case to decide what fields to compute reservation on, this case is outermost case hence the very first Work order's "Skip transfer" is considered for ALL work orders. Solution: move the case inside Sum. Steps to reproduce: 1. Make work order for more than 1 qty (with | without skip transfer) 2. Create manufacture and transfer entries. 3. Keep checking reserved quantities during this process. * test: use default warehouse for testing reservation --- erpnext/manufacturing/doctype/work_order/test_work_order.py | 2 +- erpnext/stock/doctype/bin/bin.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 1e74b6dda2..4e83b6511b 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -95,7 +95,7 @@ class TestWorkOrder(ERPNextTestCase): def test_reserved_qty_for_partial_completion(self): item = "_Test Item" - warehouse = create_warehouse("Test Warehouse for reserved_qty - _TC") + warehouse = "_Test Warehouse - _TC" bin1_at_start = get_bin(item, warehouse) diff --git a/erpnext/stock/doctype/bin/bin.py b/erpnext/stock/doctype/bin/bin.py index a33134b491..37b54116a4 100644 --- a/erpnext/stock/doctype/bin/bin.py +++ b/erpnext/stock/doctype/bin/bin.py @@ -43,9 +43,9 @@ class Bin(Document): frappe.qb .from_(wo) .from_(wo_item) - .select(Case() - .when(wo.skip_transfer == 0, Sum(wo_item.required_qty - wo_item.transferred_qty)) - .else_(Sum(wo_item.required_qty - wo_item.consumed_qty)) + .select(Sum(Case() + .when(wo.skip_transfer == 0, wo_item.required_qty - wo_item.transferred_qty) + .else_(wo_item.required_qty - wo_item.consumed_qty)) ) .where( (wo_item.item_code == self.item_code) From 66960cf0507649c58d04f31273c8d3255898f173 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 14 Dec 2021 17:12:46 +0530 Subject: [PATCH 08/28] fix: remove set only once from valuation method (#28876) --- erpnext/stock/doctype/item/item.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json index 4f4e69105a..29abd45fcc 100644 --- a/erpnext/stock/doctype/item/item.json +++ b/erpnext/stock/doctype/item/item.json @@ -361,8 +361,7 @@ "fieldname": "valuation_method", "fieldtype": "Select", "label": "Valuation Method", - "options": "\nFIFO\nMoving Average", - "set_only_once": 1 + "options": "\nFIFO\nMoving Average" }, { "depends_on": "is_stock_item", @@ -1035,7 +1034,7 @@ "image_field": "image", "index_web_pages_for_search": 1, "links": [], - "modified": "2021-12-03 08:32:03.869294", + "modified": "2021-12-14 04:13:16.857534", "modified_by": "Administrator", "module": "Stock", "name": "Item", From 96ef88d9355b00470d14fa1fa703235a8689d94d Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 14 Dec 2021 17:36:35 +0530 Subject: [PATCH 09/28] test: remove commits from tests (#27939) * test: remove commits from tests in selling module * test: remove commits from manufacturing module * test: remove commits from accounts module tests * test: remove commits from controller tests * test: keep commits in accounting dimension test * Revert "test: remove commits from controller tests" This reverts commit f5cfbee5da844f8b2bffeee907ac602754f16ff4. * Revert "test: remove commits from accounts module tests" This reverts commit df4c27efddf355e2731a18e8871e54d18590f219. --- .../doctype/blanket_order/test_blanket_order.py | 6 ++---- erpnext/manufacturing/doctype/bom/test_bom.py | 4 ++-- .../doctype/bom_update_tool/test_bom_update_tool.py | 7 ++----- erpnext/manufacturing/doctype/job_card/test_job_card.py | 6 +++--- .../doctype/production_plan/test_production_plan.py | 6 ++---- erpnext/manufacturing/doctype/routing/test_routing.py | 6 ++---- .../manufacturing/doctype/work_order/test_work_order.py | 1 - .../manufacturing/doctype/workstation/test_workstation.py | 6 ++---- erpnext/selling/doctype/customer/test_customer.py | 6 ++---- .../party_specific_item/test_party_specific_item.py | 3 ++- erpnext/selling/doctype/quotation/test_quotation.py | 6 +++--- erpnext/selling/doctype/sales_order/test_sales_order.py | 6 ++++-- .../test_pending_so_items_for_purchase_request.py | 5 ++--- erpnext/selling/report/sales_analytics/test_analytics.py | 5 ++--- 14 files changed, 30 insertions(+), 43 deletions(-) diff --git a/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py b/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py index d5db3fc1cc..eff2344e85 100644 --- a/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py +++ b/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py @@ -1,17 +1,15 @@ # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt - -import unittest - import frappe from frappe.utils import add_months, today from erpnext import get_company_currency +from erpnext.tests.utils import ERPNextTestCase from .blanket_order import make_order -class TestBlanketOrder(unittest.TestCase): +class TestBlanketOrder(ERPNextTestCase): def setUp(self): frappe.flags.args = frappe._dict() diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py index 4c032307d8..178d92c26c 100644 --- a/erpnext/manufacturing/doctype/bom/test_bom.py +++ b/erpnext/manufacturing/doctype/bom/test_bom.py @@ -2,7 +2,6 @@ # License: GNU General Public License v3. See license.txt -import unittest from collections import deque from functools import partial @@ -18,10 +17,11 @@ from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import create_stock_reconciliation, ) from erpnext.tests.test_subcontracting import set_backflush_based_on +from erpnext.tests.utils import ERPNextTestCase test_records = frappe.get_test_records('BOM') -class TestBOM(unittest.TestCase): +class TestBOM(ERPNextTestCase): def setUp(self): if not frappe.get_value('Item', '_Test Item'): make_test_records('Item') diff --git a/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py b/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py index 526c243ed7..12576cbf32 100644 --- a/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py +++ b/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py @@ -1,19 +1,16 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt - - -import unittest - import frappe from erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool import update_cost from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom from erpnext.stock.doctype.item.test_item import create_item +from erpnext.tests.utils import ERPNextTestCase test_records = frappe.get_test_records('BOM') -class TestBOMUpdateTool(unittest.TestCase): +class TestBOMUpdateTool(ERPNextTestCase): def test_replace_bom(self): current_bom = "BOM-_Test Item Home Desktop Manufactured-001" diff --git a/erpnext/manufacturing/doctype/job_card/test_job_card.py b/erpnext/manufacturing/doctype/job_card/test_job_card.py index 9b4fc8b8b7..bb5004ba86 100644 --- a/erpnext/manufacturing/doctype/job_card/test_job_card.py +++ b/erpnext/manufacturing/doctype/job_card/test_job_card.py @@ -1,6 +1,5 @@ # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -import unittest import frappe from frappe.utils import random_string @@ -12,9 +11,10 @@ from erpnext.manufacturing.doctype.job_card.job_card import ( from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record from erpnext.manufacturing.doctype.workstation.test_workstation import make_workstation from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry +from erpnext.tests.utils import ERPNextTestCase -class TestJobCard(unittest.TestCase): +class TestJobCard(ERPNextTestCase): def setUp(self): make_bom_for_jc_tests() @@ -329,4 +329,4 @@ def make_bom_for_jc_tests(): bom.rm_cost_as_per = "Valuation Rate" bom.items[0].uom = "_Test UOM 1" bom.items[0].conversion_factor = 5 - bom.insert() \ No newline at end of file + bom.insert() diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index a2980a732e..2febc1e23c 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -1,8 +1,5 @@ # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt - -import unittest - import frappe from frappe.utils import add_to_date, flt, now_datetime, nowdate @@ -17,9 +14,10 @@ from erpnext.stock.doctype.item.test_item import create_item from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import ( create_stock_reconciliation, ) +from erpnext.tests.utils import ERPNextTestCase -class TestProductionPlan(unittest.TestCase): +class TestProductionPlan(ERPNextTestCase): def setUp(self): for item in ['Test Production Item 1', 'Subassembly Item 1', 'Raw Material Item 1', 'Raw Material Item 2']: diff --git a/erpnext/manufacturing/doctype/routing/test_routing.py b/erpnext/manufacturing/doctype/routing/test_routing.py index 68d9dec04b..e90b0a7d6d 100644 --- a/erpnext/manufacturing/doctype/routing/test_routing.py +++ b/erpnext/manufacturing/doctype/routing/test_routing.py @@ -1,17 +1,15 @@ # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt - -import unittest - import frappe from frappe.test_runner import make_test_records from erpnext.manufacturing.doctype.job_card.job_card import OperationSequenceError from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record from erpnext.stock.doctype.item.test_item import make_item +from erpnext.tests.utils import ERPNextTestCase -class TestRouting(unittest.TestCase): +class TestRouting(ERPNextTestCase): @classmethod def setUpClass(cls): cls.item_code = "Test Routing Item - A" diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 4e83b6511b..165e0acebb 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -1,6 +1,5 @@ # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -import unittest import frappe from frappe.utils import add_months, cint, flt, now, today diff --git a/erpnext/manufacturing/doctype/workstation/test_workstation.py b/erpnext/manufacturing/doctype/workstation/test_workstation.py index 5ed5153528..c298c0a8db 100644 --- a/erpnext/manufacturing/doctype/workstation/test_workstation.py +++ b/erpnext/manufacturing/doctype/workstation/test_workstation.py @@ -1,8 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and Contributors # See license.txt - -import unittest - import frappe from frappe.test_runner import make_test_records @@ -13,12 +10,13 @@ from erpnext.manufacturing.doctype.workstation.workstation import ( WorkstationHolidayError, check_if_within_operating_hours, ) +from erpnext.tests.utils import ERPNextTestCase test_dependencies = ["Warehouse"] test_records = frappe.get_test_records('Workstation') make_test_records('Workstation') -class TestWorkstation(unittest.TestCase): +class TestWorkstation(ERPNextTestCase): def test_validate_timings(self): check_if_within_operating_hours("_Test Workstation 1", "Operation 1", "2013-02-02 11:00:00", "2013-02-02 19:00:00") check_if_within_operating_hours("_Test Workstation 1", "Operation 1", "2013-02-02 10:00:00", "2013-02-02 20:00:00") diff --git a/erpnext/selling/doctype/customer/test_customer.py b/erpnext/selling/doctype/customer/test_customer.py index 7d6b74d066..5301fd0524 100644 --- a/erpnext/selling/doctype/customer/test_customer.py +++ b/erpnext/selling/doctype/customer/test_customer.py @@ -2,8 +2,6 @@ # License: GNU General Public License v3. See license.txt -import unittest - import frappe from frappe.test_runner import make_test_records from frappe.utils import flt @@ -11,7 +9,7 @@ from frappe.utils import flt from erpnext.accounts.party import get_due_date from erpnext.exceptions import PartyDisabled, PartyFrozen from erpnext.selling.doctype.customer.customer import get_credit_limit, get_customer_outstanding -from erpnext.tests.utils import create_test_contact_and_address +from erpnext.tests.utils import ERPNextTestCase, create_test_contact_and_address test_ignore = ["Price List"] test_dependencies = ['Payment Term', 'Payment Terms Template'] @@ -19,7 +17,7 @@ test_records = frappe.get_test_records('Customer') -class TestCustomer(unittest.TestCase): +class TestCustomer(ERPNextTestCase): def setUp(self): if not frappe.get_value('Item', '_Test Item'): make_test_records('Item') diff --git a/erpnext/selling/doctype/party_specific_item/test_party_specific_item.py b/erpnext/selling/doctype/party_specific_item/test_party_specific_item.py index 874a364592..b951044f33 100644 --- a/erpnext/selling/doctype/party_specific_item/test_party_specific_item.py +++ b/erpnext/selling/doctype/party_specific_item/test_party_specific_item.py @@ -6,6 +6,7 @@ import unittest import frappe from erpnext.controllers.queries import item_query +from erpnext.tests.utils import ERPNextTestCase test_dependencies = ['Item', 'Customer', 'Supplier'] @@ -17,7 +18,7 @@ def create_party_specific_item(**args): psi.based_on_value = args.get('based_on_value') psi.insert() -class TestPartySpecificItem(unittest.TestCase): +class TestPartySpecificItem(ERPNextTestCase): def setUp(self): self.customer = frappe.get_last_doc("Customer") self.supplier = frappe.get_last_doc("Supplier") diff --git a/erpnext/selling/doctype/quotation/test_quotation.py b/erpnext/selling/doctype/quotation/test_quotation.py index aa83726304..4357201d23 100644 --- a/erpnext/selling/doctype/quotation/test_quotation.py +++ b/erpnext/selling/doctype/quotation/test_quotation.py @@ -1,15 +1,15 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -import unittest - import frappe from frappe.utils import add_days, add_months, flt, getdate, nowdate +from erpnext.tests.utils import ERPNextTestCase + test_dependencies = ["Product Bundle"] -class TestQuotation(unittest.TestCase): +class TestQuotation(ERPNextTestCase): def test_make_quotation_without_terms(self): quotation = make_quotation(do_not_save=1) self.assertFalse(quotation.get('payment_schedule')) diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index 2a0752e56a..42bc0b70f8 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -2,7 +2,6 @@ # License: GNU General Public License v3. See license.txt import json -import unittest import frappe import frappe.permissions @@ -28,12 +27,14 @@ from erpnext.selling.doctype.sales_order.sales_order import ( ) 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.tests.utils import ERPNextTestCase -class TestSalesOrder(unittest.TestCase): +class TestSalesOrder(ERPNextTestCase): @classmethod def setUpClass(cls): + super().setUpClass() cls.unlink_setting = int(frappe.db.get_value("Accounts Settings", "Accounts Settings", "unlink_advance_payment_on_cancelation_of_order")) @@ -42,6 +43,7 @@ class TestSalesOrder(unittest.TestCase): # reset config to previous state frappe.db.set_value("Accounts Settings", "Accounts Settings", "unlink_advance_payment_on_cancelation_of_order", cls.unlink_setting) + super().tearDownClass() def tearDown(self): frappe.set_user("Administrator") diff --git a/erpnext/selling/report/pending_so_items_for_purchase_request/test_pending_so_items_for_purchase_request.py b/erpnext/selling/report/pending_so_items_for_purchase_request/test_pending_so_items_for_purchase_request.py index 9c30afc5b1..d62915fc66 100644 --- a/erpnext/selling/report/pending_so_items_for_purchase_request/test_pending_so_items_for_purchase_request.py +++ b/erpnext/selling/report/pending_so_items_for_purchase_request/test_pending_so_items_for_purchase_request.py @@ -2,8 +2,6 @@ # For license information, please see license.txt -import unittest - from frappe.utils import add_months, nowdate from erpnext.selling.doctype.sales_order.sales_order import make_material_request @@ -11,9 +9,10 @@ from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_orde from erpnext.selling.report.pending_so_items_for_purchase_request.pending_so_items_for_purchase_request import ( execute, ) +from erpnext.tests.utils import ERPNextTestCase -class TestPendingSOItemsForPurchaseRequest(unittest.TestCase): +class TestPendingSOItemsForPurchaseRequest(ERPNextTestCase): def test_result_for_partial_material_request(self): so = make_sales_order() mr=make_material_request(so.name) diff --git a/erpnext/selling/report/sales_analytics/test_analytics.py b/erpnext/selling/report/sales_analytics/test_analytics.py index 8ffc5d6d0a..f56cce2dfd 100644 --- a/erpnext/selling/report/sales_analytics/test_analytics.py +++ b/erpnext/selling/report/sales_analytics/test_analytics.py @@ -2,15 +2,14 @@ # For license information, please see license.txt -import unittest - import frappe from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order from erpnext.selling.report.sales_analytics.sales_analytics import execute +from erpnext.tests.utils import ERPNextTestCase -class TestAnalytics(unittest.TestCase): +class TestAnalytics(ERPNextTestCase): def test_sales_analytics(self): frappe.db.sql("delete from `tabSales Order` where company='_Test Company 2'") From c5fcbeff7a391cccfb01a75c179ab3bae9e64239 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 14 Dec 2021 18:45:27 +0530 Subject: [PATCH 10/28] fix: reset_expected_response_and_resolution on hold --- .../service_level_agreement.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py index 50f31fde2d..83ec5e7c8e 100644 --- a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py +++ b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py @@ -420,13 +420,13 @@ def handle_status_change(doc, apply_sla_for_resolution): if is_open_status(prev_status) and is_hold_status(doc.status): # Issue is on hold -> Set on_hold_since doc.on_hold_since = now_time + reset_expected_response_and_resolution(doc) # Replied to Open if is_hold_status(prev_status) and is_open_status(doc.status): # Issue was on hold -> Calculate Total Hold Time calculate_hold_hours() # Issue is open -> reset resolution_date - reset_expected_response_and_resolution(doc) reset_resolution_metrics(doc) # Open to Closed @@ -440,7 +440,6 @@ def handle_status_change(doc, apply_sla_for_resolution): # Issue was closed -> Calculate Total Hold Time from resolution_date calculate_hold_hours() # Issue is open -> reset resolution_date - reset_expected_response_and_resolution(doc) reset_resolution_metrics(doc) # Closed to Replied @@ -449,6 +448,7 @@ def handle_status_change(doc, apply_sla_for_resolution): calculate_hold_hours() # Issue is on hold -> Set on_hold_since doc.on_hold_since = now_time + reset_expected_response_and_resolution(doc) # Replied to Closed if is_hold_status(prev_status) and is_fulfilled_status(doc.status): @@ -645,7 +645,7 @@ def on_communication_update(doc, status): if ( doc.sent_or_received == "Received" # a reply is received and parent.get('status') == 'Open' # issue status is set as open from communication.py - and parent._doc_before_save + and parent.get_doc_before_save() and parent.get('status') != parent._doc_before_save.get('status') # status changed ): # undo the status change in db @@ -655,13 +655,16 @@ def on_communication_update(doc, status): elif ( doc.sent_or_received == "Sent" # a reply is sent and parent.get('first_responded_on') # first_responded_on is set from communication.py - and parent._doc_before_save + and parent.get_doc_before_save() and not parent._doc_before_save.get('first_responded_on') # first_responded_on was not set ): # reset first_responded_on since it will be handled/set later on parent.first_responded_on = None parent.flags.on_first_reply = True + else: + return + handle_status_change(parent, for_resolution) update_response_and_resolution_metrics(parent, for_resolution) update_agreement_status(parent, for_resolution) From 7c33df5d544919b40f7e190eb1a4ed53e97e1fd8 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 14 Dec 2021 19:32:31 +0530 Subject: [PATCH 11/28] chore: bump coverage requirements --- codecov.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/codecov.yml b/codecov.yml index 67bd4454ef..1fa602ab30 100644 --- a/codecov.yml +++ b/codecov.yml @@ -8,6 +8,16 @@ coverage: target: auto threshold: 0.5% + patch: + default: + target: 85% + threshold: 0% + base: auto + branches: + - develop + if_ci_failed: ignore + only_pulls: true + comment: layout: "diff, files" require_changes: true From 0add80b02f9b3c1e342ecf3f50bcb1343efbfa0f Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 14 Dec 2021 20:24:29 +0530 Subject: [PATCH 12/28] fix: failing tests --- .../service_level_agreement.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py index 83ec5e7c8e..c94700bdc5 100644 --- a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py +++ b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py @@ -640,8 +640,6 @@ def on_communication_update(doc, status): if not parent.meta.has_field('service_level_agreement'): return - for_resolution = frappe.db.get_value('Service Level Agreement', parent.service_level_agreement, 'apply_sla_for_resolution') - if ( doc.sent_or_received == "Received" # a reply is received and parent.get('status') == 'Open' # issue status is set as open from communication.py @@ -650,7 +648,11 @@ def on_communication_update(doc, status): ): # undo the status change in db # since prev status is fetched from db - frappe.db.set_value(parent.doctype, parent.name, 'status', parent._doc_before_save.get('status')) + frappe.db.set_value( + parent.doctype, parent.name, + 'status', parent._doc_before_save.get('status'), + update_modified=False + ) elif ( doc.sent_or_received == "Sent" # a reply is sent @@ -665,6 +667,8 @@ def on_communication_update(doc, status): else: return + for_resolution = frappe.db.get_value('Service Level Agreement', parent.service_level_agreement, 'apply_sla_for_resolution') + handle_status_change(parent, for_resolution) update_response_and_resolution_metrics(parent, for_resolution) update_agreement_status(parent, for_resolution) @@ -673,12 +677,10 @@ def on_communication_update(doc, status): def reset_expected_response_and_resolution(doc): - update_values = {} if doc.meta.has_field("first_responded_on") and not doc.get('first_responded_on'): - update_values['response_by'] = None + doc.response_by = None if doc.meta.has_field("resolution_by") and not doc.get('resolution_date'): - update_values['resolution_by'] = None - doc.db_set(update_values) + doc.resolution_by = None def set_response_by(doc, start_date_time, priority): From 9ec8841b91222cd46b9917209d169de5f01125c6 Mon Sep 17 00:00:00 2001 From: ruthra Date: Tue, 14 Dec 2021 20:59:14 +0530 Subject: [PATCH 13/28] refactor: rename checkbox and translations - rename checkbox and make it - added translation text for labels - fixes for linter error --- .../deferred_revenue_and_expense.py | 22 ++++++++++++++----- .../test_deferred_revenue_and_expense.py | 4 ++-- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py b/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py index 5bd97c6e29..a4842c1844 100644 --- a/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py +++ b/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py @@ -2,7 +2,7 @@ # License: MIT. See LICENSE import frappe -from frappe import qb +from frappe import _, qb from frappe.query_builder import Column, functions from frappe.utils import add_days, date_diff, flt, get_first_day, get_last_day, rounded @@ -352,11 +352,15 @@ class Deferred_Revenue_and_Expense_Report(object): def get_columns(self): columns = [] - columns.append({"label": "Name", "fieldname": "name", "fieldtype": "Data", "read_only": 1}) + columns.append({"label": _("Name"), "fieldname": "name", "fieldtype": "Data", "read_only": 1}) for period in self.period_list: columns.append( - {"label": period.label, "fieldname": period.key, "fieldtype": "Currency", "read_only": 1,} - ) + { + "label": _(period.label), + "fieldname": period.key, + "fieldtype": "Currency", + "read_only": 1, + }) return columns def generate_report_data(self): @@ -393,8 +397,7 @@ class Deferred_Revenue_and_Expense_Report(object): "name": "Actual Posting", "chartType": "bar", "values": [x.actual for x in self.period_total], - }, - {"name": "Expected", "chartType": "line", "values": [x.total for x in self.period_total],}, + } ], }, "type": "axis-mixed", @@ -403,6 +406,13 @@ class Deferred_Revenue_and_Expense_Report(object): "barOptions": {"stacked": False, "spaceRatio": 0.5}, } + if self.filters.with_upcoming_postings: + chart["data"]["datasets"].append({ + "name": "Expected", + "chartType": "line", + "values": [x.total for x in self.period_total] + }) + return chart def run(self, *args, **kwargs): diff --git a/erpnext/accounts/report/deferred_revenue_and_expense/test_deferred_revenue_and_expense.py b/erpnext/accounts/report/deferred_revenue_and_expense/test_deferred_revenue_and_expense.py index 379bf0d272..1de6fb6824 100644 --- a/erpnext/accounts/report/deferred_revenue_and_expense/test_deferred_revenue_and_expense.py +++ b/erpnext/accounts/report/deferred_revenue_and_expense/test_deferred_revenue_and_expense.py @@ -1,9 +1,8 @@ -import datetime import unittest import frappe from frappe import qb -from frappe.utils import add_months, nowdate +from frappe.utils import nowdate from erpnext.accounts.doctype.account.test_account import create_account from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice @@ -123,6 +122,7 @@ class TestDeferredRevenueAndExpense(unittest.TestCase): supplier = create_supplier( supplier_name="_Test Furniture Supplier", supplier_group="Local", supplier_type="Company" ) + supplier.save() item = create_item( "_Test Office Desk", From a481a7c90e1791e4eb3300487c89a78223f668a5 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 14 Dec 2021 22:03:44 +0530 Subject: [PATCH 14/28] fix(test): Employee Exit Summary (#28888) --- erpnext/hr/doctype/exit_interview/test_exit_interview.py | 4 ++-- erpnext/hr/report/employee_exits/employee_exits.py | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/erpnext/hr/doctype/exit_interview/test_exit_interview.py b/erpnext/hr/doctype/exit_interview/test_exit_interview.py index a0bf9b32ec..8e076edf0b 100644 --- a/erpnext/hr/doctype/exit_interview/test_exit_interview.py +++ b/erpnext/hr/doctype/exit_interview/test_exit_interview.py @@ -19,7 +19,7 @@ class TestExitInterview(unittest.TestCase): frappe.db.sql('delete from `tabExit Interview`') def test_duplicate_interview(self): - employee = make_employee('employeeexit1@example.com') + employee = make_employee('employeeexitint1@example.com') frappe.db.set_value('Employee', employee, 'relieving_date', getdate()) interview = create_exit_interview(employee) @@ -27,7 +27,7 @@ class TestExitInterview(unittest.TestCase): self.assertRaises(frappe.DuplicateEntryError, doc.save) def test_relieving_date_validation(self): - employee = make_employee('employeeexit2@example.com') + employee = make_employee('employeeexitint2@example.com') # unset relieving date frappe.db.set_value('Employee', employee, 'relieving_date', None) diff --git a/erpnext/hr/report/employee_exits/employee_exits.py b/erpnext/hr/report/employee_exits/employee_exits.py index 8e0b07d3e1..bde5a89926 100644 --- a/erpnext/hr/report/employee_exits/employee_exits.py +++ b/erpnext/hr/report/employee_exits/employee_exits.py @@ -108,12 +108,11 @@ def get_data(filters): interview.status.as_('interview_status'), interview.employee_status.as_('employee_status'), interview.reference_document_name.as_('questionnaire'), fnf.name.as_('full_and_final_statement')) .distinct() - .orderby(employee.relieving_date, order=Order.asc) .where( ((employee.relieving_date.isnotnull()) | (employee.relieving_date != '')) & ((interview.name.isnull()) | ((interview.name.isnotnull()) & (interview.docstatus != 2))) & ((fnf.name.isnull()) | ((fnf.name.isnotnull()) & (fnf.docstatus != 2))) - ) + ).orderby(employee.relieving_date, order=Order.asc) ) query = get_conditions(filters, query, employee, interview, fnf) From 2411477e6dfc2e0b541674219714b84edbcb34fa Mon Sep 17 00:00:00 2001 From: Saqib Date: Wed, 15 Dec 2021 11:45:34 +0530 Subject: [PATCH 15/28] fix: time to response shows failed (#28895) --- erpnext/public/js/utils.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index 6654048a96..93a273187a 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -888,9 +888,11 @@ $(document).on('app_ready', function() { function set_time_to_resolve_and_response(frm, apply_sla_for_resolution) { frm.dashboard.clear_headline(); - let time_to_respond = get_status(frm.doc.response_by); + let time_to_respond; if (!frm.doc.first_responded_on) { time_to_respond = get_time_left(frm.doc.response_by, frm.doc.agreement_status); + } else { + time_to_respond = get_status(frm.doc.response_by, frm.doc.first_responded_on); } let alert = ` @@ -903,9 +905,11 @@ function set_time_to_resolve_and_response(frm, apply_sla_for_resolution) { if (apply_sla_for_resolution) { - let time_to_resolve = get_status(frm.doc.resolution_by); + let time_to_resolve; if (!frm.doc.resolution_date) { time_to_resolve = get_time_left(frm.doc.resolution_by, frm.doc.agreement_status); + } else { + time_to_resolve = get_status(frm.doc.resolution_by, frm.doc.resolution_date); } alert += ` @@ -928,8 +932,8 @@ function get_time_left(timestamp, agreement_status) { return {'diff_display': diff_display, 'indicator': indicator}; } -function get_status(timestamp) { - const time_left = moment(timestamp).diff(moment()); +function get_status(expected, actual) { + const time_left = moment(expected).diff(moment(actual)); if (time_left >= 0) { return {'diff_display': 'Fulfilled', 'indicator': 'green'}; } else { From be103ab8e6a172a4cfb25e85d3985fa46a2cfc50 Mon Sep 17 00:00:00 2001 From: Subin Tom <36098155+nemesis189@users.noreply.github.com> Date: Wed, 15 Dec 2021 13:01:48 +0530 Subject: [PATCH 16/28] fix: Currency fix for `cost` field in subscription plan (#28821) --- .../doctype/subscription_plan/subscription_plan.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/subscription_plan/subscription_plan.json b/erpnext/accounts/doctype/subscription_plan/subscription_plan.json index 878ae09889..563df79eec 100644 --- a/erpnext/accounts/doctype/subscription_plan/subscription_plan.json +++ b/erpnext/accounts/doctype/subscription_plan/subscription_plan.json @@ -75,7 +75,8 @@ "fieldname": "cost", "fieldtype": "Currency", "in_list_view": 1, - "label": "Cost" + "label": "Cost", + "options": "currency" }, { "depends_on": "eval:doc.price_determination==\"Based On Price List\"", @@ -147,7 +148,7 @@ } ], "links": [], - "modified": "2021-08-13 10:53:44.205774", + "modified": "2021-12-10 15:24:15.794477", "modified_by": "Administrator", "module": "Accounts", "name": "Subscription Plan", From 5c4d3f89d2dd3102edeeb072b63f8ed5a0a22e13 Mon Sep 17 00:00:00 2001 From: Subin Tom <36098155+nemesis189@users.noreply.github.com> Date: Wed, 15 Dec 2021 14:14:16 +0530 Subject: [PATCH 17/28] feat: 'Invoice Number' field in Opening Invoice Creation Tool (#28844) --- .../opening_invoice_creation_tool.py | 8 +++++-- .../test_opening_invoice_creation_tool.py | 24 +++++++++++++++---- .../opening_invoice_creation_tool_item.json | 11 ++++++++- 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py index 2a923f009d..ddb833ff3b 100644 --- a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py +++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py @@ -159,7 +159,8 @@ class OpeningInvoiceCreationTool(Document): frappe.scrub(row.party_type): row.party, "is_pos": 0, "doctype": "Sales Invoice" if self.invoice_type == "Sales" else "Purchase Invoice", - "update_stock": 0 + "update_stock": 0, + "invoice_number": row.invoice_number }) accounting_dimension = get_accounting_dimensions() @@ -200,10 +201,13 @@ def start_import(invoices): names = [] for idx, d in enumerate(invoices): try: + invoice_number = None + if d.invoice_number: + invoice_number = d.invoice_number publish(idx, len(invoices), d.doctype) doc = frappe.get_doc(d) doc.flags.ignore_mandatory = True - doc.insert() + doc.insert(set_name=invoice_number) doc.submit() frappe.db.commit() names.append(doc.name) diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py b/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py index c795e83c56..07c72bde7b 100644 --- a/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py +++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py @@ -18,10 +18,10 @@ class TestOpeningInvoiceCreationTool(unittest.TestCase): if not frappe.db.exists("Company", "_Test Opening Invoice Company"): make_company() - def make_invoices(self, invoice_type="Sales", company=None, party_1=None, party_2=None): + def make_invoices(self, invoice_type="Sales", company=None, party_1=None, party_2=None, invoice_number=None): doc = frappe.get_single("Opening Invoice Creation Tool") args = get_opening_invoice_creation_dict(invoice_type=invoice_type, company=company, - party_1=party_1, party_2=party_2) + party_1=party_1, party_2=party_2, invoice_number=invoice_number) doc.update(args) return doc.make_invoices() @@ -92,6 +92,20 @@ class TestOpeningInvoiceCreationTool(unittest.TestCase): # teardown frappe.db.set_value("Company", company, "default_receivable_account", old_default_receivable_account) + def test_renaming_of_invoice_using_invoice_number_field(self): + company = "_Test Opening Invoice Company" + party_1, party_2 = make_customer("Customer A"), make_customer("Customer B") + self.make_invoices(company=company, party_1=party_1, party_2=party_2, invoice_number="TEST-NEW-INV-11") + + sales_inv1 = frappe.get_all('Sales Invoice', filters={'customer':'Customer A'})[0].get("name") + sales_inv2 = frappe.get_all('Sales Invoice', filters={'customer':'Customer B'})[0].get("name") + self.assertEqual(sales_inv1, "TEST-NEW-INV-11") + + #teardown + for inv in [sales_inv1, sales_inv2]: + doc = frappe.get_doc('Sales Invoice', inv) + doc.cancel() + def get_opening_invoice_creation_dict(**args): party = "Customer" if args.get("invoice_type", "Sales") == "Sales" else "Supplier" company = args.get("company", "_Test Company") @@ -107,7 +121,8 @@ def get_opening_invoice_creation_dict(**args): "item_name": "Opening Item", "due_date": "2016-09-10", "posting_date": "2016-09-05", - "temporary_opening_account": get_temporary_opening_account(company) + "temporary_opening_account": get_temporary_opening_account(company), + "invoice_number": args.get("invoice_number") }, { "qty": 2.0, @@ -116,7 +131,8 @@ def get_opening_invoice_creation_dict(**args): "item_name": "Opening Item", "due_date": "2016-09-10", "posting_date": "2016-09-05", - "temporary_opening_account": get_temporary_opening_account(company) + "temporary_opening_account": get_temporary_opening_account(company), + "invoice_number": None } ] }) diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool_item/opening_invoice_creation_tool_item.json b/erpnext/accounts/doctype/opening_invoice_creation_tool_item/opening_invoice_creation_tool_item.json index 4ce8cb95b1..040624f926 100644 --- a/erpnext/accounts/doctype/opening_invoice_creation_tool_item/opening_invoice_creation_tool_item.json +++ b/erpnext/accounts/doctype/opening_invoice_creation_tool_item/opening_invoice_creation_tool_item.json @@ -1,9 +1,11 @@ { + "actions": [], "creation": "2017-08-29 04:26:36.159247", "doctype": "DocType", "editable_grid": 1, "engine": "InnoDB", "field_order": [ + "invoice_number", "party_type", "party", "temporary_opening_account", @@ -103,10 +105,17 @@ { "fieldname": "dimension_col_break", "fieldtype": "Column Break" + }, + { + "description": "Reference number of the invoice from the previous system", + "fieldname": "invoice_number", + "fieldtype": "Data", + "label": "Invoice Number" } ], "istable": 1, - "modified": "2019-07-25 15:00:00.460695", + "links": [], + "modified": "2021-12-13 18:15:41.295007", "modified_by": "Administrator", "module": "Accounts", "name": "Opening Invoice Creation Tool Item", From 63618cc7abc3f0483e0f1a158c52f8749490a043 Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Wed, 15 Dec 2021 14:26:44 +0530 Subject: [PATCH 18/28] fix: titles and order --- erpnext/setup/module_onboarding/home/home.json | 14 +++++++------- .../company_set_up/company_set_up.json | 4 ++-- .../create_a_customer/create_a_customer.json | 4 ++-- .../create_a_quotation/create_a_quotation.json | 4 ++-- .../create_a_supplier/create_a_supplier.json | 4 ++-- .../create_an_item/create_an_item.json | 4 ++-- .../onboarding_step/data_import/data_import.json | 2 +- .../onboarding_step/letterhead/letterhead.json | 4 ++-- .../navigation_help/navigation_help.json | 4 ++-- 9 files changed, 22 insertions(+), 22 deletions(-) diff --git a/erpnext/setup/module_onboarding/home/home.json b/erpnext/setup/module_onboarding/home/home.json index e3868163cf..1b2dbc6fea 100644 --- a/erpnext/setup/module_onboarding/home/home.json +++ b/erpnext/setup/module_onboarding/home/home.json @@ -25,7 +25,7 @@ "documentation_url": "https://docs.erpnext.com/docs/v13/user/manual/en/setting-up/company-setup", "idx": 0, "is_complete": 0, - "modified": "2021-12-02 11:24:49.108119", + "modified": "2021-12-15 14:23:52.460913", "modified_by": "Administrator", "module": "Setup", "name": "Home", @@ -34,6 +34,12 @@ { "step": "Company Set Up" }, + { + "step": "Navigation Help" + }, + { + "step": "Data import" + }, { "step": "Create an Item" }, @@ -43,12 +49,6 @@ { "step": "Create a Supplier" }, - { - "step": "Navigation Help" - }, - { - "step": "Data import" - }, { "step": "Create a Quotation" }, diff --git a/erpnext/setup/onboarding_step/company_set_up/company_set_up.json b/erpnext/setup/onboarding_step/company_set_up/company_set_up.json index 57a3a3c2ed..6f6583231f 100644 --- a/erpnext/setup/onboarding_step/company_set_up/company_set_up.json +++ b/erpnext/setup/onboarding_step/company_set_up/company_set_up.json @@ -1,6 +1,6 @@ { "action": "Create Entry", - "action_label": "Let's create a Company", + "action_label": "Let's review your Company", "creation": "2021-11-22 11:55:48.931427", "description": "# Set Up a Company\n\nA company is a legal entity for which you will set up your books of account and create accounting transactions. In ERPNext, you can create multiple companies, and establish relationships (group/subsidiary) among them.\n\nWithin the company master, you can capture various default accounts for that Company and set crucial settings related to the accounting methodology followed for a company.\n", "docstatus": 0, @@ -9,7 +9,7 @@ "is_complete": 0, "is_single": 0, "is_skipped": 0, - "modified": "2021-11-24 18:01:53.528032", + "modified": "2021-12-15 14:22:18.317423", "modified_by": "Administrator", "name": "Company Set Up", "owner": "Administrator", diff --git a/erpnext/setup/onboarding_step/create_a_customer/create_a_customer.json b/erpnext/setup/onboarding_step/create_a_customer/create_a_customer.json index 3f67809773..f74d745be9 100644 --- a/erpnext/setup/onboarding_step/create_a_customer/create_a_customer.json +++ b/erpnext/setup/onboarding_step/create_a_customer/create_a_customer.json @@ -2,14 +2,14 @@ "action": "Create Entry", "action_label": "Let\u2019s create your first Customer", "creation": "2020-05-14 17:46:41.831517", - "description": "The Customer master is at the heart of your sales transactions. Customers are linked in Quotations, Sales Orders, Invoices, and Payments. Customers can be either numbered or identified by name (you would typically do this based on the number of customers you have).\n\nThrough Customer\u2019s master, you can effectively track essentials like:\n - Customer\u2019s multiple address and contacts\n - Account Receivables\n - Credit Limit and Credit Period\n", + "description": "# Create a Customer\n\nThe Customer master is at the heart of your sales transactions. Customers are linked in Quotations, Sales Orders, Invoices, and Payments. Customers can be either numbered or identified by name (you would typically do this based on the number of customers you have).\n\nThrough Customer\u2019s master, you can effectively track essentials like:\n - Customer\u2019s multiple address and contacts\n - Account Receivables\n - Credit Limit and Credit Period\n", "docstatus": 0, "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, "is_single": 0, "is_skipped": 0, - "modified": "2021-11-24 18:01:53.508901", + "modified": "2021-12-15 14:20:31.197564", "modified_by": "Administrator", "name": "Create a Customer", "owner": "Administrator", diff --git a/erpnext/setup/onboarding_step/create_a_quotation/create_a_quotation.json b/erpnext/setup/onboarding_step/create_a_quotation/create_a_quotation.json index 039d32b69e..8bdb621c0a 100644 --- a/erpnext/setup/onboarding_step/create_a_quotation/create_a_quotation.json +++ b/erpnext/setup/onboarding_step/create_a_quotation/create_a_quotation.json @@ -2,14 +2,14 @@ "action": "Create Entry", "action_label": "Let\u2019s create your first Quotation", "creation": "2020-06-01 13:34:58.958641", - "description": "# Quotation\n\nLet\u2019s get started with business transactions by creating your first Quotation. You can create a Quotation for an existing customer or a prospect. It will be an approved document, with items you sell and the proposed price + taxes applied. After completing the instructions, you will get a Quotation in a ready to share print format.", + "description": "# Create a Quotation\n\nLet\u2019s get started with business transactions by creating your first Quotation. You can create a Quotation for an existing customer or a prospect. It will be an approved document, with items you sell and the proposed price + taxes applied. After completing the instructions, you will get a Quotation in a ready to share print format.", "docstatus": 0, "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, "is_single": 0, "is_skipped": 0, - "modified": "2021-12-02 11:16:01.553277", + "modified": "2021-12-15 14:21:31.675330", "modified_by": "Administrator", "name": "Create a Quotation", "owner": "Administrator", diff --git a/erpnext/setup/onboarding_step/create_a_supplier/create_a_supplier.json b/erpnext/setup/onboarding_step/create_a_supplier/create_a_supplier.json index 02e8a4021a..9574141eaa 100644 --- a/erpnext/setup/onboarding_step/create_a_supplier/create_a_supplier.json +++ b/erpnext/setup/onboarding_step/create_a_supplier/create_a_supplier.json @@ -2,14 +2,14 @@ "action": "Create Entry", "action_label": "Let\u2019s create your first Supplier", "creation": "2020-05-14 22:09:10.043554", - "description": "# Supplier\n\nAlso known as Vendor, is a master at the center of your purchase transactions. Suppliers are linked in Request for Quotation, Purchase Orders, Receipts, and Payments. Suppliers can be either numbered or identified by name.\n\nThrough Supplier\u2019s master, you can effectively track essentials like:\n - Supplier\u2019s multiple address and contacts\n - Account Receivables\n - Credit Limit and Credit Period\n", + "description": "# Create a Supplier\n\nAlso known as Vendor, is a master at the center of your purchase transactions. Suppliers are linked in Request for Quotation, Purchase Orders, Receipts, and Payments. Suppliers can be either numbered or identified by name.\n\nThrough Supplier\u2019s master, you can effectively track essentials like:\n - Supplier\u2019s multiple address and contacts\n - Account Receivables\n - Credit Limit and Credit Period\n", "docstatus": 0, "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, "is_single": 0, "is_skipped": 0, - "modified": "2021-11-24 18:01:53.499200", + "modified": "2021-12-15 14:21:23.518301", "modified_by": "Administrator", "name": "Create a Supplier", "owner": "Administrator", diff --git a/erpnext/setup/onboarding_step/create_an_item/create_an_item.json b/erpnext/setup/onboarding_step/create_an_item/create_an_item.json index 177525e5fb..cd29683346 100644 --- a/erpnext/setup/onboarding_step/create_an_item/create_an_item.json +++ b/erpnext/setup/onboarding_step/create_an_item/create_an_item.json @@ -2,7 +2,7 @@ "action": "Create Entry", "action_label": "Create a new Item", "creation": "2021-05-17 13:47:18.515052", - "description": "Item is a product, of a or service offered by your company, or something you buy as a part of your supplies or raw materials.\n\nItems are integral to everything you do in ERPNext - from billing, purchasing to managing inventory. Everything you buy or sell, whether it is a physical product or a service is an Item. Items can be stock, non-stock, variants, serialized, batched, assets etc.\n", + "description": "# Create an Item\n\nItem is a product, of a or service offered by your company, or something you buy as a part of your supplies or raw materials.\n\nItems are integral to everything you do in ERPNext - from billing, purchasing to managing inventory. Everything you buy or sell, whether it is a physical product or a service is an Item. Items can be stock, non-stock, variants, serialized, batched, assets etc.\n", "docstatus": 0, "doctype": "Onboarding Step", "form_tour": "Item General", @@ -11,7 +11,7 @@ "is_complete": 0, "is_single": 0, "is_skipped": 0, - "modified": "2021-12-02 11:16:01.451477", + "modified": "2021-12-15 14:19:56.297772", "modified_by": "Administrator", "name": "Create an Item", "owner": "Administrator", diff --git a/erpnext/setup/onboarding_step/data_import/data_import.json b/erpnext/setup/onboarding_step/data_import/data_import.json index f4628b7f08..48741dca01 100644 --- a/erpnext/setup/onboarding_step/data_import/data_import.json +++ b/erpnext/setup/onboarding_step/data_import/data_import.json @@ -9,7 +9,7 @@ "is_complete": 0, "is_single": 0, "is_skipped": 0, - "modified": "2021-11-24 18:01:53.480435", + "modified": "2021-12-15 13:10:57.346422", "modified_by": "Administrator", "name": "Data import", "owner": "Administrator", diff --git a/erpnext/setup/onboarding_step/letterhead/letterhead.json b/erpnext/setup/onboarding_step/letterhead/letterhead.json index 05ca5ca628..8e1bb8ce82 100644 --- a/erpnext/setup/onboarding_step/letterhead/letterhead.json +++ b/erpnext/setup/onboarding_step/letterhead/letterhead.json @@ -2,14 +2,14 @@ "action": "Create Entry", "action_label": "Let\u2019s setup your first Letter Head", "creation": "2021-11-22 12:36:34.583783", - "description": "# Letter Head\n\nA Letter Head contains your organization's name, logo, address, etc which appears at the header and footer portion in documents. You can learn more about Setting up Letter Head in ERPNext here.\n", + "description": "# Create a Letter Head\n\nA Letter Head contains your organization's name, logo, address, etc which appears at the header and footer portion in documents. You can learn more about Setting up Letter Head in ERPNext here.\n", "docstatus": 0, "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, "is_single": 0, "is_skipped": 0, - "modified": "2021-11-24 18:01:53.459675", + "modified": "2021-12-15 14:21:39.037742", "modified_by": "Administrator", "name": "Letterhead", "owner": "Administrator", diff --git a/erpnext/setup/onboarding_step/navigation_help/navigation_help.json b/erpnext/setup/onboarding_step/navigation_help/navigation_help.json index 1cc58cb08b..388853df79 100644 --- a/erpnext/setup/onboarding_step/navigation_help/navigation_help.json +++ b/erpnext/setup/onboarding_step/navigation_help/navigation_help.json @@ -2,14 +2,14 @@ "action": "Watch Video", "action_label": "Learn about Navigation options", "creation": "2021-11-22 12:09:52.233872", - "description": "Ease of navigating and browsing around the ERPNext is one of our core strengths. In the following video, you will learn how to reach a specific feature in ERPNext via module page or awesome bar\u2019s shortcut.\n", + "description": "# Navigation in ERPNext\n\nEase of navigating and browsing around the ERPNext is one of our core strengths. In the following video, you will learn how to reach a specific feature in ERPNext via module page or awesome bar\u2019s shortcut.\n", "docstatus": 0, "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, "is_single": 0, "is_skipped": 0, - "modified": "2021-11-24 18:01:53.490470", + "modified": "2021-12-15 14:20:55.441678", "modified_by": "Administrator", "name": "Navigation Help", "owner": "Administrator", From 29f6852c1369c2dad5574b9fff5ca5f94374038a Mon Sep 17 00:00:00 2001 From: HENRY Florian Date: Wed, 15 Dec 2021 10:38:31 +0100 Subject: [PATCH 19/28] feat: Manufacturing : in BOM Operation time can be fix (#27063) * feat: Operation time can be fixed * better coding without duplicate code * change modified date foir doctype * undo change * fix varmixing * fix varmixing * fix: remove f-string that's not required anymore * refactor: use ORM instead of raw query * fix: improve description * test: basic test for fixed time Co-authored-by: Ankush Menat --- erpnext/manufacturing/doctype/bom/bom.py | 2 +- .../doctype/bom_operation/bom_operation.json | 14 ++++++- .../doctype/work_order/test_work_order.py | 39 +++++++++++++++++++ .../doctype/work_order/work_order.py | 26 +++++++------ 4 files changed, 67 insertions(+), 14 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 2cd8f8c15a..f82d9a0d55 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -922,7 +922,7 @@ def validate_bom_no(item, bom_no): rm_item_exists = True if bom.item.lower() == item.lower() or \ bom.item.lower() == cstr(frappe.db.get_value("Item", item, "variant_of")).lower(): - rm_item_exists = True + rm_item_exists = True if not rm_item_exists: frappe.throw(_("BOM {0} does not belong to Item {1}").format(bom_no, item)) diff --git a/erpnext/manufacturing/doctype/bom_operation/bom_operation.json b/erpnext/manufacturing/doctype/bom_operation/bom_operation.json index ec617f3aaa..c7be7efc9e 100644 --- a/erpnext/manufacturing/doctype/bom_operation/bom_operation.json +++ b/erpnext/manufacturing/doctype/bom_operation/bom_operation.json @@ -11,6 +11,7 @@ "col_break1", "workstation", "time_in_mins", + "fixed_time", "costing_section", "hour_rate", "base_hour_rate", @@ -79,6 +80,14 @@ "oldfieldtype": "Currency", "reqd": 1 }, + { + "default": "0", + "description": "Operation time does not depend on quantity to produce", + "fieldname": "fixed_time", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Fixed Time" + }, { "fieldname": "operating_cost", "fieldtype": "Currency", @@ -177,12 +186,13 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-09-13 16:45:01.092868", + "modified": "2021-12-15 03:00:00.473173", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM Operation", "owner": "Administrator", "permissions": [], "sort_field": "modified", - "sort_order": "DESC" + "sort_order": "DESC", + "states": [] } \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 165e0acebb..86c687fb7c 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -846,6 +846,45 @@ class TestWorkOrder(ERPNextTestCase): close_work_order(wo_order, "Closed") self.assertEqual(wo_order.get('status'), "Closed") + def test_fix_time_operations(self): + bom = frappe.get_doc({ + "doctype": "BOM", + "item": "_Test FG Item 2", + "is_active": 1, + "is_default": 1, + "quantity": 1.0, + "with_operations": 1, + "operations": [ + { + "operation": "_Test Operation 1", + "description": "_Test", + "workstation": "_Test Workstation 1", + "time_in_mins": 60, + "operating_cost": 140, + "fixed_time": 1 + } + ], + "items": [ + { + "amount": 5000.0, + "doctype": "BOM Item", + "item_code": "_Test Item", + "parentfield": "items", + "qty": 1.0, + "rate": 5000.0, + }, + ], + }) + bom.save() + bom.submit() + + + wo1 = make_wo_order_test_record(item=bom.item, bom_no=bom.name, qty=1, skip_transfer=1, do_not_submit=1) + wo2 = make_wo_order_test_record(item=bom.item, bom_no=bom.name, qty=2, skip_transfer=1, do_not_submit=1) + + self.assertEqual(wo1.operations[0].time_in_mins, wo2.operations[0].time_in_mins) + + def update_job_card(job_card): job_card_doc = frappe.get_doc('Job Card', job_card) job_card_doc.set('scrap_items', [ diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 0090f4d04e..170454c823 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -505,16 +505,19 @@ class WorkOrder(Document): """Fetch operations from BOM and set in 'Work Order'""" def _get_operations(bom_no, qty=1): - return frappe.db.sql( - f"""select - operation, description, workstation, idx, - base_hour_rate as hour_rate, time_in_mins * {qty} as time_in_mins, - "Pending" as status, parent as bom, batch_size, sequence_id - from - `tabBOM Operation` - where - parent = %s order by idx - """, bom_no, as_dict=1) + data = frappe.get_all("BOM Operation", + filters={"parent": bom_no}, + fields=["operation", "description", "workstation", "idx", + "base_hour_rate as hour_rate", "time_in_mins", "parent as bom", + "batch_size", "sequence_id", "fixed_time"], + order_by="idx") + + for d in data: + if not d.fixed_time: + d.time_in_mins = flt(d.time_in_mins) * flt(qty) + d.status = "Pending" + + return data self.set('operations', []) @@ -542,7 +545,8 @@ class WorkOrder(Document): def calculate_time(self): for d in self.get("operations"): - d.time_in_mins = flt(d.time_in_mins) * (flt(self.qty) / flt(d.batch_size)) + if not d.fixed_time: + d.time_in_mins = flt(d.time_in_mins) * (flt(self.qty) / flt(d.batch_size)) self.calculate_operating_cost() From 228e011819959ae34287a91c144fbedb3c350d07 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 15 Dec 2021 19:18:41 +0530 Subject: [PATCH 20/28] perf: remove forcing of posting sort index on stock balance (#28902) mysql is generally smart enough to figure out which index is better based on cardinality of index. While posting sort index is better for low item high # of SLE scenario it's bad for high item variety each with lower count of SLEs. --- erpnext/stock/report/stock_balance/stock_balance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py index c0b89fdd09..3c7b26bb1b 100644 --- a/erpnext/stock/report/stock_balance/stock_balance.py +++ b/erpnext/stock/report/stock_balance/stock_balance.py @@ -167,7 +167,7 @@ def get_stock_ledger_entries(filters, items): sle.company, sle.voucher_type, sle.qty_after_transaction, sle.stock_value_difference, sle.item_code as name, sle.voucher_no, sle.stock_value, sle.batch_no from - `tabStock Ledger Entry` sle force index (posting_sort_index) + `tabStock Ledger Entry` sle where sle.docstatus < 2 %s %s and is_cancelled = 0 order by sle.posting_date, sle.posting_time, sle.creation, sle.actual_qty""" % #nosec From 2be62c279f5f63f2c2481c9f6409c5d5ce9608a6 Mon Sep 17 00:00:00 2001 From: DeeMysterio Date: Wed, 15 Dec 2021 19:41:30 +0530 Subject: [PATCH 21/28] =?UTF-8?q?feat:=20provision=20to=20have=20limited?= =?UTF-8?q?=20parameters=20for=20in-process=20quality=20ins=E2=80=A6=20(#2?= =?UTF-8?q?8549)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: provision to have limited parameters for in-process quality inspection * chore: formatting --- .../manufacturing/doctype/job_card/job_card.js | 17 +++++++++++++++++ .../doctype/job_card/job_card.json | 10 +++++++++- .../doctype/operation/operation.json | 10 +++++++++- .../quality_inspection/quality_inspection.js | 2 +- .../quality_inspection/quality_inspection.py | 9 +++++++++ 5 files changed, 45 insertions(+), 3 deletions(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.js b/erpnext/manufacturing/doctype/job_card/job_card.js index dac7b36f94..d85b8a60d2 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.js +++ b/erpnext/manufacturing/doctype/job_card/job_card.js @@ -76,6 +76,7 @@ frappe.ui.form.on('Job Card', { 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) => { @@ -86,6 +87,22 @@ frappe.ui.form.on('Job Card', { } }, + setup_quality_inspection: function(frm) { + let quality_inspection_field = frm.get_docfield("quality_inspection"); + quality_inspection_field.get_route_options_for_new_doc = function(frm) { + return { + "inspection_type": "In Process", + "reference_type": "Job Card", + "reference_name": frm.doc.name, + "item_code": frm.doc.production_item, + "item_name": frm.doc.item_name, + "item_serial_no": frm.doc.serial_no, + "batch_no": frm.doc.batch_no, + "quality_inspection_template": frm.doc.quality_inspection_template, + }; + }; + }, + setup_corrective_job_card: function(frm) { frm.add_custom_button(__('Corrective Job Card'), () => { let operations = frm.doc.sub_operations.map(d => d.sub_operation).concat(frm.doc.operation); diff --git a/erpnext/manufacturing/doctype/job_card/job_card.json b/erpnext/manufacturing/doctype/job_card/job_card.json index 6528199d82..5a071f1da6 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.json +++ b/erpnext/manufacturing/doctype/job_card/job_card.json @@ -19,6 +19,7 @@ "serial_no", "column_break_12", "wip_warehouse", + "quality_inspection_template", "quality_inspection", "project", "batch_no", @@ -408,11 +409,18 @@ "no_copy": 1, "options": "Job Card Scrap Item", "print_hide": 1 + }, + { + "fetch_from": "operation.quality_inspection_template", + "fieldname": "quality_inspection_template", + "fieldtype": "Link", + "label": "Quality Inspection Template", + "options": "Quality Inspection Template" } ], "is_submittable": 1, "links": [], - "modified": "2021-11-12 10:15:03.572401", + "modified": "2021-11-24 19:17:40.879235", "modified_by": "Administrator", "module": "Manufacturing", "name": "Job Card", diff --git a/erpnext/manufacturing/doctype/operation/operation.json b/erpnext/manufacturing/doctype/operation/operation.json index 10a97eda76..753552ce54 100644 --- a/erpnext/manufacturing/doctype/operation/operation.json +++ b/erpnext/manufacturing/doctype/operation/operation.json @@ -13,6 +13,7 @@ "is_corrective_operation", "job_card_section", "create_job_card_based_on_batch_size", + "quality_inspection_template", "column_break_6", "batch_size", "sub_operations_section", @@ -92,15 +93,22 @@ "fieldname": "is_corrective_operation", "fieldtype": "Check", "label": "Is Corrective Operation" + }, + { + "fieldname": "quality_inspection_template", + "fieldtype": "Link", + "label": "Quality Inspection Template", + "options": "Quality Inspection Template" } ], "icon": "fa fa-wrench", "index_web_pages_for_search": 1, "links": [], - "modified": "2021-01-12 15:09:23.593338", + "modified": "2021-11-24 19:15:24.357187", "modified_by": "Administrator", "module": "Manufacturing", "name": "Operation", + "naming_rule": "Set by user", "owner": "Administrator", "permissions": [ { diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.js b/erpnext/stock/doctype/quality_inspection/quality_inspection.js index d08dc3e8b7..eea28791a9 100644 --- a/erpnext/stock/doctype/quality_inspection/quality_inspection.js +++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.js @@ -59,7 +59,7 @@ frappe.ui.form.on("Quality Inspection", { }, item_code: function(frm) { - if (frm.doc.item_code) { + if (frm.doc.item_code && !frm.doc.quality_inspection_template) { return frm.call({ method: "get_quality_inspection_template", doc: frm.doc, diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.py b/erpnext/stock/doctype/quality_inspection/quality_inspection.py index 913ee1559d..4e3b80aa76 100644 --- a/erpnext/stock/doctype/quality_inspection/quality_inspection.py +++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.py @@ -18,6 +18,15 @@ class QualityInspection(Document): if not self.readings and self.item_code: self.get_item_specification_details() + if self.inspection_type=="In Process" and self.reference_type=="Job Card": + item_qi_template = frappe.db.get_value("Item", self.item_code, 'quality_inspection_template') + parameters = get_template_details(item_qi_template) + for reading in self.readings: + for d in parameters: + if reading.specification == d.specification: + reading.update(d) + reading.status = "Accepted" + if self.readings: self.inspect_and_set_status() From 564d23fa8d4fbd91976c1adbc17029eafa50c04d Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Thu, 16 Dec 2021 11:19:03 +0530 Subject: [PATCH 22/28] fix(ksa): enable ksa pos invoice print format --- erpnext/patches.txt | 2 +- erpnext/patches/v13_0/disable_ksa_print_format_for_others.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 65ad79ef23..d9cedab52a 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -316,5 +316,5 @@ erpnext.patches.v13_0.create_ksa_vat_custom_fields erpnext.patches.v14_0.rename_ongoing_status_in_sla_documents erpnext.patches.v14_0.migrate_crm_settings erpnext.patches.v13_0.rename_ksa_qr_field -erpnext.patches.v13_0.disable_ksa_print_format_for_others +erpnext.patches.v13_0.disable_ksa_print_format_for_others # 16-12-2021 erpnext.patches.v14_0.add_default_exit_questionnaire_notification_template \ No newline at end of file diff --git a/erpnext/patches/v13_0/disable_ksa_print_format_for_others.py b/erpnext/patches/v13_0/disable_ksa_print_format_for_others.py index c815b3bb3c..aa2a2d3b78 100644 --- a/erpnext/patches/v13_0/disable_ksa_print_format_for_others.py +++ b/erpnext/patches/v13_0/disable_ksa_print_format_for_others.py @@ -3,10 +3,13 @@ import frappe +from erpnext.regional.saudi_arabia.setup import add_print_formats + def execute(): company = frappe.get_all('Company', filters = {'country': 'Saudi Arabia'}) if company: + add_print_formats() return if frappe.db.exists('DocType', 'Print Format'): From 0e4842b986b5379174349b4153d64a50278fd438 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 16 Dec 2021 16:51:41 +0530 Subject: [PATCH 23/28] fix: Multicurrency invoices using subscription --- erpnext/accounts/doctype/subscription/subscription.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py index 1dae87f2a4..467d4a1334 100644 --- a/erpnext/accounts/doctype/subscription/subscription.py +++ b/erpnext/accounts/doctype/subscription/subscription.py @@ -23,6 +23,7 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( get_accounting_dimensions, ) from erpnext.accounts.doctype.subscription_plan.subscription_plan import get_plan_rate +from erpnext.accounts.party import get_party_account_currency class Subscription(Document): @@ -355,6 +356,9 @@ class Subscription(Document): if frappe.db.get_value('Supplier', self.party, 'tax_withholding_category'): invoice.apply_tds = 1 + ### Add party currency to invoice + invoice.currency = get_party_account_currency(self.party_type, self.party, self.company) + ## Add dimensions in invoice for subscription: accounting_dimensions = get_accounting_dimensions() From 6ab753015efdc6a3017942ad319f965aec4aec91 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 16 Dec 2021 17:38:29 +0530 Subject: [PATCH 24/28] fix: Add tests for multicurrency subscription --- .../doctype/subscription/test_subscription.py | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/erpnext/accounts/doctype/subscription/test_subscription.py b/erpnext/accounts/doctype/subscription/test_subscription.py index 9dd370bd47..6f67bc5128 100644 --- a/erpnext/accounts/doctype/subscription/test_subscription.py +++ b/erpnext/accounts/doctype/subscription/test_subscription.py @@ -60,15 +60,38 @@ def create_plan(): plan.billing_interval_count = 3 plan.insert() + if not frappe.db.exists('Subscription Plan', '_Test Plan Multicurrency'): + plan = frappe.new_doc('Subscription Plan') + plan.plan_name = '_Test Plan Multicurrency' + plan.item = '_Test Non Stock Item' + plan.price_determination = "Fixed Rate" + plan.cost = 50 + plan.currency = 'USD' + plan.billing_interval = 'Month' + plan.billing_interval_count = 1 + plan.insert() + +def create_parties(): if not frappe.db.exists('Supplier', '_Test Supplier'): supplier = frappe.new_doc('Supplier') supplier.supplier_name = '_Test Supplier' supplier.supplier_group = 'All Supplier Groups' supplier.insert() + if not frappe.db.exists('Customer', '_Test Subscription Customer'): + customer = frappe.new_doc('Customer') + customer.customer_name = '_Test Subscription Customer' + customer.billing_currency = 'USD' + customer.append('accounts', { + 'company': '_Test Company', + 'account': '_Test Receivable USD - _TC' + }) + customer.insert() + class TestSubscription(unittest.TestCase): def setUp(self): create_plan() + create_parties() def test_create_subscription_with_trial_with_correct_period(self): subscription = frappe.new_doc('Subscription') @@ -637,3 +660,22 @@ class TestSubscription(unittest.TestCase): subscription.process() self.assertEqual(len(subscription.invoices), 1) + + def test_multicurrency_subscription(self): + subscription = frappe.new_doc('Subscription') + subscription.party_type = 'Customer' + subscription.party = '_Test Subscription Customer' + subscription.generate_invoice_at_period_start = 1 + subscription.company = '_Test Company' + # select subscription start date as '2018-01-15' + subscription.start_date = '2018-01-01' + subscription.append('plans', {'plan': '_Test Plan Multicurrency', 'qty': 1}) + subscription.save() + + subscription.process() + self.assertEqual(len(subscription.invoices), 1) + self.assertEqual(subscription.status, 'Unpaid') + + # Check the currency of the created invoice + currency = frappe.db.get_value('Sales Invoice', subscription.invoices[0].invoice, 'currency') + self.assertEqual(currency, 'USD') \ No newline at end of file From 6ba8f7644d03fc71420f60ed92a0164f774ad26c Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 16 Dec 2021 22:31:31 +0530 Subject: [PATCH 25/28] chore: ledger invariant check report (#28921) --- .../stock_ledger_invariant_check/__init__.py | 0 .../stock_ledger_invariant_check.js | 43 ++++ .../stock_ledger_invariant_check.json | 26 ++ .../stock_ledger_invariant_check.py | 236 ++++++++++++++++++ erpnext/stock/report/test_reports.py | 6 + 5 files changed, 311 insertions(+) create mode 100644 erpnext/stock/report/stock_ledger_invariant_check/__init__.py create mode 100644 erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.js create mode 100644 erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.json create mode 100644 erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.py diff --git a/erpnext/stock/report/stock_ledger_invariant_check/__init__.py b/erpnext/stock/report/stock_ledger_invariant_check/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.js b/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.js new file mode 100644 index 0000000000..c484516a16 --- /dev/null +++ b/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.js @@ -0,0 +1,43 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +const DIFFERNCE_FIELD_NAMES = [ + "difference_in_qty", + "fifo_qty_diff", + "fifo_value_diff", + "fifo_valuation_diff", + "valuation_diff", + "fifo_difference_diff" +]; + +frappe.query_reports["Stock Ledger Invariant Check"] = { + "filters": [ + { + "fieldname": "item_code", + "fieldtype": "Link", + "label": "Item", + "mandatory": 1, + "options": "Item", + get_query: function() { + return { + filters: {is_stock_item: 1, has_serial_no: 0} + } + } + }, + { + "fieldname": "warehouse", + "fieldtype": "Link", + "label": "Warehouse", + "mandatory": 1, + "options": "Warehouse", + } + ], + formatter (value, row, column, data, default_formatter) { + value = default_formatter(value, row, column, data); + if (DIFFERNCE_FIELD_NAMES.includes(column.fieldname) && Math.abs(data[column.fieldname]) > 0.001) { + value = "" + value + ""; + } + return value; + }, +}; diff --git a/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.json b/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.json new file mode 100644 index 0000000000..d28fe0f62d --- /dev/null +++ b/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.json @@ -0,0 +1,26 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2021-12-16 06:31:23.290916", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "modified": "2021-12-16 09:55:58.341764", + "modified_by": "Administrator", + "module": "Stock", + "name": "Stock Ledger Invariant Check", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Stock Ledger Entry", + "report_name": "Stock Ledger Invariant Check", + "report_type": "Script Report", + "roles": [ + { + "role": "System Manager" + } + ] +} \ No newline at end of file diff --git a/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.py b/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.py new file mode 100644 index 0000000000..ca47a1ec5b --- /dev/null +++ b/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.py @@ -0,0 +1,236 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# License: GNU GPL v3. See LICENSE + +import json + +import frappe + +SLE_FIELDS = ( + "name", + "posting_date", + "posting_time", + "creation", + "voucher_type", + "voucher_no", + "actual_qty", + "qty_after_transaction", + "incoming_rate", + "outgoing_rate", + "stock_queue", + "batch_no", + "stock_value", + "stock_value_difference", + "valuation_rate", +) + + +def execute(filters=None): + columns = get_columns() + data = get_data(filters) + return columns, data + + +def get_data(filters): + sles = get_stock_ledger_entries(filters) + return add_invariant_check_fields(sles) + + +def get_stock_ledger_entries(filters): + return frappe.get_all( + "Stock Ledger Entry", + fields=SLE_FIELDS, + filters={ + "item_code": filters.item_code, + "warehouse": filters.warehouse, + "is_cancelled": 0 + }, + order_by="timestamp(posting_date, posting_time), creation", + ) + + +def add_invariant_check_fields(sles): + balance_qty = 0.0 + for idx, sle in enumerate(sles): + queue = json.loads(sle.stock_queue) + + fifo_qty = 0.0 + fifo_value = 0.0 + for qty, rate in queue: + fifo_qty += qty + fifo_value += qty * rate + + balance_qty += sle.actual_qty + if sle.voucher_type == "Stock Reconciliation" and not sle.batch_no: + balance_qty = sle.qty_after_transaction + + sle.fifo_queue_qty = fifo_qty + sle.fifo_stock_value = fifo_value + sle.fifo_valuation_rate = fifo_value / fifo_qty if fifo_qty else None + sle.balance_value_by_qty = ( + sle.stock_value / sle.qty_after_transaction if sle.qty_after_transaction else None + ) + sle.expected_qty_after_transaction = balance_qty + + # set difference fields + sle.difference_in_qty = sle.qty_after_transaction - sle.expected_qty_after_transaction + sle.fifo_qty_diff = sle.qty_after_transaction - fifo_qty + sle.fifo_value_diff = sle.stock_value - fifo_value + sle.fifo_valuation_diff = ( + sle.valuation_rate - sle.fifo_valuation_rate if sle.fifo_valuation_rate else None + ) + sle.valuation_diff = ( + sle.valuation_rate - sle.balance_value_by_qty if sle.balance_value_by_qty else None + ) + + if idx > 0: + sle.fifo_stock_diff = sle.fifo_stock_value - sles[idx - 1].fifo_stock_value + sle.fifo_difference_diff = sle.fifo_stock_diff - sle.stock_value_difference + + return sles + + +def get_columns(): + return [ + { + "fieldname": "name", + "fieldtype": "Link", + "label": "Stock Ledger Entry", + "options": "Stock Ledger Entry", + }, + { + "fieldname": "posting_date", + "fieldtype": "Date", + "label": "Posting Date", + }, + { + "fieldname": "posting_time", + "fieldtype": "Time", + "label": "Posting Time", + }, + { + "fieldname": "creation", + "fieldtype": "Datetime", + "label": "Creation", + }, + { + "fieldname": "voucher_type", + "fieldtype": "Link", + "label": "Voucher Type", + "options": "DocType", + }, + { + "fieldname": "voucher_no", + "fieldtype": "Dynamic Link", + "label": "Voucher No", + "options": "voucher_type", + }, + { + "fieldname": "batch_no", + "fieldtype": "Link", + "label": "Batch", + "options": "Batch", + }, + { + "fieldname": "actual_qty", + "fieldtype": "Float", + "label": "Qty Change", + }, + { + "fieldname": "incoming_rate", + "fieldtype": "Float", + "label": "Incoming Rate", + }, + { + "fieldname": "outgoing_rate", + "fieldtype": "Float", + "label": "Outgoing Rate", + }, + { + "fieldname": "qty_after_transaction", + "fieldtype": "Float", + "label": "(A) Qty After Transaction", + }, + { + "fieldname": "expected_qty_after_transaction", + "fieldtype": "Float", + "label": "(B) Expected Qty After Transaction", + }, + { + "fieldname": "difference_in_qty", + "fieldtype": "Float", + "label": "A - B", + }, + { + "fieldname": "stock_queue", + "fieldtype": "Data", + "label": "FIFO Queue", + }, + + { + "fieldname": "fifo_queue_qty", + "fieldtype": "Float", + "label": "(C) Total qty in queue", + }, + { + "fieldname": "fifo_qty_diff", + "fieldtype": "Float", + "label": "A - C", + }, + { + "fieldname": "stock_value", + "fieldtype": "Float", + "label": "(D) Balance Stock Value", + }, + { + "fieldname": "fifo_stock_value", + "fieldtype": "Float", + "label": "(E) Balance Stock Value in Queue", + }, + { + "fieldname": "fifo_value_diff", + "fieldtype": "Float", + "label": "D - E", + }, + + { + "fieldname": "stock_value_difference", + "fieldtype": "Float", + "label": "(F) Stock Value Difference", + }, + { + "fieldname": "fifo_stock_diff", + "fieldtype": "Float", + "label": "(G) Stock Value difference (FIFO queue)", + }, + { + "fieldname": "fifo_difference_diff", + "fieldtype": "Float", + "label": "F - G", + }, + { + "fieldname": "valuation_rate", + "fieldtype": "Float", + "label": "(H) Valuation Rate", + }, + { + "fieldname": "fifo_valuation_rate", + "fieldtype": "Float", + "label": "(I) Valuation Rate as per FIFO", + }, + + { + "fieldname": "fifo_valuation_diff", + "fieldtype": "Float", + "label": "H - I", + }, + { + "fieldname": "balance_value_by_qty", + "fieldtype": "Float", + "label": "(J) Valuation = Value (D) รท Qty (A)", + }, + { + "fieldname": "valuation_diff", + "fieldtype": "Float", + "label": "H - J", + }, + ] diff --git a/erpnext/stock/report/test_reports.py b/erpnext/stock/report/test_reports.py index d7fb5b2bf3..1dcf863a9d 100644 --- a/erpnext/stock/report/test_reports.py +++ b/erpnext/stock/report/test_reports.py @@ -41,6 +41,12 @@ REPORT_FILTER_TEST_CASES: List[Tuple[ReportName, ReportFilters]] = [ ("Total Stock Summary", {"group_by": "warehouse",}), ("Batch Item Expiry Status", {}), ("Stock Ageing", {"range1": 30, "range2": 60, "range3": 90, "_optional": True}), + ("Stock Ledger Invariant Check", + { + "warehouse": "_Test Warehouse - _TC", + "item": "_Test Item" + } + ), ] OPTIONAL_FILTERS = { From 4cb5b627a19edd00ecc28753ddfea0bb6ed92444 Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Thu, 16 Dec 2021 23:06:48 +0530 Subject: [PATCH 26/28] fix: mapping to maintenance visit gets erased (#28917) * fix: mappings to maintenance visit gets erased * test: mapping of schedule to visit --- .../maintenance_schedule/test_maintenance_schedule.py | 9 +++++++-- .../doctype/maintenance_visit/maintenance_visit.js | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py index 37ea3fdac3..501712613a 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py @@ -56,9 +56,14 @@ class TestMaintenanceSchedule(unittest.TestCase): ms.submit() s_id = ms.get_pending_data(data_type = "id", item_name = i.item_name, s_date = expected_dates[1]) - test = make_maintenance_visit(source_name = ms.name, item_name = "_Test Item", s_id = s_id) + + # Check if item is mapped in visit. + test_map_visit = make_maintenance_visit(source_name = ms.name, item_name = "_Test Item", s_id = s_id) + self.assertEqual(len(test_map_visit.purposes), 1) + self.assertEqual(test_map_visit.purposes[0].item_name, "_Test Item") + visit = frappe.new_doc('Maintenance Visit') - visit = test + visit = test_map_visit visit.maintenance_schedule = ms.name visit.maintenance_schedule_detail = s_id visit.completion_status = "Partially Completed" diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js index 6f6ca61ebc..d2197a6877 100644 --- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js +++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js @@ -47,7 +47,7 @@ frappe.ui.form.on('Maintenance Visit', { frm.set_value({ status: 'Draft' }); } if (frm.doc.__islocal) { - frm.clear_table("purposes"); + frm.doc.maintenance_type == 'Unscheduled' && frm.clear_table("purposes"); frm.set_value({ mntc_date: frappe.datetime.get_today() }); } }, From c1a0f649cb08484f6eb558791531bd932ca86999 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 17 Dec 2021 19:06:19 +0530 Subject: [PATCH 27/28] fix: convert asynchronous field update to synchronous (#28930) (cherry picked from commit 5adf85f5b7ba94591381b03709b1fee6aa9331a5) Co-authored-by: Dan Navarro on Ubuntu Work --- erpnext/public/js/utils.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index 93a273187a..9339c5d998 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -430,12 +430,9 @@ erpnext.utils.select_alternate_items = function(opts) { qty = row.qty; } row[item_field] = d.alternate_item; - frm.script_manager.trigger(item_field, row.doctype, row.name) - .then(() => { - frappe.model.set_value(row.doctype, row.name, 'qty', qty); - frappe.model.set_value(row.doctype, row.name, - opts.original_item_field, d.item_code); - }); + frappe.model.set_value(row.doctype, row.name, 'qty', qty); + frappe.model.set_value(row.doctype, row.name, opts.original_item_field, d.item_code); + frm.trigger(item_field, row.doctype, row.name); }); refresh_field(opts.child_docname); From e85da7a03a45787ec785f1d0b5d0efc56f145149 Mon Sep 17 00:00:00 2001 From: Subin Tom <36098155+nemesis189@users.noreply.github.com> Date: Fri, 17 Dec 2021 21:48:10 +0530 Subject: [PATCH 28/28] fix: Add Invoice Number field to list view in Opening Inv Creation Tool (#28938) --- .../opening_invoice_creation_tool_item.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool_item/opening_invoice_creation_tool_item.json b/erpnext/accounts/doctype/opening_invoice_creation_tool_item/opening_invoice_creation_tool_item.json index 040624f926..5c19091c3f 100644 --- a/erpnext/accounts/doctype/opening_invoice_creation_tool_item/opening_invoice_creation_tool_item.json +++ b/erpnext/accounts/doctype/opening_invoice_creation_tool_item/opening_invoice_creation_tool_item.json @@ -110,12 +110,13 @@ "description": "Reference number of the invoice from the previous system", "fieldname": "invoice_number", "fieldtype": "Data", + "in_list_view": 1, "label": "Invoice Number" } ], "istable": 1, "links": [], - "modified": "2021-12-13 18:15:41.295007", + "modified": "2021-12-17 19:25:06.053187", "modified_by": "Administrator", "module": "Accounts", "name": "Opening Invoice Creation Tool Item",