Merge branch 'develop' into revert-drop-einvoicing
diff --git a/.flake8 b/.flake8
index 56c9b9a..5735456 100644
--- a/.flake8
+++ b/.flake8
@@ -28,6 +28,7 @@
B007,
B950,
W191,
+ E124, # closing bracket, irritating while writing QB code
max-line-length = 200
exclude=.github/helper/semgrep_rules
diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py
index 44cea31..51e1d6e 100644
--- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py
+++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py
@@ -2,9 +2,10 @@
# For license information, please see license.txt
+from functools import reduce
+
import frappe
from frappe.utils import flt
-from six.moves import reduce
from erpnext.controllers.status_updater import StatusUpdater
diff --git a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py
index 4559fa9..8e3bd8b 100644
--- a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py
+++ b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py
@@ -5,7 +5,6 @@
import frappe
from frappe import _, scrub
from frappe.utils import cint, flt
-from six import iteritems
from erpnext.accounts.party import get_partywise_advanced_payment_amount
from erpnext.accounts.report.accounts_receivable.accounts_receivable import ReceivablePayableReport
@@ -40,7 +39,7 @@
if self.filters.show_gl_balance:
gl_balance_map = get_gl_balance(self.filters.report_date)
- for party, party_dict in iteritems(self.party_total):
+ for party, party_dict in self.party_total.items():
if party_dict.outstanding == 0:
continue
diff --git a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py
index 8fd4978..d2ac10a 100644
--- a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py
+++ b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py
@@ -2,13 +2,14 @@
# For license information, please see license.txt
+from urllib.parse import urlencode
+
import frappe
import requests
from frappe import _
from frappe.model.document import Document
from frappe.utils import get_url_to_form
from frappe.utils.file_manager import get_file_path
-from six.moves.urllib.parse import urlencode
class LinkedInSettings(Document):
diff --git a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_methods.py b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_methods.py
index 66826ba..03c1a1a 100644
--- a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_methods.py
+++ b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_methods.py
@@ -5,11 +5,11 @@
import csv
import math
import time
+from io import StringIO
import dateutil
import frappe
from frappe import _
-from six import StringIO
import erpnext.erpnext_integrations.doctype.amazon_mws_settings.amazon_mws_api as mws
diff --git a/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.py b/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.py
index e242ace..a8119ac 100644
--- a/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.py
+++ b/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.py
@@ -2,13 +2,14 @@
# For license information, please see license.txt
+from urllib.parse import urlencode
+
import frappe
import gocardless_pro
from frappe import _
from frappe.integrations.utils import create_payment_gateway, create_request_log
from frappe.model.document import Document
from frappe.utils import call_hook_method, cint, flt, get_url
-from six.moves.urllib.parse import urlencode
class GoCardlessSettings(Document):
diff --git a/erpnext/erpnext_integrations/doctype/woocommerce_settings/woocommerce_settings.py b/erpnext/erpnext_integrations/doctype/woocommerce_settings/woocommerce_settings.py
index 8da52f4..309d2cb 100644
--- a/erpnext/erpnext_integrations/doctype/woocommerce_settings/woocommerce_settings.py
+++ b/erpnext/erpnext_integrations/doctype/woocommerce_settings/woocommerce_settings.py
@@ -2,12 +2,13 @@
# For license information, please see license.txt
+from urllib.parse import urlparse
+
import frappe
from frappe import _
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
from frappe.model.document import Document
from frappe.utils.nestedset import get_root_of
-from six.moves.urllib.parse import urlparse
class WoocommerceSettings(Document):
diff --git a/erpnext/erpnext_integrations/utils.py b/erpnext/erpnext_integrations/utils.py
index d922d87..30d3948 100644
--- a/erpnext/erpnext_integrations/utils.py
+++ b/erpnext/erpnext_integrations/utils.py
@@ -1,10 +1,10 @@
import base64
import hashlib
import hmac
+from urllib.parse import urlparse
import frappe
from frappe import _
-from six.moves.urllib.parse import urlparse
from erpnext import get_default_company
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index d953697..045e5bc 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -149,6 +149,7 @@
self.set_bom_material_details()
self.set_bom_scrap_items_detail()
self.validate_materials()
+ self.validate_transfer_against()
self.set_routing_operations()
self.validate_operations()
self.calculate_cost()
@@ -690,6 +691,12 @@
if act_pbom and act_pbom[0][0]:
frappe.throw(_("Cannot deactivate or cancel BOM as it is linked with other BOMs"))
+ def validate_transfer_against(self):
+ if not self.with_operations:
+ self.transfer_material_against = "Work Order"
+ if not self.transfer_material_against and not self.is_new():
+ frappe.throw(_("Setting {} is required").format(self.meta.get_label("transfer_material_against")), title=_("Missing value"))
+
def set_routing_operations(self):
if self.routing and self.with_operations and not self.operations:
self.get_routing()
diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py
index 7d90933..53437c8 100644
--- a/erpnext/manufacturing/doctype/bom/test_bom.py
+++ b/erpnext/manufacturing/doctype/bom/test_bom.py
@@ -403,6 +403,36 @@
new_bom.delete()
+ def test_valid_transfer_defaults(self):
+ bom_with_op = frappe.db.get_value("BOM", {"item": "_Test FG Item 2", "with_operations": 1, "is_active": 1})
+ bom = frappe.copy_doc(frappe.get_doc("BOM", bom_with_op), ignore_no_copy=False)
+
+ # test defaults
+ bom.docstatus = 0
+ bom.transfer_material_against = None
+ bom.insert()
+ self.assertEqual(bom.transfer_material_against, "Work Order")
+
+ bom.reload()
+ bom.transfer_material_against = None
+ with self.assertRaises(frappe.ValidationError):
+ bom.save()
+ bom.reload()
+
+ # test saner default
+ bom.transfer_material_against = "Job Card"
+ bom.with_operations = 0
+ bom.save()
+ self.assertEqual(bom.transfer_material_against, "Work Order")
+
+ # test no value on existing doc
+ bom.transfer_material_against = None
+ bom.with_operations = 0
+ bom.save()
+ self.assertEqual(bom.transfer_material_against, "Work Order")
+ bom.delete()
+
+
def get_default_bom(item_code="_Test FG Item 2"):
return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1})
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py
index 170454c..93ca805 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order.py
@@ -65,6 +65,7 @@
self.validate_warehouse_belongs_to_company()
self.calculate_operating_cost()
self.validate_qty()
+ self.validate_transfer_against()
self.validate_operation_time()
self.status = self.get_status()
@@ -72,6 +73,7 @@
self.set_required_items(reset_only_qty = len(self.get("required_items")))
+
def validate_sales_order(self):
if self.sales_order:
self.check_sales_order_on_hold_or_close()
@@ -625,6 +627,16 @@
if not self.qty > 0:
frappe.throw(_("Quantity to Manufacture must be greater than 0."))
+ def validate_transfer_against(self):
+ if not self.docstatus == 1:
+ # let user configure operations until they're ready to submit
+ return
+ if not self.operations:
+ self.transfer_material_against = "Work Order"
+ if not self.transfer_material_against:
+ frappe.throw(_("Setting {} is required").format(self.meta.get_label("transfer_material_against")), title=_("Missing value"))
+
+
def validate_operation_time(self):
for d in self.operations:
if not d.time_in_mins > 0:
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 728c059..3a1095f 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -340,4 +340,5 @@
erpnext.patches.v14_0.delete_agriculture_doctypes
erpnext.patches.v14_0.rearrange_company_fields
erpnext.patches.v14_0.update_leave_notification_template
-erpnext.patches.v14_0.restore_einvoice_fields
\ No newline at end of file
+erpnext.patches.v14_0.restore_einvoice_fields
+erpnext.patches.v13_0.update_sane_transfer_against
diff --git a/erpnext/patches/v13_0/update_sane_transfer_against.py b/erpnext/patches/v13_0/update_sane_transfer_against.py
new file mode 100644
index 0000000..a163d38
--- /dev/null
+++ b/erpnext/patches/v13_0/update_sane_transfer_against.py
@@ -0,0 +1,11 @@
+import frappe
+
+
+def execute():
+ bom = frappe.qb.DocType("BOM")
+
+ (frappe.qb
+ .update(bom)
+ .set(bom.transfer_material_against, "Work Order")
+ .where(bom.with_operations == 0)
+ ).run()
diff --git a/erpnext/regional/germany/utils/datev/datev_csv.py b/erpnext/regional/germany/utils/datev/datev_csv.py
index 2d1e02e..ec271a1 100644
--- a/erpnext/regional/germany/utils/datev/datev_csv.py
+++ b/erpnext/regional/germany/utils/datev/datev_csv.py
@@ -1,11 +1,11 @@
import datetime
import zipfile
from csv import QUOTE_NONNUMERIC
+from io import BytesIO
import frappe
import pandas as pd
from frappe import _
-from six import BytesIO
from .datev_constants import DataCategory
diff --git a/erpnext/regional/report/datev/test_datev.py b/erpnext/regional/report/datev/test_datev.py
index 14d5495..052fb2a 100644
--- a/erpnext/regional/report/datev/test_datev.py
+++ b/erpnext/regional/report/datev/test_datev.py
@@ -1,9 +1,9 @@
import zipfile
+from io import BytesIO
from unittest import TestCase
import frappe
from frappe.utils import cstr, now_datetime, today
-from six import BytesIO
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.regional.germany.utils.datev.datev_constants import (
diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py
index c94b346..9f1eb75 100644
--- a/erpnext/setup/doctype/item_group/item_group.py
+++ b/erpnext/setup/doctype/item_group/item_group.py
@@ -3,6 +3,7 @@
import copy
+from urllib.parse import quote
import frappe
from frappe import _
@@ -10,7 +11,6 @@
from frappe.utils.nestedset import NestedSet
from frappe.website.utils import clear_cache
from frappe.website.website_generator import WebsiteGenerator
-from six.moves.urllib.parse import quote
from erpnext.shopping_cart.filters import ProductFiltersBuilder
from erpnext.shopping_cart.product_info import set_product_info_for_website
diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.js b/erpnext/stock/report/stock_ledger/stock_ledger.js
index fe2417b..ef7c2cc 100644
--- a/erpnext/stock/report/stock_ledger/stock_ledger.js
+++ b/erpnext/stock/report/stock_ledger/stock_ledger.js
@@ -86,10 +86,10 @@
],
"formatter": function (value, row, column, data, default_formatter) {
value = default_formatter(value, row, column, data);
- if (column.fieldname == "out_qty" && data.out_qty < 0) {
+ if (column.fieldname == "out_qty" && data && data.out_qty < 0) {
value = "<span style='color:red'>" + value + "</span>";
}
- else if (column.fieldname == "in_qty" && data.in_qty > 0) {
+ else if (column.fieldname == "in_qty" && data && data.in_qty > 0) {
value = "<span style='color:green'>" + value + "</span>";
}