Merge branch 'develop' into serial-no-space
diff --git a/erpnext/__init__.py b/erpnext/__init__.py
index a181c2d..c90e01c 100644
--- a/erpnext/__init__.py
+++ b/erpnext/__init__.py
@@ -5,7 +5,7 @@
from erpnext.hooks import regional_overrides
from frappe.utils import getdate
-__version__ = '13.7.1'
+__version__ = '13.8.0'
def get_default_company(user=None):
'''Get default company for user'''
diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py
index 1be2fbf..f763df0 100644
--- a/erpnext/accounts/doctype/account/account.py
+++ b/erpnext/accounts/doctype/account/account.py
@@ -230,7 +230,7 @@
if self.check_gle_exists():
throw(_("Account with existing transaction can not be converted to group."))
elif self.account_type and not self.flags.exclude_account_type_check:
- throw(_("Cannot covert to Group because Account Type is selected."))
+ throw(_("Cannot convert to Group because Account Type is selected."))
else:
self.is_group = 1
self.save()
diff --git a/erpnext/accounts/doctype/budget/test_budget.py b/erpnext/accounts/doctype/budget/test_budget.py
index 603e21e..6c25f00 100644
--- a/erpnext/accounts/doctype/budget/test_budget.py
+++ b/erpnext/accounts/doctype/budget/test_budget.py
@@ -249,7 +249,7 @@
def set_total_expense_zero(posting_date, budget_against_field=None, budget_against_CC=None):
if budget_against_field == "project":
- budget_against = "_Test Project"
+ budget_against = frappe.db.get_value("Project", {"project_name": "_Test Project"})
else:
budget_against = budget_against_CC or "_Test Cost Center - _TC"
@@ -275,7 +275,7 @@
"_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True)
elif budget_against_field == "project":
make_journal_entry("_Test Account Cost for Goods Sold - _TC",
- "_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", submit=True, project="_Test Project", posting_date=nowdate())
+ "_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", submit=True, project=budget_against, posting_date=nowdate())
def make_budget(**args):
args = frappe._dict(args)
diff --git a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py
index 4fd8413..8456b49 100644
--- a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py
+++ b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py
@@ -391,5 +391,5 @@
})
company.save()
- install_country_fixtures(company.name)
+ install_country_fixtures(company.name, company.country)
company.create_default_tax_template()
diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
index 6635128..d788d91 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
@@ -306,5 +306,5 @@
}
]
})
-
+ jv.flags.ignore_mandatory = True
jv.submit()
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json
index 7459c11..33c3e04 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json
@@ -1545,6 +1545,7 @@
"fieldname": "consolidated_invoice",
"fieldtype": "Link",
"label": "Consolidated Sales Invoice",
+ "no_copy": 1,
"options": "Sales Invoice",
"read_only": 1
}
@@ -1552,7 +1553,7 @@
"icon": "fa fa-file-text",
"is_submittable": 1,
"links": [],
- "modified": "2021-02-01 15:03:33.800707",
+ "modified": "2021-07-29 13:37:20.636171",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Invoice",
diff --git a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py
index ffe8be1..3173db1 100644
--- a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py
+++ b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py
@@ -15,6 +15,7 @@
class TestPricingRule(unittest.TestCase):
def setUp(self):
delete_existing_pricing_rules()
+ setup_pricing_rule_data()
def tearDown(self):
delete_existing_pricing_rules()
@@ -554,6 +555,8 @@
for doc in [si, si1]:
doc.delete()
+test_dependencies = ["Campaign"]
+
def make_pricing_rule(**args):
args = frappe._dict(args)
@@ -600,6 +603,13 @@
if args.get(applicable_for):
doc.db_set(applicable_for, args.get(applicable_for))
+def setup_pricing_rule_data():
+ if not frappe.db.exists('Campaign', '_Test Campaign'):
+ frappe.get_doc({
+ 'doctype': 'Campaign',
+ 'campaign_name': '_Test Campaign',
+ 'name': '_Test Campaign'
+ }).insert()
def delete_existing_pricing_rules():
for doctype in ["Pricing Rule", "Pricing Rule Item Code",
diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py
index b54d0e7..94abf3b 100644
--- a/erpnext/accounts/doctype/pricing_rule/utils.py
+++ b/erpnext/accounts/doctype/pricing_rule/utils.py
@@ -168,7 +168,7 @@
frappe.throw(_("Invalid {0}").format(args.get(field)))
parent_groups = frappe.db.sql_list("""select name from `tab%s`
- where lft>=%s and rgt<=%s""" % (parenttype, '%s', '%s'), (lft, rgt))
+ where lft<=%s and rgt>=%s""" % (parenttype, '%s', '%s'), (lft, rgt))
if parenttype in ["Customer Group", "Item Group", "Territory"]:
parent_field = "parent_{0}".format(frappe.scrub(parenttype))
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
index 7562418..bda9f41 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
@@ -134,7 +134,7 @@
},
get_query_filters: {
docstatus: 1,
- status: ["not in", ["Closed", "Completed"]],
+ status: ["not in", ["Closed", "Completed", "Return Issued"]],
company: me.frm.doc.company,
is_return: 0
}
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index b99d75e..863c104 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -27,6 +27,8 @@
from erpnext.accounts.deferred_revenue import validate_service_stop_date
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import get_item_account_wise_additional_cost
+class WarehouseMissingError(frappe.ValidationError): pass
+
form_grid_templates = {
"items": "templates/form_grid/item_grid.html"
}
@@ -207,8 +209,8 @@
if self.update_stock and for_validate:
for d in self.get('items'):
if not d.warehouse:
- frappe.throw(_("Warehouse required at Row No {0}, please set default warehouse for the item {1} for the company {2}").
- format(d.idx, d.item_code, self.company))
+ frappe.throw(_("Row No {0}: Warehouse is required. Please set a Default Warehouse for Item {1} and Company {2}").
+ format(d.idx, d.item_code, self.company), exc=WarehouseMissingError)
super(PurchaseInvoice, self).validate_warehouse()
@@ -246,7 +248,7 @@
and (not item.po_detail or
not frappe.db.get_value("Purchase Order Item", item.po_detail, "delivered_by_supplier")):
- if self.update_stock and (not item.from_warehouse):
+ if self.update_stock and item.warehouse and (not item.from_warehouse):
if for_validate and item.expense_account and item.expense_account != warehouse_account[item.warehouse]["account"]:
msg = _("Row {0}: Expense Head changed to {1} because account {2} is not linked to warehouse {3} or it is not the default inventory account").format(
item.idx, frappe.bold(warehouse_account[item.warehouse]["account"]), frappe.bold(item.expense_account), frappe.bold(item.warehouse))
@@ -657,7 +659,7 @@
)
gl_entries.append(
self.get_gl_dict({
- "account": self.get_company_default("exchange_gain_loss_account"),
+ "account": self.get_company_default("exchange_gain_loss_account"),
"against": self.supplier,
"credit": discrepancy_caused_by_exchange_rate_difference,
"cost_center": item.cost_center,
@@ -1193,7 +1195,7 @@
purchase_receipts_or_invoices.append(item.get(doc_reference))
if item.get(items_reference):
items.append(item.get(items_reference))
-
+
exchange_rate_map = frappe._dict(frappe.get_all(parent_doctype, filters={'name': ('in',
purchase_receipts_or_invoices)}, fields=['name', 'conversion_rate'], as_list=1))
diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
index db6f143..e90b35f 100644
--- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
@@ -240,7 +240,7 @@
pi.conversion_rate = 80
pi.insert()
- pi.submit()
+ pi.submit()
# Get exchnage gain and loss account
exchange_gain_loss_account = frappe.db.get_value('Company', pi.company, 'exchange_gain_loss_account')
@@ -978,7 +978,7 @@
unlink_enabled = frappe.db.get_value(
"Accounts Settings", "Accounts Settings",
"unlink_payment_on_cancel_of_invoice")
-
+
frappe.db.set_value(
"Accounts Settings", "Accounts Settings",
"unlink_payment_on_cancel_of_invoice", 1)
@@ -1018,8 +1018,8 @@
expected_gle = [
["_Test Account Cost for Goods Sold - _TC", 37500.0],
- ["_Test Payable USD - _TC", -40000.0],
- ["Exchange Gain/Loss - _TC", 2500.0]
+ ["_Test Payable USD - _TC", -35000.0],
+ ["Exchange Gain/Loss - _TC", -2500.0]
]
gl_entries = frappe.db.sql("""
@@ -1027,7 +1027,7 @@
where voucher_no=%s
group by account
order by account asc""", (pi.name), as_dict=1)
-
+
for i, gle in enumerate(gl_entries):
self.assertEqual(expected_gle[i][0], gle.account)
self.assertEqual(expected_gle[i][1], gle.balance)
@@ -1049,8 +1049,8 @@
expected_gle = [
["_Test Account Cost for Goods Sold - _TC", 36500.0],
- ["_Test Payable USD - _TC", -38000.0],
- ["Exchange Gain/Loss - _TC", 1500.0]
+ ["_Test Payable USD - _TC", -35000.0],
+ ["Exchange Gain/Loss - _TC", -1500.0]
]
gl_entries = frappe.db.sql("""
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index c6e6e3d..be20b18 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -2,13 +2,14 @@
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
-import frappe
+import frappe, erpnext
import unittest, copy, time
from frappe.utils import nowdate, flt, getdate, cint, add_days, add_months
from frappe.model.dynamic_links import get_dynamic_link_map
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry, get_qty_after_transaction
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import unlink_payment_on_cancel_of_invoice
+from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import WarehouseMissingError
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
from erpnext.assets.doctype.asset.test_asset import create_asset, create_asset_data
from erpnext.exceptions import InvalidAccountCurrency, InvalidCurrency
@@ -1073,7 +1074,7 @@
def test_gle_made_when_asset_is_returned(self):
create_asset_data()
asset = create_asset(item_code="Macbook Pro")
-
+
si = create_sales_invoice(item_code="Macbook Pro", asset=asset.name, qty=1, rate=90000)
return_si = create_sales_invoice(is_return=1, return_against=si.name, item_code="Macbook Pro", asset=asset.name, qty=-1, rate=90000)
@@ -1081,7 +1082,7 @@
# Asset value is 100,000 but it was sold for 90,000, so there should be a loss of 10,000
loss_for_si = frappe.get_all(
- "GL Entry",
+ "GL Entry",
filters = {
"voucher_no": si.name,
"account": disposal_account
@@ -1090,7 +1091,7 @@
)[0]
loss_for_return_si = frappe.get_all(
- "GL Entry",
+ "GL Entry",
filters = {
"voucher_no": return_si.name,
"account": disposal_account
@@ -1836,6 +1837,89 @@
self.assertEqual(target_doc.company, "_Test Company 1")
self.assertEqual(target_doc.supplier, "_Test Internal Supplier")
+ def test_inter_company_transaction_without_default_warehouse(self):
+ "Check mapping (expense account) of inter company SI to PI in absence of default warehouse."
+ # setup
+ old_negative_stock = frappe.db.get_single_value("Stock Settings", "allow_negative_stock")
+ frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1)
+
+ old_perpetual_inventory = erpnext.is_perpetual_inventory_enabled('_Test Company 1')
+ frappe.local.enable_perpetual_inventory['_Test Company 1'] = 1
+
+ frappe.db.set_value("Company", '_Test Company 1', "stock_received_but_not_billed", "Stock Received But Not Billed - _TC1")
+ frappe.db.set_value("Company", '_Test Company 1', "expenses_included_in_valuation", "Expenses Included In Valuation - _TC1")
+
+
+ if not frappe.db.exists("Customer", "_Test Internal Customer"):
+ customer = frappe.get_doc({
+ "customer_group": "_Test Customer Group",
+ "customer_name": "_Test Internal Customer",
+ "customer_type": "Individual",
+ "doctype": "Customer",
+ "territory": "_Test Territory",
+ "is_internal_customer": 1,
+ "represents_company": "_Test Company 1"
+ })
+
+ customer.append("companies", {
+ "company": "Wind Power LLC"
+ })
+
+ customer.insert()
+
+ if not frappe.db.exists("Supplier", "_Test Internal Supplier"):
+ supplier = frappe.get_doc({
+ "supplier_group": "_Test Supplier Group",
+ "supplier_name": "_Test Internal Supplier",
+ "doctype": "Supplier",
+ "is_internal_supplier": 1,
+ "represents_company": "Wind Power LLC"
+ })
+
+ supplier.append("companies", {
+ "company": "_Test Company 1"
+ })
+
+ supplier.insert()
+
+ # begin test
+ si = create_sales_invoice(
+ company = "Wind Power LLC",
+ customer = "_Test Internal Customer",
+ debit_to = "Debtors - WP",
+ warehouse = "Stores - WP",
+ income_account = "Sales - WP",
+ expense_account = "Cost of Goods Sold - WP",
+ cost_center = "Main - WP",
+ currency = "USD",
+ update_stock = 1,
+ do_not_save = 1
+ )
+ si.selling_price_list = "_Test Price List Rest of the World"
+ si.submit()
+
+ target_doc = make_inter_company_transaction("Sales Invoice", si.name)
+
+ # in absence of warehouse Stock Received But Not Billed is set as expense account while mapping
+ # mapping is not obstructed
+ self.assertIsNone(target_doc.items[0].warehouse)
+ self.assertEqual(target_doc.items[0].expense_account, "Stock Received But Not Billed - _TC1")
+
+ target_doc.items[0].update({"cost_center": "Main - _TC1"})
+
+ # missing warehouse is validated on save, after mapping
+ self.assertRaises(WarehouseMissingError, target_doc.save)
+
+ target_doc.items[0].update({"warehouse": "Stores - _TC1"})
+ target_doc.save()
+
+ # after warehouse is set, linked account or default inventory account is set
+ self.assertEqual(target_doc.items[0].expense_account, 'Stock In Hand - _TC1')
+
+ # tear down
+ frappe.local.enable_perpetual_inventory['_Test Company 1'] = old_perpetual_inventory
+ frappe.db.set_value("Stock Settings", None, "allow_negative_stock", old_negative_stock)
+
def test_internal_transfer_gl_entry(self):
## Create internal transfer account
account = create_account(account_name="Unrealized Profit",
diff --git a/erpnext/erpnext_integrations/doctype/shopify_tax_account/__init__.py b/erpnext/accounts/doctype/south_africa_vat_account/__init__.py
similarity index 100%
rename from erpnext/erpnext_integrations/doctype/shopify_tax_account/__init__.py
rename to erpnext/accounts/doctype/south_africa_vat_account/__init__.py
diff --git a/erpnext/accounts/doctype/south_africa_vat_account/south_africa_vat_account.json b/erpnext/accounts/doctype/south_africa_vat_account/south_africa_vat_account.json
new file mode 100644
index 0000000..fa1aa7d
--- /dev/null
+++ b/erpnext/accounts/doctype/south_africa_vat_account/south_africa_vat_account.json
@@ -0,0 +1,34 @@
+{
+ "actions": [],
+ "autoname": "account",
+ "creation": "2021-07-08 22:04:24.634967",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "account"
+ ],
+ "fields": [
+ {
+ "allow_in_quick_entry": 1,
+ "fieldname": "account",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "in_preview": 1,
+ "label": "Account",
+ "options": "Account"
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2021-07-08 22:35:33.202911",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "South Africa VAT Account",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/south_africa_vat_account/south_africa_vat_account.py b/erpnext/accounts/doctype/south_africa_vat_account/south_africa_vat_account.py
new file mode 100644
index 0000000..4bd8c65
--- /dev/null
+++ b/erpnext/accounts/doctype/south_africa_vat_account/south_africa_vat_account.py
@@ -0,0 +1,8 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+# import frappe
+from frappe.model.document import Document
+
+class SouthAfricaVATAccount(Document):
+ pass
diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.json b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.json
index f9160e2..153906f 100644
--- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.json
+++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.json
@@ -1,263 +1,151 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 1,
- "allow_rename": 1,
- "autoname": "Prompt",
- "beta": 0,
- "creation": "2018-04-13 18:42:06.431683",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "actions": [],
+ "allow_import": 1,
+ "allow_rename": 1,
+ "autoname": "Prompt",
+ "creation": "2018-04-13 18:42:06.431683",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "category_details_section",
+ "category_name",
+ "round_off_tax_amount",
+ "column_break_2",
+ "consider_party_ledger_amount",
+ "tax_on_excess_amount",
+ "section_break_8",
+ "rates",
+ "section_break_7",
+ "accounts"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "category_name",
"fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
"in_list_view": 1,
- "in_standard_filter": 0,
"label": "Category Name",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "show_days": 1,
+ "show_seconds": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "section_break_8",
"fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
"label": "Tax Withholding Rates",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "show_days": 1,
+ "show_seconds": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "rates",
"fieldtype": "Table",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
"label": "Rates",
- "length": 0,
- "no_copy": 0,
"options": "Tax Withholding Rate",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
"reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "show_days": 1,
+ "show_seconds": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "section_break_7",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
+ "fieldname": "section_break_7",
+ "fieldtype": "Section Break",
"label": "Account Details",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "show_days": 1,
+ "show_seconds": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "accounts",
- "fieldtype": "Table",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Accounts",
- "length": 0,
- "no_copy": 0,
- "options": "Tax Withholding Account",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "fieldname": "accounts",
+ "fieldtype": "Table",
+ "label": "Accounts",
+ "options": "Tax Withholding Account",
+ "reqd": 1,
+ "show_days": 1,
+ "show_seconds": 1
+ },
+ {
+ "fieldname": "category_details_section",
+ "fieldtype": "Section Break",
+ "label": "Category Details",
+ "show_days": 1,
+ "show_seconds": 1
+ },
+ {
+ "fieldname": "column_break_2",
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
+ },
+ {
+ "default": "0",
+ "description": "Even invoices with apply tax withholding unchecked will be considered for checking cumulative threshold breach",
+ "fieldname": "consider_party_ledger_amount",
+ "fieldtype": "Check",
+ "label": "Consider Entire Party Ledger Amount",
+ "show_days": 1,
+ "show_seconds": 1
+ },
+ {
+ "default": "0",
+ "description": "Tax will be withheld only for amount exceeding the cumulative threshold",
+ "fieldname": "tax_on_excess_amount",
+ "fieldtype": "Check",
+ "label": "Only Deduct Tax On Excess Amount ",
+ "show_days": 1,
+ "show_seconds": 1
+ },
+ {
+ "description": "Checking this will round off the tax amount to the nearest integer",
+ "fieldname": "round_off_tax_amount",
+ "fieldtype": "Check",
+ "label": "Round Off Tax Amount",
+ "show_days": 1,
+ "show_seconds": 1
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2018-07-17 22:53:26.193179",
- "modified_by": "Administrator",
- "module": "Accounts",
- "name": "Tax Withholding Category",
- "name_case": "",
- "owner": "Administrator",
+ ],
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2021-07-27 21:47:34.396071",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Tax Withholding Category",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "System Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
"write": 1
- },
+ },
{
- "amend": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Accounts Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Accounts Manager",
+ "share": 1,
"write": 1
- },
+ },
{
- "amend": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Accounts User",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Accounts User",
+ "share": 1,
"write": 1
}
- ],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0,
- "track_views": 0
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
index b9ee4a0..481ef28 100644
--- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
+++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
@@ -6,7 +6,7 @@
import frappe
from frappe import _
from frappe.model.document import Document
-from frappe.utils import flt, getdate
+from frappe.utils import flt, getdate, cint
from erpnext.accounts.utils import get_fiscal_year
class TaxWithholdingCategory(Document):
@@ -86,7 +86,10 @@
"rate": tax_rate_detail.tax_withholding_rate,
"threshold": tax_rate_detail.single_threshold,
"cumulative_threshold": tax_rate_detail.cumulative_threshold,
- "description": tax_withholding.category_name if tax_withholding.category_name else tax_withholding_category
+ "description": tax_withholding.category_name if tax_withholding.category_name else tax_withholding_category,
+ "consider_party_ledger_amount": tax_withholding.consider_party_ledger_amount,
+ "tax_on_excess_amount": tax_withholding.tax_on_excess_amount,
+ "round_off_tax_amount": tax_withholding.round_off_tax_amount
})
def get_tax_withholding_rates(tax_withholding, fiscal_year):
@@ -145,6 +148,7 @@
def get_tax_amount(party_type, parties, inv, tax_details, fiscal_year_details, pan_no=None):
fiscal_year = fiscal_year_details[0]
+
vouchers = get_invoice_vouchers(parties, fiscal_year, inv.company, party_type=party_type)
advance_vouchers = get_advance_vouchers(parties, fiscal_year, inv.company, party_type=party_type)
taxable_vouchers = vouchers + advance_vouchers
@@ -235,10 +239,18 @@
def get_tds_amount(ldc, parties, inv, tax_details, fiscal_year_details, tax_deducted, vouchers):
tds_amount = 0
+ invoice_filters = {
+ 'name': ('in', vouchers),
+ 'docstatus': 1
+ }
- supp_credit_amt = frappe.db.get_value('Purchase Invoice', {
- 'name': ('in', vouchers), 'docstatus': 1, 'apply_tds': 1
- }, 'sum(net_total)') or 0.0
+ field = 'sum(net_total)'
+
+ if not cint(tax_details.consider_party_ledger_amount):
+ invoice_filters.update({'apply_tds': 1})
+ field = 'sum(grand_total)'
+
+ supp_credit_amt = frappe.db.get_value('Purchase Invoice', invoice_filters, field) or 0.0
supp_jv_credit_amt = frappe.db.get_value('Journal Entry Account', {
'parent': ('in', vouchers), 'docstatus': 1,
@@ -255,6 +267,13 @@
cumulative_threshold = tax_details.get('cumulative_threshold', 0)
if ((threshold and inv.net_total >= threshold) or (cumulative_threshold and supp_credit_amt >= cumulative_threshold)):
+ if (cumulative_threshold and supp_credit_amt >= cumulative_threshold) and cint(tax_details.tax_on_excess_amount):
+ # Get net total again as TDS is calculated on net total
+ # Grand is used to just check for threshold breach
+ net_total = frappe.db.get_value('Purchase Invoice', invoice_filters, 'sum(net_total)') or 0.0
+ net_total += inv.net_total
+ supp_credit_amt = net_total - cumulative_threshold
+
if ldc and is_valid_certificate(
ldc.valid_from, ldc.valid_upto,
inv.get('posting_date') or inv.get('transaction_date'), tax_deducted,
@@ -263,6 +282,9 @@
tds_amount = get_ltds_amount(supp_credit_amt, 0, ldc.certificate_limit, ldc.rate, tax_details)
else:
tds_amount = supp_credit_amt * tax_details.rate / 100 if supp_credit_amt > 0 else 0
+
+ if cint(tax_details.round_off_tax_amount):
+ tds_amount = round(tds_amount)
return tds_amount
diff --git a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py
index dd26be7..2ba22ca 100644
--- a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py
+++ b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py
@@ -87,6 +87,31 @@
for d in invoices:
d.cancel()
+ def test_tax_withholding_category_checks(self):
+ invoices = []
+ frappe.db.set_value("Supplier", "Test TDS Supplier3", "tax_withholding_category", "New TDS Category")
+
+ # First Invoice with no tds check
+ pi = create_purchase_invoice(supplier = "Test TDS Supplier3", rate = 20000, do_not_save=True)
+ pi.apply_tds = 0
+ pi.save()
+ pi.submit()
+ invoices.append(pi)
+
+ # Second Invoice will apply TDS checked
+ pi1 = create_purchase_invoice(supplier = "Test TDS Supplier3", rate = 20000)
+ pi1.submit()
+ invoices.append(pi1)
+
+ # Cumulative threshold is 30000
+ # Threshold calculation should be on both the invoices
+ # TDS should be applied only on 1000
+ self.assertEqual(pi1.taxes[0].tax_amount, 1000)
+
+ for d in invoices:
+ d.cancel()
+
+
def test_cumulative_threshold_tcs(self):
frappe.db.set_value("Customer", "Test TCS Customer", "tax_withholding_category", "Cumulative Threshold TCS")
invoices = []
@@ -195,7 +220,7 @@
def create_records():
# create a new suppliers
- for name in ['Test TDS Supplier', 'Test TDS Supplier1', 'Test TDS Supplier2']:
+ for name in ['Test TDS Supplier', 'Test TDS Supplier1', 'Test TDS Supplier2', 'Test TDS Supplier3']:
if frappe.db.exists('Supplier', name):
continue
@@ -311,3 +336,23 @@
'account': 'TDS - _TC'
}]
}).insert()
+
+ if not frappe.db.exists("Tax Withholding Category", "New TDS Category"):
+ frappe.get_doc({
+ "doctype": "Tax Withholding Category",
+ "name": "New TDS Category",
+ "category_name": "New TDS Category",
+ "round_off_tax_amount": 1,
+ "consider_party_ledger_amount": 1,
+ "tax_on_excess_amount": 1,
+ "rates": [{
+ 'fiscal_year': fiscal_year,
+ 'tax_withholding_rate': 10,
+ 'single_threshold': 0,
+ 'cumulative_threshold': 30000
+ }],
+ "accounts": [{
+ 'company': '_Test Company',
+ 'account': 'TDS - _TC'
+ }]
+ }).insert()
diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py
index 84c7454..6d8623c 100644
--- a/erpnext/accounts/report/gross_profit/gross_profit.py
+++ b/erpnext/accounts/report/gross_profit/gross_profit.py
@@ -241,6 +241,7 @@
sle.voucher_detail_no == row.item_row:
previous_stock_value = len(my_sle) > i+1 and \
flt(my_sle[i+1].stock_value) or 0.0
+
if previous_stock_value:
return (previous_stock_value - flt(sle.stock_value)) * flt(row.qty) / abs(flt(sle.qty))
else:
@@ -335,7 +336,7 @@
res = frappe.db.sql("""select item_code, voucher_type, voucher_no,
voucher_detail_no, stock_value, warehouse, actual_qty as qty
from `tabStock Ledger Entry`
- where company=%(company)s
+ where company=%(company)s and is_cancelled = 0
order by
item_code desc, warehouse desc, posting_date desc,
posting_time desc, creation desc""", self.filters, as_dict=True)
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index 9afe365..9272bc4 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -566,10 +566,10 @@
frappe.msgprint(_("Payment Entries {0} are un-linked").format("\n".join(linked_pe)))
@frappe.whitelist()
-def get_company_default(company, fieldname):
- value = frappe.get_cached_value('Company', company, fieldname)
+def get_company_default(company, fieldname, ignore_validation=False):
+ value = frappe.get_cached_value('Company', company, fieldname)
- if not value:
+ if not ignore_validation and not value:
throw(_("Please set default {0} in Company {1}")
.format(frappe.get_meta("Company").get_label(fieldname), company))
diff --git a/erpnext/change_log/v13/v13_8_0.md b/erpnext/change_log/v13/v13_8_0.md
new file mode 100644
index 0000000..98ed95a
--- /dev/null
+++ b/erpnext/change_log/v13/v13_8_0.md
@@ -0,0 +1,39 @@
+# Version 13.8.0 Release Notes
+
+### Features & Enhancements
+- Report to show COGS by item groups ([#26222](https://github.com/frappe/erpnext/pull/26222))
+- Enhancements in TDS ([#26677](https://github.com/frappe/erpnext/pull/26677))
+- API Endpoint to update halted Razorpay subscriptions ([#26564](https://github.com/frappe/erpnext/pull/26564))
+
+### Fixes
+- Incorrect bom name ([#26600](https://github.com/frappe/erpnext/pull/26600))
+- Exchange rate revaluation posting date and precision fixes ([#26651](https://github.com/frappe/erpnext/pull/26651))
+- POS item cart dom updates ([#26460](https://github.com/frappe/erpnext/pull/26460))
+- General Ledger report not working with filter group by ([#26439](https://github.com/frappe/erpnext/pull/26438))
+- Tax calculation for Recurring additional salary ([#24206](https://github.com/frappe/erpnext/pull/24206))
+- Validation check for batch for stock reconciliation type in stock entry ([#26487](https://github.com/frappe/erpnext/pull/26487))
+- Improved UX for additional discount field ([#26502](https://github.com/frappe/erpnext/pull/26502))
+- Add missing cess amount in GSTR-3B report ([#26644](https://github.com/frappe/erpnext/pull/26644))
+- Optimized code for reposting item valuation ([#26431](https://github.com/frappe/erpnext/pull/26431))
+- FG item not fetched in manufacture entry ([#26508](https://github.com/frappe/erpnext/pull/26508))
+- Errors on parallel requests creation of company for India ([#26420](https://github.com/frappe/erpnext/pull/26420))
+- Incorrect valuation rate calculation in gross profit report ([#26558](https://github.com/frappe/erpnext/pull/26558))
+- Empty "against account" in Purchase Receipt GLE ([#26712](https://github.com/frappe/erpnext/pull/26712))
+- Remove cancelled entries from Stock and Account Value comparison report ([#26721](https://github.com/frappe/erpnext/pull/26721))
+- Remove manual permission checking ([#26691](https://github.com/frappe/erpnext/pull/26691))
+- Delete child docs when parent doc is deleted ([#26518](https://github.com/frappe/erpnext/pull/26518))
+- GST Reports timeout issue ([#26646](https://github.com/frappe/erpnext/pull/26646))
+- Parent condition in pricing rules ([#26727](https://github.com/frappe/erpnext/pull/26727))
+- Added Company filters for Loan ([#26294](https://github.com/frappe/erpnext/pull/26294))
+- Incorrect discount amount on amended document ([#26292](https://github.com/frappe/erpnext/pull/26292))
+- Exchange gain loss not set for advances linked with invoices ([#26436](https://github.com/frappe/erpnext/pull/26436))
+- Unallocated amount in Payment Entry after taxes ([#26412](https://github.com/frappe/erpnext/pull/26412))
+- Wrong operation time in Work Order ([#26613](https://github.com/frappe/erpnext/pull/26613))
+- Serial No and Batch validation ([#26614](https://github.com/frappe/erpnext/pull/26614))
+- Gl Entries for exchange gain loss ([#26734](https://github.com/frappe/erpnext/pull/26734))
+- TDS computation summary shows cancelled invoices ([#26485](https://github.com/frappe/erpnext/pull/26485))
+- Price List rate not fetched for return sales invoice fixed ([#26560](https://github.com/frappe/erpnext/pull/26560))
+- Included company in link document type filters for contact ([#26576](https://github.com/frappe/erpnext/pull/26576))
+- Ignore mandatory fields while creating payment reconciliation Journal Entry ([#26643](https://github.com/frappe/erpnext/pull/26643))
+- Unable to download GSTR-1 json ([#26418](https://github.com/frappe/erpnext/pull/26418))
+- Paging buttons not working on item group portal page ([#26498](https://github.com/frappe/erpnext/pull/26498))
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index cdd865a..bf4ab1a 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -674,19 +674,24 @@
if self.get('doctype') in ['Purchase Invoice', 'Sales Invoice']:
for d in self.get("advances"):
if d.exchange_gain_loss:
- party = self.supplier if self.get('doctype') == 'Purchase Invoice' else self.customer
- party_account = self.credit_to if self.get('doctype') == 'Purchase Invoice' else self.debit_to
- party_type = "Supplier" if self.get('doctype') == 'Purchase Invoice' else "Customer"
+ is_purchase_invoice = self.get('doctype') == 'Purchase Invoice'
+ party = self.supplier if is_purchase_invoice else self.customer
+ party_account = self.credit_to if is_purchase_invoice else self.debit_to
+ party_type = "Supplier" if is_purchase_invoice else "Customer"
gain_loss_account = frappe.db.get_value('Company', self.company, 'exchange_gain_loss_account')
+ if not gain_loss_account:
+ frappe.throw(_("Please set Default Exchange Gain/Loss Account in Company {}")
+ .format(self.get('company')))
account_currency = get_account_currency(gain_loss_account)
if account_currency != self.company_currency:
- frappe.throw(_("Currency for {0} must be {1}").format(d.account, self.company_currency))
+ frappe.throw(_("Currency for {0} must be {1}").format(gain_loss_account, self.company_currency))
# for purchase
dr_or_cr = 'debit' if d.exchange_gain_loss > 0 else 'credit'
- # just reverse for sales?
- dr_or_cr = 'debit' if dr_or_cr == 'credit' else 'credit'
+ if not is_purchase_invoice:
+ # just reverse for sales?
+ dr_or_cr = 'debit' if dr_or_cr == 'credit' else 'credit'
gl_entries.append(
self.get_gl_dict({
@@ -904,9 +909,9 @@
frappe.throw(_("Cannot overbill for Item {0} in row {1} more than {2}. To allow over-billing, please set allowance in Accounts Settings")
.format(item.item_code, item.idx, max_allowed_amt))
- def get_company_default(self, fieldname):
+ def get_company_default(self, fieldname, ignore_validation=False):
from erpnext.accounts.utils import get_company_default
- return get_company_default(self.company, fieldname)
+ return get_company_default(self.company, fieldname, ignore_validation=ignore_validation)
def get_stock_items(self):
stock_items = []
diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py
index 2803193..21c052a 100644
--- a/erpnext/controllers/queries.py
+++ b/erpnext/controllers/queries.py
@@ -407,6 +407,7 @@
INNER JOIN `tabBatch` batch on sle.batch_no = batch.name
where
batch.disabled = 0
+ and sle.is_cancelled = 0
and sle.item_code = %(item_code)s
and sle.warehouse = %(warehouse)s
and (sle.batch_no like %(txt)s
diff --git a/erpnext/crm/doctype/opportunity/opportunity.js b/erpnext/crm/doctype/opportunity/opportunity.js
index 43e1b99..e9a7a95 100644
--- a/erpnext/crm/doctype/opportunity/opportunity.js
+++ b/erpnext/crm/doctype/opportunity/opportunity.js
@@ -53,6 +53,13 @@
frm.get_field("items").grid.set_multiple_add("item_code", "qty");
},
+ status:function(frm){
+ if (frm.doc.status == "Lost"){
+ frm.trigger('set_as_lost_dialog');
+ }
+
+ },
+
customer_address: function(frm, cdt, cdn) {
erpnext.utils.get_address_display(frm, 'customer_address', 'address_display', false);
},
@@ -91,11 +98,6 @@
frm.add_custom_button(__('Quotation'),
cur_frm.cscript.create_quotation, __('Create'));
- if(doc.status!=="Quotation") {
- frm.add_custom_button(__('Lost'), () => {
- frm.trigger('set_as_lost_dialog');
- });
- }
}
if(!frm.doc.__islocal && frm.perm[0].write && frm.doc.docstatus==0) {
diff --git a/erpnext/education/api.py b/erpnext/education/api.py
index afa0be9..4493a3f 100644
--- a/erpnext/education/api.py
+++ b/erpnext/education/api.py
@@ -34,11 +34,14 @@
}
}}, ignore_permissions=True)
student.save()
+
+ student_applicant = frappe.db.get_value("Student Applicant", source_name,
+ ["student_category", "program"], as_dict=True)
program_enrollment = frappe.new_doc("Program Enrollment")
program_enrollment.student = student.name
- program_enrollment.student_category = student.student_category
+ program_enrollment.student_category = student_applicant.student_category
program_enrollment.student_name = student.title
- program_enrollment.program = frappe.db.get_value("Student Applicant", source_name, "program")
+ program_enrollment.program = student_applicant.program
frappe.publish_realtime('enroll_student_progress', {"progress": [2, 4]}, user=frappe.session.user)
return program_enrollment
diff --git a/erpnext/education/doctype/program_enrollment_tool_student/program_enrollment_tool_student.json b/erpnext/education/doctype/program_enrollment_tool_student/program_enrollment_tool_student.json
index 9be292b..1d74973 100644
--- a/erpnext/education/doctype/program_enrollment_tool_student/program_enrollment_tool_student.json
+++ b/erpnext/education/doctype/program_enrollment_tool_student/program_enrollment_tool_student.json
@@ -1,195 +1,68 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2016-06-10 03:29:02.539914",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
+ "actions": [],
+ "creation": "2016-06-10 03:29:02.539914",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "student_applicant",
+ "student",
+ "student_name",
+ "column_break_3",
+ "student_batch_name",
+ "student_category"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "",
- "fieldname": "student_applicant",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Student Applicant",
- "length": 0,
- "no_copy": 0,
- "options": "Student Applicant",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "student_applicant",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Student Applicant",
+ "options": "Student Applicant"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "",
- "fieldname": "student",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Student",
- "length": 0,
- "no_copy": 0,
- "options": "Student",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "student",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Student",
+ "options": "Student"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_3",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "column_break_3",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "student_name",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Student Name",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "student_name",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Student Name",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "student_batch_name",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Student Batch Name",
- "length": 0,
- "no_copy": 0,
- "options": "Student Batch Name",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
+ "fieldname": "student_batch_name",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Student Batch Name",
+ "options": "Student Batch Name"
+ },
+ {
+ "fieldname": "student_category",
+ "fieldtype": "Link",
+ "label": "Student Category",
+ "options": "Student Category",
+ "read_only": 1
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2018-01-02 12:03:53.890741",
- "modified_by": "Administrator",
- "module": "Education",
- "name": "Program Enrollment Tool Student",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "restrict_to_domain": "Education",
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 0,
- "track_seen": 0
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2021-07-29 18:19:54.471594",
+ "modified_by": "Administrator",
+ "module": "Education",
+ "name": "Program Enrollment Tool Student",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "restrict_to_domain": "Education",
+ "sort_field": "modified",
+ "sort_order": "DESC"
}
\ No newline at end of file
diff --git a/erpnext/erpnext_integrations/connectors/shopify_connection.py b/erpnext/erpnext_integrations/connectors/shopify_connection.py
deleted file mode 100644
index 5d5b2e1..0000000
--- a/erpnext/erpnext_integrations/connectors/shopify_connection.py
+++ /dev/null
@@ -1,353 +0,0 @@
-from __future__ import unicode_literals
-import frappe
-from frappe import _
-import json
-from frappe.utils import cstr, cint, nowdate, getdate, flt, get_request_session, get_datetime
-from erpnext.erpnext_integrations.utils import validate_webhooks_request
-from erpnext.selling.doctype.sales_order.sales_order import make_delivery_note, make_sales_invoice
-from erpnext.erpnext_integrations.doctype.shopify_settings.sync_product import sync_item_from_shopify
-from erpnext.erpnext_integrations.doctype.shopify_settings.sync_customer import create_customer
-from erpnext.erpnext_integrations.doctype.shopify_log.shopify_log import make_shopify_log, dump_request_data
-from erpnext.erpnext_integrations.doctype.shopify_settings.shopify_settings import get_shopify_url, get_header
-
-@frappe.whitelist(allow_guest=True)
-@validate_webhooks_request("Shopify Settings", 'X-Shopify-Hmac-Sha256', secret_key='shared_secret')
-def store_request_data(order=None, event=None):
- if frappe.request:
- order = json.loads(frappe.request.data)
- event = frappe.request.headers.get('X-Shopify-Topic')
-
- dump_request_data(order, event)
-
-def sync_sales_order(order, request_id=None, old_order_sync=False):
- frappe.set_user('Administrator')
- shopify_settings = frappe.get_doc("Shopify Settings")
- frappe.flags.request_id = request_id
-
- if not frappe.db.get_value("Sales Order", filters={"shopify_order_id": cstr(order['id'])}):
- try:
- validate_customer(order, shopify_settings)
- validate_item(order, shopify_settings)
- create_order(order, shopify_settings, old_order_sync=old_order_sync)
- except Exception as e:
- make_shopify_log(status="Error", exception=e)
-
- else:
- make_shopify_log(status="Success")
-
-def prepare_sales_invoice(order, request_id=None):
- frappe.set_user('Administrator')
- shopify_settings = frappe.get_doc("Shopify Settings")
- frappe.flags.request_id = request_id
-
- try:
- sales_order = get_sales_order(cstr(order['id']))
- if sales_order:
- create_sales_invoice(order, shopify_settings, sales_order)
- make_shopify_log(status="Success")
- except Exception as e:
- make_shopify_log(status="Error", exception=e, rollback=True)
-
-def prepare_delivery_note(order, request_id=None):
- frappe.set_user('Administrator')
- shopify_settings = frappe.get_doc("Shopify Settings")
- frappe.flags.request_id = request_id
-
- try:
- sales_order = get_sales_order(cstr(order['id']))
- if sales_order:
- create_delivery_note(order, shopify_settings, sales_order)
- make_shopify_log(status="Success")
- except Exception as e:
- make_shopify_log(status="Error", exception=e, rollback=True)
-
-def get_sales_order(shopify_order_id):
- sales_order = frappe.db.get_value("Sales Order", filters={"shopify_order_id": shopify_order_id})
- if sales_order:
- so = frappe.get_doc("Sales Order", sales_order)
- return so
-
-def validate_customer(order, shopify_settings):
- customer_id = order.get("customer", {}).get("id")
- if customer_id:
- if not frappe.db.get_value("Customer", {"shopify_customer_id": customer_id}, "name"):
- create_customer(order.get("customer"), shopify_settings)
-
-def validate_item(order, shopify_settings):
- for item in order.get("line_items"):
- if item.get("product_id") and not frappe.db.get_value("Item", {"shopify_product_id": item.get("product_id")}, "name"):
- sync_item_from_shopify(shopify_settings, item)
-
-def create_order(order, shopify_settings, old_order_sync=False, company=None):
- so = create_sales_order(order, shopify_settings, company)
- if so:
- if order.get("financial_status") == "paid":
- create_sales_invoice(order, shopify_settings, so, old_order_sync=old_order_sync)
-
- if order.get("fulfillments") and not old_order_sync:
- create_delivery_note(order, shopify_settings, so)
-
-def create_sales_order(shopify_order, shopify_settings, company=None):
- product_not_exists = []
- customer = frappe.db.get_value("Customer", {"shopify_customer_id": shopify_order.get("customer", {}).get("id")}, "name")
- so = frappe.db.get_value("Sales Order", {"shopify_order_id": shopify_order.get("id")}, "name")
-
- if not so:
- items = get_order_items(shopify_order.get("line_items"), shopify_settings, getdate(shopify_order.get('created_at')))
-
- if not items:
- message = 'Following items exists in the shopify order but relevant records were not found in the shopify Product master'
- message += "\n" + ", ".join(product_not_exists)
-
- make_shopify_log(status="Error", exception=message, rollback=True)
-
- return ''
-
- so = frappe.get_doc({
- "doctype": "Sales Order",
- "naming_series": shopify_settings.sales_order_series or "SO-Shopify-",
- "shopify_order_id": shopify_order.get("id"),
- "shopify_order_number": shopify_order.get("name"),
- "customer": customer or shopify_settings.default_customer,
- "transaction_date": getdate(shopify_order.get("created_at")) or nowdate(),
- "delivery_date": getdate(shopify_order.get("created_at")) or nowdate(),
- "company": shopify_settings.company,
- "selling_price_list": shopify_settings.price_list,
- "ignore_pricing_rule": 1,
- "items": items,
- "taxes": get_order_taxes(shopify_order, shopify_settings),
- "apply_discount_on": "Grand Total",
- "discount_amount": get_discounted_amount(shopify_order),
- })
-
- if company:
- so.update({
- "company": company,
- "status": "Draft"
- })
- so.flags.ignore_mandatory = True
- so.save(ignore_permissions=True)
- so.submit()
-
- else:
- so = frappe.get_doc("Sales Order", so)
-
- frappe.db.commit()
- return so
-
-def create_sales_invoice(shopify_order, shopify_settings, so, old_order_sync=False):
- if not frappe.db.get_value("Sales Invoice", {"shopify_order_id": shopify_order.get("id")}, "name")\
- and so.docstatus==1 and not so.per_billed and cint(shopify_settings.sync_sales_invoice):
-
- if old_order_sync:
- posting_date = getdate(shopify_order.get('created_at'))
- else:
- posting_date = nowdate()
-
- si = make_sales_invoice(so.name, ignore_permissions=True)
- si.shopify_order_id = shopify_order.get("id")
- si.shopify_order_number = shopify_order.get("name")
- si.set_posting_time = 1
- si.posting_date = posting_date
- si.due_date = posting_date
- si.naming_series = shopify_settings.sales_invoice_series or "SI-Shopify-"
- si.flags.ignore_mandatory = True
- set_cost_center(si.items, shopify_settings.cost_center)
- si.insert(ignore_mandatory=True)
- si.submit()
- make_payament_entry_against_sales_invoice(si, shopify_settings, posting_date)
- frappe.db.commit()
-
-def set_cost_center(items, cost_center):
- for item in items:
- item.cost_center = cost_center
-
-def make_payament_entry_against_sales_invoice(doc, shopify_settings, posting_date=None):
- from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
- payment_entry = get_payment_entry(doc.doctype, doc.name, bank_account=shopify_settings.cash_bank_account)
- payment_entry.flags.ignore_mandatory = True
- payment_entry.reference_no = doc.name
- payment_entry.posting_date = posting_date or nowdate()
- payment_entry.reference_date = posting_date or nowdate()
- payment_entry.insert(ignore_permissions=True)
- payment_entry.submit()
-
-def create_delivery_note(shopify_order, shopify_settings, so):
- if not cint(shopify_settings.sync_delivery_note):
- return
-
- for fulfillment in shopify_order.get("fulfillments"):
- if not frappe.db.get_value("Delivery Note", {"shopify_fulfillment_id": fulfillment.get("id")}, "name")\
- and so.docstatus==1:
-
- dn = make_delivery_note(so.name)
- dn.shopify_order_id = fulfillment.get("order_id")
- dn.shopify_order_number = shopify_order.get("name")
- dn.set_posting_time = 1
- dn.posting_date = getdate(fulfillment.get("created_at"))
- dn.shopify_fulfillment_id = fulfillment.get("id")
- dn.naming_series = shopify_settings.delivery_note_series or "DN-Shopify-"
- dn.items = get_fulfillment_items(dn.items, fulfillment.get("line_items"), shopify_settings)
- dn.flags.ignore_mandatory = True
- dn.save()
- dn.submit()
- frappe.db.commit()
-
-def get_fulfillment_items(dn_items, fulfillment_items, shopify_settings):
- return [dn_item.update({"qty": item.get("quantity")}) for item in fulfillment_items for dn_item in dn_items\
- if get_item_code(item) == dn_item.item_code]
-
-def get_discounted_amount(order):
- discounted_amount = 0.0
- for discount in order.get("discount_codes"):
- discounted_amount += flt(discount.get("amount"))
- return discounted_amount
-
-def get_order_items(order_items, shopify_settings, delivery_date):
- items = []
- all_product_exists = True
- product_not_exists = []
-
- for shopify_item in order_items:
- if not shopify_item.get('product_exists'):
- all_product_exists = False
- product_not_exists.append({'title':shopify_item.get('title'),
- 'shopify_order_id': shopify_item.get('id')})
- continue
-
- if all_product_exists:
- item_code = get_item_code(shopify_item)
- items.append({
- "item_code": item_code,
- "item_name": shopify_item.get("name"),
- "rate": shopify_item.get("price"),
- "delivery_date": delivery_date,
- "qty": shopify_item.get("quantity"),
- "stock_uom": shopify_item.get("uom") or _("Nos"),
- "warehouse": shopify_settings.warehouse
- })
- else:
- items = []
-
- return items
-
-def get_item_code(shopify_item):
- item_code = frappe.db.get_value("Item", {"shopify_variant_id": shopify_item.get("variant_id")}, "item_code")
- if not item_code:
- item_code = frappe.db.get_value("Item", {"shopify_product_id": shopify_item.get("product_id")}, "item_code")
- if not item_code:
- item_code = frappe.db.get_value("Item", {"item_name": shopify_item.get("title")}, "item_code")
-
- return item_code
-
-def get_order_taxes(shopify_order, shopify_settings):
- taxes = []
- for tax in shopify_order.get("tax_lines"):
- taxes.append({
- "charge_type": _("On Net Total"),
- "account_head": get_tax_account_head(tax),
- "description": "{0} - {1}%".format(tax.get("title"), tax.get("rate") * 100.0),
- "rate": tax.get("rate") * 100.00,
- "included_in_print_rate": 1 if shopify_order.get("taxes_included") else 0,
- "cost_center": shopify_settings.cost_center
- })
-
- taxes = update_taxes_with_shipping_lines(taxes, shopify_order.get("shipping_lines"), shopify_settings)
-
- return taxes
-
-def update_taxes_with_shipping_lines(taxes, shipping_lines, shopify_settings):
- """Shipping lines represents the shipping details,
- each such shipping detail consists of a list of tax_lines"""
- for shipping_charge in shipping_lines:
- if shipping_charge.get("price"):
- taxes.append({
- "charge_type": _("Actual"),
- "account_head": get_tax_account_head(shipping_charge),
- "description": shipping_charge["title"],
- "tax_amount": shipping_charge["price"],
- "cost_center": shopify_settings.cost_center
- })
-
- for tax in shipping_charge.get("tax_lines"):
- taxes.append({
- "charge_type": _("Actual"),
- "account_head": get_tax_account_head(tax),
- "description": tax["title"],
- "tax_amount": tax["price"],
- "cost_center": shopify_settings.cost_center
- })
-
- return taxes
-
-def get_tax_account_head(tax):
- tax_title = tax.get("title").encode("utf-8")
-
- tax_account = frappe.db.get_value("Shopify Tax Account", \
- {"parent": "Shopify Settings", "shopify_tax": tax_title}, "tax_account")
-
- if not tax_account:
- frappe.throw(_("Tax Account not specified for Shopify Tax {0}").format(tax.get("title")))
-
- return tax_account
-
-@frappe.whitelist(allow_guest=True)
-def sync_old_orders():
- frappe.set_user('Administrator')
- shopify_settings = frappe.get_doc('Shopify Settings')
-
- if not shopify_settings.sync_missing_orders:
- return
-
- url = get_url(shopify_settings)
- session = get_request_session()
-
- try:
- res = session.get(url, headers=get_header(shopify_settings))
- res.raise_for_status()
- orders = res.json()["orders"]
-
- for order in orders:
- if is_sync_complete(shopify_settings, order):
- stop_sync(shopify_settings)
- return
-
- sync_sales_order(order=order, old_order_sync=True)
- last_order_id = order.get('id')
-
- if last_order_id:
- shopify_settings.load_from_db()
- shopify_settings.last_order_id = last_order_id
- shopify_settings.save()
- frappe.db.commit()
-
- except Exception as e:
- raise e
-
-def stop_sync(shopify_settings):
- shopify_settings.sync_missing_orders = 0
- shopify_settings.last_order_id = ''
- shopify_settings.save()
- frappe.db.commit()
-
-def get_url(shopify_settings):
- last_order_id = shopify_settings.last_order_id
-
- if not last_order_id:
- if shopify_settings.sync_based_on == 'Date':
- url = get_shopify_url("admin/api/2021-04/orders.json?limit=250&created_at_min={0}&since_id=0".format(
- get_datetime(shopify_settings.from_date)), shopify_settings)
- else:
- url = get_shopify_url("admin/api/2021-04/orders.json?limit=250&since_id={0}".format(
- shopify_settings.from_order_id), shopify_settings)
- else:
- url = get_shopify_url("admin/api/2021-04/orders.json?limit=250&since_id={0}".format(last_order_id), shopify_settings)
-
- return url
-
-def is_sync_complete(shopify_settings, order):
- if shopify_settings.sync_based_on == 'Date':
- return getdate(shopify_settings.to_date) < getdate(order.get('created_at'))
- else:
- return cstr(order.get('id')) == cstr(shopify_settings.to_order_id)
-
diff --git a/erpnext/erpnext_integrations/doctype/shopify_log/shopify_log.js b/erpnext/erpnext_integrations/doctype/shopify_log/shopify_log.js
deleted file mode 100644
index d3fe7d2b..0000000
--- a/erpnext/erpnext_integrations/doctype/shopify_log/shopify_log.js
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on('Shopify Log', {
- refresh: function(frm) {
- if (frm.doc.request_data && frm.doc.status=='Error'){
- frm.add_custom_button('Resync', function() {
- frappe.call({
- method:"erpnext.erpnext_integrations.doctype.shopify_log.shopify_log.resync",
- args:{
- method:frm.doc.method,
- name: frm.doc.name,
- request_data: frm.doc.request_data
- },
- callback: function(r){
- frappe.msgprint(__("Order rescheduled for sync"))
- }
- })
- }).addClass('btn-primary');
- }
- }
-});
diff --git a/erpnext/erpnext_integrations/doctype/shopify_log/shopify_log.json b/erpnext/erpnext_integrations/doctype/shopify_log/shopify_log.json
deleted file mode 100644
index ab373ee..0000000
--- a/erpnext/erpnext_integrations/doctype/shopify_log/shopify_log.json
+++ /dev/null
@@ -1,268 +0,0 @@
-{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2016-03-14 10:02:06.227184",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "System",
- "editable_grid": 0,
- "fields": [
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "title",
- "fieldtype": "Data",
- "hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Title",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "Queued",
- "fieldname": "status",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Status",
- "length": 0,
- "no_copy": 0,
- "options": "",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "method",
- "fieldtype": "Small Text",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Method",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "message",
- "fieldtype": "Code",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Message",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "traceback",
- "fieldtype": "Code",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Traceback",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "request_data",
- "fieldtype": "Code",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Request Data",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- }
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 1,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2018-04-20 16:23:36.862381",
- "modified_by": "Administrator",
- "module": "ERPNext Integrations",
- "name": "Shopify Log",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [
- {
- "amend": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Administrator",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
- "write": 1
- },
- {
- "amend": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "System Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
- "write": 1
- }
- ],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "title_field": "title",
- "track_changes": 0,
- "track_seen": 0
-}
\ No newline at end of file
diff --git a/erpnext/erpnext_integrations/doctype/shopify_log/shopify_log.py b/erpnext/erpnext_integrations/doctype/shopify_log/shopify_log.py
deleted file mode 100644
index a2b6af9..0000000
--- a/erpnext/erpnext_integrations/doctype/shopify_log/shopify_log.py
+++ /dev/null
@@ -1,68 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-from __future__ import unicode_literals
-import frappe
-import json
-from frappe.model.document import Document
-from erpnext.erpnext_integrations.utils import get_webhook_address
-
-class ShopifyLog(Document):
- pass
-
-
-def make_shopify_log(status="Queued", exception=None, rollback=False):
- # if name not provided by log calling method then fetch existing queued state log
- make_new = False
-
- if not frappe.flags.request_id:
- make_new = True
-
- if rollback:
- frappe.db.rollback()
-
- if make_new:
- log = frappe.get_doc({"doctype":"Shopify Log"}).insert(ignore_permissions=True)
- else:
- log = log = frappe.get_doc("Shopify Log", frappe.flags.request_id)
-
- log.message = get_message(exception)
- log.traceback = frappe.get_traceback()
- log.status = status
- log.save(ignore_permissions=True)
- frappe.db.commit()
-
-def get_message(exception):
- message = None
-
- if hasattr(exception, 'message'):
- message = exception.message
- elif hasattr(exception, '__str__'):
- message = exception.__str__()
- else:
- message = "Something went wrong while syncing"
- return message
-
-def dump_request_data(data, event="create/order"):
- event_mapper = {
- "orders/create": get_webhook_address(connector_name='shopify_connection', method="sync_sales_order", exclude_uri=True),
- "orders/paid" : get_webhook_address(connector_name='shopify_connection', method="prepare_sales_invoice", exclude_uri=True),
- "orders/fulfilled": get_webhook_address(connector_name='shopify_connection', method="prepare_delivery_note", exclude_uri=True)
- }
-
- log = frappe.get_doc({
- "doctype": "Shopify Log",
- "request_data": json.dumps(data, indent=1),
- "method": event_mapper[event]
- }).insert(ignore_permissions=True)
-
- frappe.db.commit()
- frappe.enqueue(method=event_mapper[event], queue='short', timeout=300, is_async=True,
- **{"order": data, "request_id": log.name})
-
-@frappe.whitelist()
-def resync(method, name, request_data):
- frappe.db.set_value("Shopify Log", name, "status", "Queued", update_modified=False)
- frappe.enqueue(method=method, queue='short', timeout=300, is_async=True,
- **{"order": json.loads(request_data), "request_id": name})
diff --git a/erpnext/erpnext_integrations/doctype/shopify_log/shopify_log_list.js b/erpnext/erpnext_integrations/doctype/shopify_log/shopify_log_list.js
deleted file mode 100644
index 0913ce4..0000000
--- a/erpnext/erpnext_integrations/doctype/shopify_log/shopify_log_list.js
+++ /dev/null
@@ -1,12 +0,0 @@
-frappe.listview_settings['Shopify Log'] = {
- add_fields: ["status"],
- get_indicator: function(doc) {
- if(doc.status==="Success"){
- return [__("Success"), "green", "status,=,Success"];
- } else if(doc.status ==="Error"){
- return [__("Error"), "red", "status,=,Error"];
- } else if(doc.status ==="Queued"){
- return [__("Queued"), "orange", "status,=,Queued"];
- }
- }
-}
diff --git a/erpnext/erpnext_integrations/doctype/shopify_log/test_shopify_log.js b/erpnext/erpnext_integrations/doctype/shopify_log/test_shopify_log.js
deleted file mode 100644
index d22b6d5..0000000
--- a/erpnext/erpnext_integrations/doctype/shopify_log/test_shopify_log.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Shopify Log", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Shopify Log
- () => frappe.tests.make('Shopify Log', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/erpnext_integrations/doctype/shopify_log/test_shopify_log.py b/erpnext/erpnext_integrations/doctype/shopify_log/test_shopify_log.py
deleted file mode 100644
index 5892e1d..0000000
--- a/erpnext/erpnext_integrations/doctype/shopify_log/test_shopify_log.py
+++ /dev/null
@@ -1,12 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-from __future__ import unicode_literals
-
-import frappe
-import unittest
-
-# test_records = frappe.get_test_records('Shopify Log')
-
-class TestShopifyLog(unittest.TestCase):
- pass
diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.js b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.js
deleted file mode 100644
index 1574795..0000000
--- a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.js
+++ /dev/null
@@ -1,90 +0,0 @@
-// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-// License: GNU General Public License v3. See license.txt
-
-frappe.provide("erpnext_integrations.shopify_settings");
-
-frappe.ui.form.on("Shopify Settings", "onload", function(frm){
- frappe.call({
- method:"erpnext.erpnext_integrations.doctype.shopify_settings.shopify_settings.get_series",
- callback:function(r){
- $.each(r.message, function(key, value){
- set_field_options(key, value);
- });
- }
- });
- erpnext_integrations.shopify_settings.setup_queries(frm);
-})
-
-frappe.ui.form.on("Shopify Settings", "app_type", function(frm) {
- frm.toggle_reqd("api_key", (frm.doc.app_type == "Private"));
- frm.toggle_reqd("password", (frm.doc.app_type == "Private"));
-})
-
-frappe.ui.form.on("Shopify Settings", "refresh", function(frm){
- if(!frm.doc.__islocal && frm.doc.enable_shopify === 1){
- frm.toggle_reqd("price_list", true);
- frm.toggle_reqd("warehouse", true);
- frm.toggle_reqd("taxes", true);
- frm.toggle_reqd("company", true);
- frm.toggle_reqd("cost_center", true);
- frm.toggle_reqd("cash_bank_account", true);
- frm.toggle_reqd("sales_order_series", true);
- frm.toggle_reqd("customer_group", true);
- frm.toggle_reqd("shared_secret", true);
-
- frm.toggle_reqd("sales_invoice_series", frm.doc.sync_sales_invoice);
- frm.toggle_reqd("delivery_note_series", frm.doc.sync_delivery_note);
-
- }
-})
-
-$.extend(erpnext_integrations.shopify_settings, {
- setup_queries: function(frm) {
- frm.fields_dict["warehouse"].get_query = function(doc) {
- return {
- filters:{
- "company": doc.company,
- "is_group": "No"
- }
- }
- }
-
- frm.fields_dict["taxes"].grid.get_field("tax_account").get_query = function(doc){
- return {
- "query": "erpnext.controllers.queries.tax_account_query",
- "filters": {
- "account_type": ["Tax", "Chargeable", "Expense Account"],
- "company": doc.company
- }
- }
- }
-
- frm.fields_dict["cash_bank_account"].get_query = function(doc) {
- return {
- filters: [
- ["Account", "account_type", "in", ["Cash", "Bank"]],
- ["Account", "root_type", "=", "Asset"],
- ["Account", "is_group", "=",0],
- ["Account", "company", "=", doc.company]
- ]
- }
- }
-
- frm.fields_dict["cost_center"].get_query = function(doc) {
- return {
- filters:{
- "company": doc.company,
- "is_group": "No"
- }
- }
- }
-
- frm.fields_dict["price_list"].get_query = function() {
- return {
- filters:{
- "selling": 1
- }
- }
- }
- }
-})
diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.json b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.json
deleted file mode 100644
index 308e7d1..0000000
--- a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.json
+++ /dev/null
@@ -1,353 +0,0 @@
-{
- "actions": [],
- "creation": "2015-05-18 05:21:07.270859",
- "doctype": "DocType",
- "document_type": "System",
- "engine": "InnoDB",
- "field_order": [
- "status_html",
- "enable_shopify",
- "app_type",
- "column_break_4",
- "last_sync_datetime",
- "section_break_2",
- "shopify_url",
- "api_key",
- "column_break_3",
- "password",
- "shared_secret",
- "access_token",
- "section_break_38",
- "webhooks",
- "section_break_15",
- "default_customer",
- "column_break_19",
- "customer_group",
- "company_dependent_settings",
- "company",
- "cash_bank_account",
- "column_break_20",
- "cost_center",
- "erp_settings",
- "price_list",
- "update_price_in_erpnext_price_list",
- "column_break_26",
- "warehouse",
- "section_break_25",
- "sales_order_series",
- "column_break_27",
- "sync_delivery_note",
- "delivery_note_series",
- "sync_sales_invoice",
- "sales_invoice_series",
- "section_break_22",
- "html_16",
- "taxes",
- "syncing_details_section",
- "sync_missing_orders",
- "sync_based_on",
- "column_break_41",
- "from_date",
- "to_date",
- "from_order_id",
- "to_order_id",
- "last_order_id"
- ],
- "fields": [
- {
- "fieldname": "status_html",
- "fieldtype": "HTML",
- "label": "status html",
- "read_only": 1
- },
- {
- "default": "0",
- "fieldname": "enable_shopify",
- "fieldtype": "Check",
- "label": "Enable Shopify"
- },
- {
- "default": "Private",
- "fieldname": "app_type",
- "fieldtype": "Data",
- "in_list_view": 1,
- "label": "App Type",
- "read_only": 1,
- "reqd": 1
- },
- {
- "fieldname": "column_break_4",
- "fieldtype": "Column Break"
- },
- {
- "fieldname": "last_sync_datetime",
- "fieldtype": "Datetime",
- "label": "Last Sync Datetime",
- "read_only": 1
- },
- {
- "fieldname": "section_break_2",
- "fieldtype": "Section Break"
- },
- {
- "description": "eg: frappe.myshopify.com",
- "fieldname": "shopify_url",
- "fieldtype": "Data",
- "in_list_view": 1,
- "label": "Shop URL",
- "reqd": 1
- },
- {
- "depends_on": "eval:doc.app_type==\"Private\"",
- "fieldname": "api_key",
- "fieldtype": "Data",
- "label": "API Key"
- },
- {
- "fieldname": "column_break_3",
- "fieldtype": "Column Break"
- },
- {
- "depends_on": "eval:doc.app_type==\"Private\"",
- "fieldname": "password",
- "fieldtype": "Password",
- "label": "Password"
- },
- {
- "fieldname": "shared_secret",
- "fieldtype": "Data",
- "label": "Shared secret"
- },
- {
- "fieldname": "access_token",
- "fieldtype": "Data",
- "hidden": 1,
- "label": "Access Token",
- "read_only": 1
- },
- {
- "collapsible": 1,
- "fieldname": "section_break_38",
- "fieldtype": "Section Break",
- "label": "Webhooks Details"
- },
- {
- "fieldname": "webhooks",
- "fieldtype": "Table",
- "label": "Webhooks",
- "options": "Shopify Webhook Detail",
- "read_only": 1
- },
- {
- "fieldname": "section_break_15",
- "fieldtype": "Section Break",
- "label": "Customer Settings"
- },
- {
- "description": "If Shopify does not have a customer in the order, then while syncing the orders, the system will consider the default customer for the order",
- "fieldname": "default_customer",
- "fieldtype": "Link",
- "label": "Default Customer",
- "options": "Customer"
- },
- {
- "fieldname": "column_break_19",
- "fieldtype": "Column Break"
- },
- {
- "description": "Customer Group will set to selected group while syncing customers from Shopify",
- "fieldname": "customer_group",
- "fieldtype": "Link",
- "label": "Customer Group",
- "options": "Customer Group"
- },
- {
- "fieldname": "company_dependent_settings",
- "fieldtype": "Section Break"
- },
- {
- "fieldname": "company",
- "fieldtype": "Link",
- "label": "For Company",
- "options": "Company"
- },
- {
- "description": "Cash Account will used for Sales Invoice creation",
- "fieldname": "cash_bank_account",
- "fieldtype": "Link",
- "label": "Cash/Bank Account",
- "options": "Account"
- },
- {
- "fieldname": "column_break_20",
- "fieldtype": "Column Break"
- },
- {
- "fieldname": "cost_center",
- "fieldtype": "Link",
- "label": "Cost Center",
- "options": "Cost Center"
- },
- {
- "fieldname": "erp_settings",
- "fieldtype": "Section Break"
- },
- {
- "fieldname": "price_list",
- "fieldtype": "Link",
- "label": "Price List",
- "options": "Price List"
- },
- {
- "default": "0",
- "fieldname": "update_price_in_erpnext_price_list",
- "fieldtype": "Check",
- "label": "Update Price from Shopify To ERPNext Price List"
- },
- {
- "fieldname": "column_break_26",
- "fieldtype": "Column Break"
- },
- {
- "description": "Default Warehouse to to create Sales Order and Delivery Note",
- "fieldname": "warehouse",
- "fieldtype": "Link",
- "label": "Warehouse",
- "options": "Warehouse"
- },
- {
- "fieldname": "section_break_25",
- "fieldtype": "Section Break"
- },
- {
- "fieldname": "sales_order_series",
- "fieldtype": "Select",
- "label": "Sales Order Series"
- },
- {
- "fieldname": "column_break_27",
- "fieldtype": "Column Break"
- },
- {
- "default": "0",
- "fieldname": "sync_delivery_note",
- "fieldtype": "Check",
- "label": "Import Delivery Notes from Shopify on Shipment"
- },
- {
- "depends_on": "eval:doc.sync_delivery_note==1",
- "fieldname": "delivery_note_series",
- "fieldtype": "Select",
- "label": "Delivery Note Series"
- },
- {
- "default": "0",
- "fieldname": "sync_sales_invoice",
- "fieldtype": "Check",
- "label": "Import Sales Invoice from Shopify if Payment is marked"
- },
- {
- "depends_on": "eval:doc.sync_sales_invoice==1",
- "fieldname": "sales_invoice_series",
- "fieldtype": "Select",
- "label": "Sales Invoice Series"
- },
- {
- "fieldname": "section_break_22",
- "fieldtype": "Section Break"
- },
- {
- "fieldname": "html_16",
- "fieldtype": "HTML",
- "options": "Map Shopify Taxes / Shipping Charges to ERPNext Account"
- },
- {
- "fieldname": "taxes",
- "fieldtype": "Table",
- "label": "Shopify Tax Account",
- "options": "Shopify Tax Account"
- },
- {
- "collapsible": 1,
- "fieldname": "syncing_details_section",
- "fieldtype": "Section Break",
- "label": "Syncing Missing Orders"
- },
- {
- "depends_on": "eval:doc.sync_missing_orders",
- "fieldname": "last_order_id",
- "fieldtype": "Data",
- "label": "Last Order Id",
- "read_only": 1
- },
- {
- "fieldname": "column_break_41",
- "fieldtype": "Column Break"
- },
- {
- "default": "0",
- "description": "On checking this Order from the ",
- "fieldname": "sync_missing_orders",
- "fieldtype": "Check",
- "label": "Sync Missing Old Shopify Orders"
- },
- {
- "depends_on": "eval:doc.sync_missing_orders",
- "fieldname": "sync_based_on",
- "fieldtype": "Select",
- "label": "Sync Based On",
- "mandatory_depends_on": "eval:doc.sync_missing_orders",
- "options": "\nDate\nShopify Order Id"
- },
- {
- "depends_on": "eval:doc.sync_based_on == 'Date' && doc.sync_missing_orders",
- "fieldname": "from_date",
- "fieldtype": "Date",
- "label": "From Date",
- "mandatory_depends_on": "eval:doc.sync_based_on == 'Date' && doc.sync_missing_orders"
- },
- {
- "depends_on": "eval:doc.sync_based_on == 'Date' && doc.sync_missing_orders",
- "fieldname": "to_date",
- "fieldtype": "Date",
- "label": "To Date",
- "mandatory_depends_on": "eval:doc.sync_based_on == 'Date' && doc.sync_missing_orders"
- },
- {
- "depends_on": "eval:doc.sync_based_on == 'Shopify Order Id' && doc.sync_missing_orders",
- "fieldname": "from_order_id",
- "fieldtype": "Data",
- "label": "From Order Id",
- "mandatory_depends_on": "eval:doc.sync_based_on == 'Shopify Order Id' && doc.sync_missing_orders"
- },
- {
- "depends_on": "eval:doc.sync_based_on == 'Shopify Order Id' && doc.sync_missing_orders",
- "fieldname": "to_order_id",
- "fieldtype": "Data",
- "label": "To Order Id",
- "mandatory_depends_on": "eval:doc.sync_based_on == 'Shopify Order Id' && doc.sync_missing_orders"
- }
- ],
- "issingle": 1,
- "links": [],
- "modified": "2021-03-02 17:35:41.953317",
- "modified_by": "Administrator",
- "module": "ERPNext Integrations",
- "name": "Shopify Settings",
- "owner": "Administrator",
- "permissions": [
- {
- "create": 1,
- "delete": 1,
- "email": 1,
- "print": 1,
- "read": 1,
- "role": "System Manager",
- "share": 1,
- "write": 1
- }
- ],
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1
-}
\ No newline at end of file
diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py
deleted file mode 100644
index 381c5e5..0000000
--- a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py
+++ /dev/null
@@ -1,144 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-from __future__ import unicode_literals
-import frappe
-import json
-from frappe import _
-from frappe.model.document import Document
-from frappe.utils import get_request_session
-from requests.exceptions import HTTPError
-from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
-from erpnext.erpnext_integrations.utils import get_webhook_address
-from erpnext.erpnext_integrations.doctype.shopify_log.shopify_log import make_shopify_log
-
-class ShopifySettings(Document):
- def validate(self):
- if self.enable_shopify == 1:
- setup_custom_fields()
- self.validate_access_credentials()
- self.register_webhooks()
- else:
- self.unregister_webhooks()
-
- def validate_access_credentials(self):
- if not (self.get_password(raise_exception=False) and self.api_key and self.shopify_url):
- frappe.msgprint(_("Missing value for Password, API Key or Shopify URL"), raise_exception=frappe.ValidationError)
-
- def register_webhooks(self):
- webhooks = ["orders/create", "orders/paid", "orders/fulfilled"]
- # url = get_shopify_url('admin/webhooks.json', self)
- created_webhooks = [d.method for d in self.webhooks]
- url = get_shopify_url('admin/api/2021-04/webhooks.json', self)
- for method in webhooks:
- session = get_request_session()
- try:
- res = session.post(url, data=json.dumps({
- "webhook": {
- "topic": method,
- "address": get_webhook_address(connector_name='shopify_connection', method='store_request_data', force_https=True),
- "format": "json"
- }
- }), headers=get_header(self))
- res.raise_for_status()
- self.update_webhook_table(method, res.json())
-
- except HTTPError as e:
- error_message = res.json().get('errors', e)
- make_shopify_log(status="Warning", exception=error_message, rollback=True)
-
- except Exception as e:
- make_shopify_log(status="Warning", exception=e, rollback=True)
-
- def unregister_webhooks(self):
- session = get_request_session()
- deleted_webhooks = []
-
- for d in self.webhooks:
- url = get_shopify_url('admin/api/2021-04/webhooks/{0}.json'.format(d.webhook_id), self)
- try:
- res = session.delete(url, headers=get_header(self))
- res.raise_for_status()
- deleted_webhooks.append(d)
-
- except HTTPError as e:
- error_message = res.json().get('errors', e)
- make_shopify_log(status="Warning", exception=error_message, rollback=True)
-
- except Exception as e:
- frappe.log_error(message=e, title='Shopify Webhooks Issue')
-
- for d in deleted_webhooks:
- self.remove(d)
-
- def update_webhook_table(self, method, res):
- self.append("webhooks", {
- "webhook_id": res['webhook']['id'],
- "method": method
- })
-
-def get_shopify_url(path, settings):
- if settings.app_type == "Private":
- return 'https://{}:{}@{}/{}'.format(settings.api_key, settings.get_password('password'), settings.shopify_url, path)
- else:
- return 'https://{}/{}'.format(settings.shopify_url, path)
-
-def get_header(settings):
- header = {'Content-Type': 'application/json'}
-
- return header
-
-@frappe.whitelist()
-def get_series():
- return {
- "sales_order_series" : frappe.get_meta("Sales Order").get_options("naming_series") or "SO-Shopify-",
- "sales_invoice_series" : frappe.get_meta("Sales Invoice").get_options("naming_series") or "SI-Shopify-",
- "delivery_note_series" : frappe.get_meta("Delivery Note").get_options("naming_series") or "DN-Shopify-"
- }
-
-def setup_custom_fields():
- custom_fields = {
- "Customer": [
- dict(fieldname='shopify_customer_id', label='Shopify Customer Id',
- fieldtype='Data', insert_after='series', read_only=1, print_hide=1)
- ],
- "Supplier": [
- dict(fieldname='shopify_supplier_id', label='Shopify Supplier Id',
- fieldtype='Data', insert_after='supplier_name', read_only=1, print_hide=1)
- ],
- "Address": [
- dict(fieldname='shopify_address_id', label='Shopify Address Id',
- fieldtype='Data', insert_after='fax', read_only=1, print_hide=1)
- ],
- "Item": [
- dict(fieldname='shopify_variant_id', label='Shopify Variant Id',
- fieldtype='Data', insert_after='item_code', read_only=1, print_hide=1),
- dict(fieldname='shopify_product_id', label='Shopify Product Id',
- fieldtype='Data', insert_after='item_code', read_only=1, print_hide=1),
- dict(fieldname='shopify_description', label='Shopify Description',
- fieldtype='Text Editor', insert_after='description', read_only=1, print_hide=1)
- ],
- "Sales Order": [
- dict(fieldname='shopify_order_id', label='Shopify Order Id',
- fieldtype='Data', insert_after='title', read_only=1, print_hide=1),
- dict(fieldname='shopify_order_number', label='Shopify Order Number',
- fieldtype='Data', insert_after='shopify_order_id', read_only=1, print_hide=1)
- ],
- "Delivery Note":[
- dict(fieldname='shopify_order_id', label='Shopify Order Id',
- fieldtype='Data', insert_after='title', read_only=1, print_hide=1),
- dict(fieldname='shopify_order_number', label='Shopify Order Number',
- fieldtype='Data', insert_after='shopify_order_id', read_only=1, print_hide=1),
- dict(fieldname='shopify_fulfillment_id', label='Shopify Fulfillment Id',
- fieldtype='Data', insert_after='title', read_only=1, print_hide=1)
- ],
- "Sales Invoice": [
- dict(fieldname='shopify_order_id', label='Shopify Order Id',
- fieldtype='Data', insert_after='title', read_only=1, print_hide=1),
- dict(fieldname='shopify_order_number', label='Shopify Order Number',
- fieldtype='Data', insert_after='shopify_order_id', read_only=1, print_hide=1)
- ]
- }
-
- create_custom_fields(custom_fields)
diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/sync_customer.py b/erpnext/erpnext_integrations/doctype/shopify_settings/sync_customer.py
deleted file mode 100644
index 2af57f4..0000000
--- a/erpnext/erpnext_integrations/doctype/shopify_settings/sync_customer.py
+++ /dev/null
@@ -1,71 +0,0 @@
-from __future__ import unicode_literals
-import frappe
-from frappe import _
-
-def create_customer(shopify_customer, shopify_settings):
- import frappe.utils.nestedset
-
- cust_name = (shopify_customer.get("first_name") + " " + (shopify_customer.get("last_name") \
- and shopify_customer.get("last_name") or "")) if shopify_customer.get("first_name")\
- else shopify_customer.get("email")
-
- try:
- customer = frappe.get_doc({
- "doctype": "Customer",
- "name": shopify_customer.get("id"),
- "customer_name" : cust_name,
- "shopify_customer_id": shopify_customer.get("id"),
- "sync_with_shopify": 1,
- "customer_group": shopify_settings.customer_group,
- "territory": frappe.utils.nestedset.get_root_of("Territory"),
- "customer_type": _("Individual")
- })
- customer.flags.ignore_mandatory = True
- customer.insert(ignore_permissions=True)
-
- if customer:
- create_customer_address(customer, shopify_customer)
-
- frappe.db.commit()
-
- except Exception as e:
- raise e
-
-def create_customer_address(customer, shopify_customer):
- addresses = shopify_customer.get("addresses", [])
-
- if not addresses and "default_address" in shopify_customer:
- addresses.append(shopify_customer["default_address"])
-
- for i, address in enumerate(addresses):
- address_title, address_type = get_address_title_and_type(customer.customer_name, i)
- try :
- frappe.get_doc({
- "doctype": "Address",
- "shopify_address_id": address.get("id"),
- "address_title": address_title,
- "address_type": address_type,
- "address_line1": address.get("address1") or "Address 1",
- "address_line2": address.get("address2"),
- "city": address.get("city") or "City",
- "state": address.get("province"),
- "pincode": address.get("zip"),
- "country": address.get("country"),
- "phone": address.get("phone"),
- "email_id": shopify_customer.get("email"),
- "links": [{
- "link_doctype": "Customer",
- "link_name": customer.name
- }]
- }).insert(ignore_mandatory=True)
-
- except Exception as e:
- raise e
-
-def get_address_title_and_type(customer_name, index):
- address_type = _("Billing")
- address_title = customer_name
- if frappe.db.get_value("Address", "{0}-{1}".format(customer_name.strip(), address_type)):
- address_title = "{0}-{1}".format(customer_name.strip(), index)
-
- return address_title, address_type
diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/sync_product.py b/erpnext/erpnext_integrations/doctype/shopify_settings/sync_product.py
deleted file mode 100644
index 16efb6c..0000000
--- a/erpnext/erpnext_integrations/doctype/shopify_settings/sync_product.py
+++ /dev/null
@@ -1,309 +0,0 @@
-from __future__ import unicode_literals
-import frappe
-from frappe import _
-from erpnext import get_default_company
-from frappe.utils import cstr, cint, get_request_session
-from erpnext.erpnext_integrations.doctype.shopify_settings.shopify_settings import get_shopify_url, get_header
-
-shopify_variants_attr_list = ["option1", "option2", "option3"]
-
-def sync_item_from_shopify(shopify_settings, item):
- url = get_shopify_url("admin/api/2021-04/products/{0}.json".format(item.get("product_id")), shopify_settings)
- session = get_request_session()
-
- try:
- res = session.get(url, headers=get_header(shopify_settings))
- res.raise_for_status()
-
- shopify_item = res.json()["product"]
- make_item(shopify_settings.warehouse, shopify_item)
- except Exception as e:
- raise e
-
-def make_item(warehouse, shopify_item):
- add_item_weight(shopify_item)
-
- if has_variants(shopify_item):
- attributes = create_attribute(shopify_item)
- create_item(shopify_item, warehouse, 1, attributes)
- create_item_variants(shopify_item, warehouse, attributes, shopify_variants_attr_list)
-
- else:
- shopify_item["variant_id"] = shopify_item['variants'][0]["id"]
- create_item(shopify_item, warehouse)
-
-def add_item_weight(shopify_item):
- shopify_item["weight"] = shopify_item['variants'][0]["weight"]
- shopify_item["weight_unit"] = shopify_item['variants'][0]["weight_unit"]
-
-def has_variants(shopify_item):
- if len(shopify_item.get("options")) >= 1 and "Default Title" not in shopify_item.get("options")[0]["values"]:
- return True
- return False
-
-def create_attribute(shopify_item):
- attribute = []
- # shopify item dict
- for attr in shopify_item.get('options'):
- if not frappe.db.get_value("Item Attribute", attr.get("name"), "name"):
- frappe.get_doc({
- "doctype": "Item Attribute",
- "attribute_name": attr.get("name"),
- "item_attribute_values": [
- {
- "attribute_value": attr_value,
- "abbr":attr_value
- }
- for attr_value in attr.get("values")
- ]
- }).insert()
- attribute.append({"attribute": attr.get("name")})
-
- else:
- # check for attribute values
- item_attr = frappe.get_doc("Item Attribute", attr.get("name"))
- if not item_attr.numeric_values:
- set_new_attribute_values(item_attr, attr.get("values"))
- item_attr.save()
- attribute.append({"attribute": attr.get("name")})
-
- else:
- attribute.append({
- "attribute": attr.get("name"),
- "from_range": item_attr.get("from_range"),
- "to_range": item_attr.get("to_range"),
- "increment": item_attr.get("increment"),
- "numeric_values": item_attr.get("numeric_values")
- })
-
- return attribute
-
-def set_new_attribute_values(item_attr, values):
- for attr_value in values:
- if not any((d.abbr.lower() == attr_value.lower() or d.attribute_value.lower() == attr_value.lower())\
- for d in item_attr.item_attribute_values):
- item_attr.append("item_attribute_values", {
- "attribute_value": attr_value,
- "abbr": attr_value
- })
-
-def create_item(shopify_item, warehouse, has_variant=0, attributes=None,variant_of=None):
- item_dict = {
- "doctype": "Item",
- "shopify_product_id": shopify_item.get("id"),
- "shopify_variant_id": shopify_item.get("variant_id"),
- "variant_of": variant_of,
- "sync_with_shopify": 1,
- "is_stock_item": 1,
- "item_code": cstr(shopify_item.get("item_code")) or cstr(shopify_item.get("id")),
- "item_name": shopify_item.get("title", '').strip(),
- "description": shopify_item.get("body_html") or shopify_item.get("title"),
- "shopify_description": shopify_item.get("body_html") or shopify_item.get("title"),
- "item_group": get_item_group(shopify_item.get("product_type")),
- "has_variants": has_variant,
- "attributes":attributes or [],
- "stock_uom": shopify_item.get("uom") or _("Nos"),
- "stock_keeping_unit": shopify_item.get("sku") or get_sku(shopify_item),
- "default_warehouse": warehouse,
- "image": get_item_image(shopify_item),
- "weight_uom": shopify_item.get("weight_unit"),
- "weight_per_unit": shopify_item.get("weight"),
- "default_supplier": get_supplier(shopify_item),
- "item_defaults": [
- {
- "company": get_default_company()
- }
- ]
- }
-
- if not is_item_exists(item_dict, attributes, variant_of=variant_of):
- item_details = get_item_details(shopify_item)
- name = ''
-
- if not item_details:
- new_item = frappe.get_doc(item_dict)
- new_item.insert(ignore_permissions=True, ignore_mandatory=True)
- name = new_item.name
-
- if not name:
- name = item_details.name
-
- if not has_variant:
- add_to_price_list(shopify_item, name)
-
- frappe.db.commit()
-
-def create_item_variants(shopify_item, warehouse, attributes, shopify_variants_attr_list):
- template_item = frappe.db.get_value("Item", filters={"shopify_product_id": shopify_item.get("id")},
- fieldname=["name", "stock_uom"], as_dict=True)
-
- if template_item:
- for variant in shopify_item.get("variants"):
- shopify_item_variant = {
- "id" : variant.get("id"),
- "item_code": variant.get("id"),
- "title": variant.get("title"),
- "product_type": shopify_item.get("product_type"),
- "sku": variant.get("sku"),
- "uom": template_item.stock_uom or _("Nos"),
- "item_price": variant.get("price"),
- "variant_id": variant.get("id"),
- "weight_unit": variant.get("weight_unit"),
- "weight": variant.get("weight")
- }
-
- for i, variant_attr in enumerate(shopify_variants_attr_list):
- if variant.get(variant_attr):
- attributes[i].update({"attribute_value": get_attribute_value(variant.get(variant_attr), attributes[i])})
- create_item(shopify_item_variant, warehouse, 0, attributes, template_item.name)
-
-def get_attribute_value(variant_attr_val, attribute):
- attribute_value = frappe.db.sql("""select attribute_value from `tabItem Attribute Value`
- where parent = %s and (abbr = %s or attribute_value = %s)""", (attribute["attribute"], variant_attr_val,
- variant_attr_val), as_list=1)
- return attribute_value[0][0] if len(attribute_value)>0 else cint(variant_attr_val)
-
-def get_item_group(product_type=None):
- import frappe.utils.nestedset
- parent_item_group = frappe.utils.nestedset.get_root_of("Item Group")
-
- if product_type:
- if not frappe.db.get_value("Item Group", product_type, "name"):
- item_group = frappe.get_doc({
- "doctype": "Item Group",
- "item_group_name": product_type,
- "parent_item_group": parent_item_group,
- "is_group": "No"
- }).insert()
- return item_group.name
- else:
- return product_type
- else:
- return parent_item_group
-
-
-def get_sku(item):
- if item.get("variants"):
- return item.get("variants")[0].get("sku")
- return ""
-
-def add_to_price_list(item, name):
- shopify_settings = frappe.db.get_value("Shopify Settings", None, ["price_list", "update_price_in_erpnext_price_list"], as_dict=1)
- if not shopify_settings.update_price_in_erpnext_price_list:
- return
-
- item_price_name = frappe.db.get_value("Item Price",
- {"item_code": name, "price_list": shopify_settings.price_list}, "name")
-
- if not item_price_name:
- frappe.get_doc({
- "doctype": "Item Price",
- "price_list": shopify_settings.price_list,
- "item_code": name,
- "price_list_rate": item.get("item_price") or item.get("variants")[0].get("price")
- }).insert()
- else:
- item_rate = frappe.get_doc("Item Price", item_price_name)
- item_rate.price_list_rate = item.get("item_price") or item.get("variants")[0].get("price")
- item_rate.save()
-
-def get_item_image(shopify_item):
- if shopify_item.get("image"):
- return shopify_item.get("image").get("src")
- return None
-
-def get_supplier(shopify_item):
- if shopify_item.get("vendor"):
- supplier = frappe.db.sql("""select name from tabSupplier
- where name = %s or shopify_supplier_id = %s """, (shopify_item.get("vendor"),
- shopify_item.get("vendor").lower()), as_list=1)
-
- if not supplier:
- supplier = frappe.get_doc({
- "doctype": "Supplier",
- "supplier_name": shopify_item.get("vendor"),
- "shopify_supplier_id": shopify_item.get("vendor").lower(),
- "supplier_group": get_supplier_group()
- }).insert()
- return supplier.name
- else:
- return shopify_item.get("vendor")
- else:
- return ""
-
-def get_supplier_group():
- supplier_group = frappe.db.get_value("Supplier Group", _("Shopify Supplier"))
- if not supplier_group:
- supplier_group = frappe.get_doc({
- "doctype": "Supplier Group",
- "supplier_group_name": _("Shopify Supplier")
- }).insert()
- return supplier_group.name
- return supplier_group
-
-def get_item_details(shopify_item):
- item_details = {}
-
- item_details = frappe.db.get_value("Item", {"shopify_product_id": shopify_item.get("id")},
- ["name", "stock_uom", "item_name"], as_dict=1)
-
- if item_details:
- return item_details
-
- else:
- item_details = frappe.db.get_value("Item", {"shopify_variant_id": shopify_item.get("id")},
- ["name", "stock_uom", "item_name"], as_dict=1)
- return item_details
-
-def is_item_exists(shopify_item, attributes=None, variant_of=None):
- if variant_of:
- name = variant_of
- else:
- name = frappe.db.get_value("Item", {"item_name": shopify_item.get("item_name")})
-
- if name:
- item = frappe.get_doc("Item", name)
- item.flags.ignore_mandatory=True
-
- if not variant_of and not item.shopify_product_id:
- item.shopify_product_id = shopify_item.get("shopify_product_id")
- item.shopify_variant_id = shopify_item.get("shopify_variant_id")
- item.save()
- return True
-
- if item.shopify_product_id and attributes and attributes[0].get("attribute_value"):
- if not variant_of:
- variant_of = frappe.db.get_value("Item",
- {"shopify_product_id": item.shopify_product_id}, "variant_of")
-
- # create conditions for all item attributes,
- # as we are putting condition basis on OR it will fetch all items matching either of conditions
- # thus comparing matching conditions with len(attributes)
- # which will give exact matching variant item.
-
- conditions = ["(iv.attribute='{0}' and iv.attribute_value = '{1}')"\
- .format(attr.get("attribute"), attr.get("attribute_value")) for attr in attributes]
-
- conditions = "( {0} ) and iv.parent = it.name ) = {1}".format(" or ".join(conditions), len(attributes))
-
- parent = frappe.db.sql(""" select * from tabItem it where
- ( select count(*) from `tabItem Variant Attribute` iv
- where {conditions} and it.variant_of = %s """.format(conditions=conditions) ,
- variant_of, as_list=1)
-
- if parent:
- variant = frappe.get_doc("Item", parent[0][0])
- variant.flags.ignore_mandatory = True
-
- variant.shopify_product_id = shopify_item.get("shopify_product_id")
- variant.shopify_variant_id = shopify_item.get("shopify_variant_id")
- variant.save()
- return False
-
- if item.shopify_product_id and item.shopify_product_id != shopify_item.get("shopify_product_id"):
- return False
-
- return True
-
- else:
- return False
diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/test_data/custom_field.json b/erpnext/erpnext_integrations/doctype/shopify_settings/test_data/custom_field.json
deleted file mode 100644
index db6c3d5..0000000
--- a/erpnext/erpnext_integrations/doctype/shopify_settings/test_data/custom_field.json
+++ /dev/null
@@ -1,527 +0,0 @@
-[
- {
- "allow_on_submit": 0,
- "collapsible": 0,
- "collapsible_depends_on": null,
- "default": null,
- "depends_on": null,
- "description": null,
- "docstatus": 0,
- "doctype": "Custom Field",
- "dt": "Print Settings",
- "fieldname": "compact_item_print",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "insert_after": "with_letterhead",
- "label": "Compact Item Print",
- "modified": "2016-06-06 15:18:17.025602",
- "name": "Print Settings-compact_item_print",
- "no_copy": 0,
- "options": null,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "print_width": null,
- "read_only": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "unique": 0,
- "width": null
- },
- {
- "allow_on_submit": 0,
- "collapsible": 0,
- "collapsible_depends_on": null,
- "default": null,
- "depends_on": null,
- "description": null,
- "docstatus": 0,
- "doctype": "Custom Field",
- "dt": "Customer",
- "fieldname": "shopify_customer_id",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "insert_after": "naming_series",
- "label": "Shopify Customer Id",
- "modified": "2016-01-15 17:25:28.991818",
- "name": "Customer-shopify_customer_id",
- "no_copy": 1,
- "options": null,
- "permlevel": 0,
- "precision": "",
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "print_width": null,
- "read_only": 1,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "unique": 0,
- "width": null
- },
- {
- "allow_on_submit": 0,
- "collapsible": 0,
- "collapsible_depends_on": null,
- "default": null,
- "depends_on": null,
- "description": null,
- "docstatus": 0,
- "doctype": "Custom Field",
- "dt": "Address",
- "fieldname": "shopify_address_id",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "insert_after": "fax",
- "label": "Shopify Address Id",
- "modified": "2016-01-15 17:50:52.213743",
- "name": "Address-shopify_address_id",
- "no_copy": 1,
- "options": null,
- "permlevel": 0,
- "precision": "",
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "print_width": null,
- "read_only": 1,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "unique": 0,
- "width": null
- },
- {
- "allow_on_submit": 0,
- "collapsible": 0,
- "collapsible_depends_on": null,
- "default": null,
- "depends_on": null,
- "description": null,
- "docstatus": 0,
- "doctype": "Custom Field",
- "dt": "Sales Order",
- "fieldname": "shopify_order_id",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "insert_after": "title",
- "label": "Shopify Order Id",
- "modified": "2016-01-18 09:55:50.764524",
- "name": "Sales Order-shopify_order_id",
- "no_copy": 1,
- "options": null,
- "permlevel": 0,
- "precision": "",
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "print_width": null,
- "read_only": 1,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "unique": 0,
- "width": null
- },
- {
- "allow_on_submit": 0,
- "collapsible": 0,
- "collapsible_depends_on": null,
- "default": null,
- "depends_on": null,
- "description": null,
- "docstatus": 0,
- "doctype": "Custom Field",
- "dt": "Item",
- "fieldname": "shopify_product_id",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "insert_after": "item_code",
- "label": "Shopify Product Id",
- "modified": "2016-01-19 15:44:16.132952",
- "name": "Item-shopify_product_id",
- "no_copy": 1,
- "options": null,
- "permlevel": 0,
- "precision": "",
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "print_width": null,
- "read_only": 1,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "unique": 0,
- "width": null
- },
- {
- "allow_on_submit": 0,
- "collapsible": 0,
- "collapsible_depends_on": null,
- "default": null,
- "depends_on": null,
- "description": null,
- "docstatus": 0,
- "doctype": "Custom Field",
- "dt": "Sales Invoice",
- "fieldname": "shopify_order_id",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "insert_after": "naming_series",
- "label": "Shopify Order Id",
- "modified": "2016-01-19 16:30:12.261797",
- "name": "Sales Invoice-shopify_order_id",
- "no_copy": 1,
- "options": null,
- "permlevel": 0,
- "precision": "",
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "print_width": null,
- "read_only": 1,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "unique": 0,
- "width": null
- },
- {
- "allow_on_submit": 0,
- "collapsible": 0,
- "collapsible_depends_on": null,
- "default": null,
- "depends_on": null,
- "description": null,
- "docstatus": 0,
- "doctype": "Custom Field",
- "dt": "Delivery Note",
- "fieldname": "shopify_order_id",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "insert_after": "title",
- "label": "Shopify Order Id",
- "modified": "2016-01-19 16:30:31.201198",
- "name": "Delivery Note-shopify_order_id",
- "no_copy": 1,
- "options": null,
- "permlevel": 0,
- "precision": "",
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "print_width": null,
- "read_only": 1,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "unique": 0,
- "width": null
- },
- {
- "allow_on_submit": 0,
- "collapsible": 0,
- "collapsible_depends_on": null,
- "default": null,
- "depends_on": null,
- "description": null,
- "docstatus": 0,
- "doctype": "Custom Field",
- "dt": "Item",
- "fieldname": "stock_keeping_unit",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "insert_after": "stock_uom",
- "label": "Stock Keeping Unit",
- "modified": "2015-11-10 09:29:10.854943",
- "name": "Item-stock_keeping_unit",
- "no_copy": 1,
- "options": null,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "print_width": null,
- "read_only": 1,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "unique": 0,
- "width": null
- },
- {
- "allow_on_submit": 0,
- "collapsible": 0,
- "collapsible_depends_on": null,
- "default": "0",
- "depends_on": null,
- "description": null,
- "docstatus": 0,
- "doctype": "Custom Field",
- "dt": "Item",
- "fieldname": "sync_with_shopify",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "insert_after": "is_stock_item",
- "label": "Sync With Shopify",
- "modified": "2015-10-12 15:54:31.997714",
- "name": "Item-sync_with_shopify",
- "no_copy": 0,
- "options": null,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "print_width": null,
- "read_only": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "unique": 0,
- "width": null
- },
- {
- "allow_on_submit": 0,
- "collapsible": 0,
- "collapsible_depends_on": null,
- "default": null,
- "depends_on": null,
- "description": null,
- "docstatus": 0,
- "doctype": "Custom Field",
- "dt": "Customer",
- "fieldname": "sync_with_shopify",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "insert_after": "is_frozen",
- "label": "Sync With Shopify",
- "modified": "2015-10-01 17:31:55.758826",
- "name": "Customer-sync_with_shopify",
- "no_copy": 0,
- "options": null,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "print_width": null,
- "read_only": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "unique": 0,
- "width": null
- },
- {
- "allow_on_submit": 0,
- "collapsible": 0,
- "collapsible_depends_on": null,
- "default": null,
- "depends_on": null,
- "description": null,
- "docstatus": 0,
- "doctype": "Custom Field",
- "dt": "Item",
- "fieldname": "shopify_variant_id",
- "fieldtype": "Data",
- "hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "insert_after": "item_code",
- "label": "Variant Id",
- "modified": "2015-11-09 18:26:50.825858",
- "name": "Item-shopify_variant_id",
- "no_copy": 1,
- "options": null,
- "permlevel": 0,
- "precision": "",
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "print_width": null,
- "read_only": 1,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "unique": 0,
- "width": null
- },
- {
- "allow_on_submit": 0,
- "collapsible": 0,
- "collapsible_depends_on": null,
- "default": null,
- "depends_on": null,
- "description": null,
- "docstatus": 0,
- "doctype": "Custom Field",
- "dt": "Item",
- "fieldname": "sync_qty_with_shopify",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "insert_after": "item_code",
- "label": "Sync Quantity With Shopify",
- "modified": "2015-12-29 08:37:46.183295",
- "name": "Item-sync_qty_with_shopify",
- "no_copy": 0,
- "options": null,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "print_width": null,
- "read_only": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "unique": 0,
- "width": null
- },
- {
- "allow_on_submit": 0,
- "collapsible": 0,
- "collapsible_depends_on": null,
- "default": null,
- "depends_on": null,
- "description": null,
- "docstatus": 0,
- "doctype": "Custom Field",
- "dt": "Delivery Note",
- "fieldname": "shopify_fulfillment_id",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "insert_after": "title",
- "label": "Shopify Fulfillment Id",
- "modified": "2016-01-20 23:50:35.609543",
- "name": "Delivery Note-shopify_fulfillment_id",
- "no_copy": 1,
- "options": null,
- "permlevel": 0,
- "precision": "",
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "print_width": null,
- "read_only": 1,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "unique": 0,
- "width": null
- },
- {
- "allow_on_submit": 0,
- "collapsible": 0,
- "collapsible_depends_on": null,
- "default": null,
- "depends_on": null,
- "description": null,
- "docstatus": 0,
- "doctype": "Custom Field",
- "dt": "Supplier",
- "fieldname": "shopify_supplier_id",
- "fieldtype": "Data",
- "hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "insert_after": "supplier_name",
- "label": "Shopify Supplier Id",
- "modified": "2016-02-01 15:41:25.818306",
- "name": "Supplier-shopify_supplier_id",
- "no_copy": 1,
- "options": null,
- "permlevel": 0,
- "precision": "",
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "print_width": null,
- "read_only": 1,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "unique": 0,
- "width": null
- },
- {
- "allow_on_submit": 0,
- "collapsible": 0,
- "collapsible_depends_on": null,
- "default": null,
- "depends_on": null,
- "description": null,
- "docstatus": 0,
- "doctype": "Custom Field",
- "dt": "Item",
- "fieldname": "shopify_description",
- "fieldtype": "Text Editor",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "insert_after": "section_break_11",
- "label": "shopify_description",
- "modified": "2016-06-15 12:15:36.325581",
- "name": "Item-shopify_description",
- "no_copy": 0,
- "options": null,
- "permlevel": 0,
- "precision": "",
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "print_width": null,
- "read_only": 0,
- "report_hide": 1,
- "reqd": 0,
- "search_index": 0,
- "unique": 0,
- "width": null
- }
-]
\ No newline at end of file
diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/test_data/shopify_customer.json b/erpnext/erpnext_integrations/doctype/shopify_settings/test_data/shopify_customer.json
deleted file mode 100644
index e91ce9a..0000000
--- a/erpnext/erpnext_integrations/doctype/shopify_settings/test_data/shopify_customer.json
+++ /dev/null
@@ -1,59 +0,0 @@
-{
- "customer": {
- "id": 2324518599,
- "email": "andrew@wyatt.co.in",
- "accepts_marketing": false,
- "created_at": "2016-01-20T17:18:35+05:30",
- "updated_at": "2016-01-20T17:22:23+05:30",
- "first_name": "Andrew",
- "last_name": "Wyatt",
- "orders_count": 0,
- "state": "disabled",
- "total_spent": "0.00",
- "last_order_id": null,
- "note": "",
- "verified_email": true,
- "multipass_identifier": null,
- "tax_exempt": false,
- "tags": "",
- "last_order_name": null,
- "default_address": {
- "id": 2476804295,
- "first_name": "Andrew",
- "last_name": "Wyatt",
- "company": "Wyatt Inc.",
- "address1": "B-11, Betahouse",
- "address2": "Street 11, Sector 52",
- "city": "Manhattan",
- "province": "New York",
- "country": "United States",
- "zip": "10027",
- "phone": "145-112211",
- "name": "Andrew Wyatt",
- "province_code": "NY",
- "country_code": "US",
- "country_name": "United States",
- "default": true
- },
- "addresses": [
- {
- "id": 2476804295,
- "first_name": "Andrew",
- "last_name": "Wyatt",
- "company": "Wyatt Inc.",
- "address1": "B-11, Betahouse",
- "address2": "Street 11, Sector 52",
- "city": "Manhattan",
- "province": "New York",
- "country": "United States",
- "zip": "10027",
- "phone": "145-112211",
- "name": "Andrew Wyatt",
- "province_code": "NY",
- "country_code": "US",
- "country_name": "United States",
- "default": true
- }
- ]
- }
-}
\ No newline at end of file
diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/test_data/shopify_item.json b/erpnext/erpnext_integrations/doctype/shopify_settings/test_data/shopify_item.json
deleted file mode 100644
index 296dede..0000000
--- a/erpnext/erpnext_integrations/doctype/shopify_settings/test_data/shopify_item.json
+++ /dev/null
@@ -1,125 +0,0 @@
-{
- "product": {
- "id": 4059739520,
- "title": "Shopify Test Item",
- "body_html": "<div>Hold back Spin Medallion-Set of 2</div>\n<div></div>\n<div>Finish: Plated/ Powder Coated</div>\n<div>Material: Iron</div>\n<div>Color Finish: Satin Silver, Brown Oil Rubbed, Roman Bronze</div>\n<div>Qty: 1 Set</div>",
- "vendor": "Boa casa",
- "product_type": "Curtain Accessories",
- "created_at": "2016-01-18T17:16:37+05:30",
- "handle": "1001624-01",
- "updated_at": "2016-01-20T17:26:44+05:30",
- "published_at": "2016-01-18T17:16:37+05:30",
- "template_suffix": null,
- "published_scope": "global",
- "tags": "Category_Curtain Accessories, Type_Holdback",
- "variants": [{
- "id": 13917612359,
- "product_id": 4059739520,
- "title": "Test BALCK Item",
- "price": "499.00",
- "sku": "",
- "position": 1,
- "grams": 0,
- "inventory_policy": "continue",
- "compare_at_price": null,
- "fulfillment_service": "manual",
- "inventory_management": "shopify",
- "option1": "BLACK",
- "option2": null,
- "option3": null,
- "created_at": "2016-01-18T17:16:37+05:30",
- "updated_at": "2016-01-20T17:26:44+05:30",
- "requires_shipping": true,
- "taxable": true,
- "barcode": "",
- "inventory_quantity": -1,
- "old_inventory_quantity": -1,
- "image_id": 8539321735,
- "weight": 0,
- "weight_unit": "kg"
- }, {
- "id": 13917612423,
- "product_id": 4059739520,
- "title": "Test BLUE Item",
- "price": "499.00",
- "sku": "",
- "position": 2,
- "grams": 0,
- "inventory_policy": "continue",
- "compare_at_price": null,
- "fulfillment_service": "manual",
- "inventory_management": "shopify",
- "option1": "BLUE",
- "option2": null,
- "option3": null,
- "created_at": "2016-01-18T17:16:37+05:30",
- "updated_at": "2016-01-20T17:26:44+05:30",
- "requires_shipping": true,
- "taxable": true,
- "barcode": "",
- "inventory_quantity": -1,
- "old_inventory_quantity": -1,
- "image_id": null,
- "weight": 0,
- "weight_unit": "kg"
- }, {
- "id": 13917612487,
- "product_id": 4059739520,
- "title": "Test White Item",
- "price": "499.00",
- "sku": "",
- "position": 3,
- "grams": 0,
- "inventory_policy": "continue",
- "compare_at_price": null,
- "fulfillment_service": "manual",
- "inventory_management": "shopify",
- "option1": "White",
- "option2": null,
- "option3": null,
- "created_at": "2016-01-18T17:16:37+05:30",
- "updated_at": "2016-01-18T17:16:37+05:30",
- "requires_shipping": true,
- "taxable": true,
- "barcode": "",
- "inventory_quantity": 0,
- "old_inventory_quantity": 0,
- "image_id": null,
- "weight": 0,
- "weight_unit": "kg"
- }],
- "options": [{
- "id": 4985027399,
- "product_id": 4059739520,
- "name": "Colour",
- "position": 1,
- "values": [
- "BLACK",
- "BLUE",
- "White"
- ]
- }],
- "images": [{
- "id": 8539321735,
- "product_id": 4059739520,
- "position": 1,
- "created_at": "2016-01-18T17:16:37+05:30",
- "updated_at": "2016-01-18T17:16:37+05:30",
- "src": "https://cdn.shopify.com/s/files/1/1123/0654/products/2015-12-17_6.png?v=1453117597",
- "variant_ids": [
- 13917612359
- ]
- }],
- "image": {
- "id": 8539321735,
- "product_id": 4059739520,
- "position": 1,
- "created_at": "2016-01-18T17:16:37+05:30",
- "updated_at": "2016-01-18T17:16:37+05:30",
- "src": "https://cdn.shopify.com/s/files/1/1123/0654/products/2015-12-17_6.png?v=1453117597",
- "variant_ids": [
- 13917612359
- ]
- }
- }
-}
\ No newline at end of file
diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/test_data/shopify_order.json b/erpnext/erpnext_integrations/doctype/shopify_settings/test_data/shopify_order.json
deleted file mode 100644
index 988a2f0..0000000
--- a/erpnext/erpnext_integrations/doctype/shopify_settings/test_data/shopify_order.json
+++ /dev/null
@@ -1,270 +0,0 @@
-{
- "order": {
- "id": 2414345735,
- "email": "andrew@wyatt.co.in",
- "closed_at": null,
- "created_at": "2016-01-20T17:26:39+05:30",
- "updated_at": "2016-01-20T17:27:15+05:30",
- "number": 5,
- "note": "",
- "token": "660fed25987517b733644a8c9ec7c8e0",
- "gateway": "manual",
- "test": false,
- "total_price": "1018.00",
- "subtotal_price": "998.00",
- "total_weight": 0,
- "total_tax": "0.00",
- "taxes_included": false,
- "currency": "INR",
- "financial_status": "paid",
- "confirmed": true,
- "total_discounts": "0.00",
- "total_line_items_price": "998.00",
- "cart_token": null,
- "buyer_accepts_marketing": false,
- "name": "#1005",
- "referring_site": null,
- "landing_site": null,
- "cancelled_at": null,
- "cancel_reason": null,
- "total_price_usd": "15.02",
- "checkout_token": null,
- "reference": null,
- "user_id": 55391175,
- "location_id": null,
- "source_identifier": null,
- "source_url": null,
- "processed_at": "2016-01-20T17:26:39+05:30",
- "device_id": null,
- "browser_ip": null,
- "landing_site_ref": null,
- "order_number": 1005,
- "discount_codes": [],
- "note_attributes": [],
- "payment_gateway_names": [
- "manual"
- ],
- "processing_method": "manual",
- "checkout_id": null,
- "source_name": "shopify_draft_order",
- "fulfillment_status": "fulfilled",
- "tax_lines": [],
- "tags": "",
- "contact_email": "andrew@wyatt.co.in",
- "line_items": [
- {
- "id": 4125768135,
- "variant_id": 13917612359,
- "title": "Shopify Test Item",
- "quantity": 1,
- "price": "499.00",
- "grams": 0,
- "sku": "",
- "variant_title": "Roman BALCK 1",
- "vendor": "Boa casa",
- "fulfillment_service": "manual",
- "product_id": 4059739527,
- "requires_shipping": true,
- "taxable": true,
- "gift_card": false,
- "name": "Roman BALCK 1",
- "variant_inventory_management": "shopify",
- "properties": [],
- "product_exists": true,
- "fulfillable_quantity": 0,
- "total_discount": "0.00",
- "fulfillment_status": "fulfilled",
- "tax_lines": []
- },
- {
- "id": 4125768199,
- "variant_id": 13917612423,
- "title": "Shopify Test Item",
- "quantity": 1,
- "price": "499.00",
- "grams": 0,
- "sku": "",
- "variant_title": "Satin BLUE 1",
- "vendor": "Boa casa",
- "fulfillment_service": "manual",
- "product_id": 4059739527,
- "requires_shipping": true,
- "taxable": true,
- "gift_card": false,
- "name": "Satin BLUE 1",
- "variant_inventory_management": "shopify",
- "properties": [],
- "product_exists": true,
- "fulfillable_quantity": 0,
- "total_discount": "0.00",
- "fulfillment_status": "fulfilled",
- "tax_lines": []
- }
- ],
- "shipping_lines": [
- {
- "id": 2108906247,
- "title": "International Shipping",
- "price": "20.00",
- "code": "International Shipping",
- "source": "shopify",
- "phone": null,
- "tax_lines": []
- }
- ],
- "billing_address": {
- "first_name": "Andrew",
- "address1": "B-11, Betahouse",
- "phone": "145-112211",
- "city": "Manhattan",
- "zip": "10027",
- "province": "New York",
- "country": "United States",
- "last_name": "Wyatt",
- "address2": "Street 11, Sector 52",
- "company": "Wyatt Inc.",
- "latitude": 40.8138912,
- "longitude": -73.96243270000001,
- "name": "Andrew Wyatt",
- "country_code": "US",
- "province_code": "NY"
- },
- "shipping_address": {
- "first_name": "Andrew",
- "address1": "B-11, Betahouse",
- "phone": "145-112211",
- "city": "Manhattan",
- "zip": "10027",
- "province": "New York",
- "country": "United States",
- "last_name": "Wyatt",
- "address2": "Street 11, Sector 52",
- "company": "Wyatt Inc.",
- "latitude": 40.8138912,
- "longitude": -73.96243270000001,
- "name": "Andrew Wyatt",
- "country_code": "US",
- "province_code": "NY"
- },
- "fulfillments": [
- {
- "id": 1849629255,
- "order_id": 2414345735,
- "status": "success",
- "created_at": "2016-01-20T17:27:15+05:30",
- "service": "manual",
- "updated_at": "2016-01-20T17:27:15+05:30",
- "tracking_company": null,
- "tracking_number": null,
- "tracking_numbers": [],
- "tracking_url": null,
- "tracking_urls": [],
- "receipt": {},
- "line_items": [
- {
- "id": 4125768199,
- "variant_id": 13917612423,
- "title": "1001624/01",
- "quantity": 1,
- "price": "499.00",
- "grams": 0,
- "sku": "",
- "variant_title": "Satin Silver",
- "vendor": "Boa casa",
- "fulfillment_service": "manual",
- "product_id": 4059739527,
- "requires_shipping": true,
- "taxable": true,
- "gift_card": false,
- "name": "1001624/01 - Satin Silver",
- "variant_inventory_management": "shopify",
- "properties": [],
- "product_exists": true,
- "fulfillable_quantity": 0,
- "total_discount": "0.00",
- "fulfillment_status": "fulfilled",
- "tax_lines": []
- }
- ]
- },
- {
- "id": 1849628167,
- "order_id": 2414345735,
- "status": "success",
- "created_at": "2016-01-20T17:26:58+05:30",
- "service": "manual",
- "updated_at": "2016-01-20T17:26:58+05:30",
- "tracking_company": null,
- "tracking_number": null,
- "tracking_numbers": [],
- "tracking_url": null,
- "tracking_urls": [],
- "receipt": {},
- "line_items": [
- {
- "id": 4125768135,
- "variant_id": 13917612359,
- "title": "1001624/01",
- "quantity": 1,
- "price": "499.00",
- "grams": 0,
- "sku": "",
- "variant_title": "Roman Bronze",
- "vendor": "Boa casa",
- "fulfillment_service": "manual",
- "product_id": 4059739527,
- "requires_shipping": true,
- "taxable": true,
- "gift_card": false,
- "name": "1001624/01 - Roman Bronze",
- "variant_inventory_management": "shopify",
- "properties": [],
- "product_exists": true,
- "fulfillable_quantity": 0,
- "total_discount": "0.00",
- "fulfillment_status": "fulfilled",
- "tax_lines": []
- }
- ]
- }
- ],
- "refunds": [],
- "customer": {
- "id": 2324518599,
- "email": "andrew@wyatt.co.in",
- "accepts_marketing": false,
- "created_at": "2016-01-20T17:18:35+05:30",
- "updated_at": "2016-01-20T17:26:39+05:30",
- "first_name": "Andrew",
- "last_name": "Wyatt",
- "orders_count": 1,
- "state": "disabled",
- "total_spent": "1018.00",
- "last_order_id": 2414345735,
- "note": "",
- "verified_email": true,
- "multipass_identifier": null,
- "tax_exempt": false,
- "tags": "",
- "last_order_name": "#1005",
- "default_address": {
- "id": 2476804295,
- "first_name": "Andrew",
- "last_name": "Wyatt",
- "company": "Wyatt Inc.",
- "address1": "B-11, Betahouse",
- "address2": "Street 11, Sector 52",
- "city": "Manhattan",
- "province": "New York",
- "country": "United States",
- "zip": "10027",
- "phone": "145-112211",
- "name": "Andrew Wyatt",
- "province_code": "NY",
- "country_code": "US",
- "country_name": "United States",
- "default": true
- }
- }
- }
-}
\ No newline at end of file
diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/test_shopify_settings.js b/erpnext/erpnext_integrations/doctype/shopify_settings/test_shopify_settings.js
deleted file mode 100644
index b2f82d5..0000000
--- a/erpnext/erpnext_integrations/doctype/shopify_settings/test_shopify_settings.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Shopify Settings", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Shopify Settings
- () => frappe.tests.make('Shopify Settings', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/test_shopify_settings.py b/erpnext/erpnext_integrations/doctype/shopify_settings/test_shopify_settings.py
deleted file mode 100644
index 6bec301..0000000
--- a/erpnext/erpnext_integrations/doctype/shopify_settings/test_shopify_settings.py
+++ /dev/null
@@ -1,107 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-from __future__ import unicode_literals
-import frappe
-
-import unittest, os, json
-from frappe.utils import cstr, cint
-from erpnext.erpnext_integrations.connectors.shopify_connection import create_order
-from erpnext.erpnext_integrations.doctype.shopify_settings.sync_product import make_item
-from erpnext.erpnext_integrations.doctype.shopify_settings.sync_customer import create_customer
-from frappe.core.doctype.data_import.data_import import import_doc
-
-
-class ShopifySettings(unittest.TestCase):
- @classmethod
- def setUpClass(cls):
- frappe.set_user("Administrator")
-
- cls.allow_negative_stock = cint(frappe.db.get_value('Stock Settings', None, 'allow_negative_stock'))
- if not cls.allow_negative_stock:
- frappe.db.set_value('Stock Settings', None, 'allow_negative_stock', 1)
-
- # use the fixture data
- import_doc(path=frappe.get_app_path("erpnext", "erpnext_integrations/doctype/shopify_settings/test_data/custom_field.json"))
-
- frappe.reload_doctype("Customer")
- frappe.reload_doctype("Sales Order")
- frappe.reload_doctype("Delivery Note")
- frappe.reload_doctype("Sales Invoice")
-
- cls.setup_shopify()
-
- @classmethod
- def tearDownClass(cls):
- if not cls.allow_negative_stock:
- frappe.db.set_value('Stock Settings', None, 'allow_negative_stock', 0)
-
- @classmethod
- def setup_shopify(cls):
- shopify_settings = frappe.get_doc("Shopify Settings")
- shopify_settings.taxes = []
-
- shopify_settings.update({
- "app_type": "Private",
- "shopify_url": "test.myshopify.com",
- "api_key": "17702c7c4452b9c5d235240b6e7a39da",
- "password": "17702c7c4452b9c5d235240b6e7a39da",
- "shared_secret": "17702c7c4452b9c5d235240b6e7a39da",
- "price_list": "_Test Price List",
- "warehouse": "_Test Warehouse - _TC",
- "cash_bank_account": "Cash - _TC",
- "account": "Cash - _TC",
- "customer_group": "_Test Customer Group",
- "cost_center": "Main - _TC",
- "taxes": [
- {
- "shopify_tax": "International Shipping",
- "tax_account":"Legal Expenses - _TC"
- }
- ],
- "enable_shopify": 0,
- "sales_order_series": "SO-",
- "sync_sales_invoice": 1,
- "sales_invoice_series": "SINV-",
- "sync_delivery_note": 1,
- "delivery_note_series": "DN-"
- }).save(ignore_permissions=True)
-
- cls.shopify_settings = shopify_settings
-
- def test_order(self):
- # Create Customer
- with open (os.path.join(os.path.dirname(__file__), "test_data", "shopify_customer.json")) as shopify_customer:
- shopify_customer = json.load(shopify_customer)
- create_customer(shopify_customer.get("customer"), self.shopify_settings)
-
- # Create Item
- with open (os.path.join(os.path.dirname(__file__), "test_data", "shopify_item.json")) as shopify_item:
- shopify_item = json.load(shopify_item)
- make_item("_Test Warehouse - _TC", shopify_item.get("product"))
-
- # Create Order
- with open (os.path.join(os.path.dirname(__file__), "test_data", "shopify_order.json")) as shopify_order:
- shopify_order = json.load(shopify_order)
-
- create_order(shopify_order.get("order"), self.shopify_settings, False, company="_Test Company")
-
- sales_order = frappe.get_doc("Sales Order", {"shopify_order_id": cstr(shopify_order.get("order").get("id"))})
-
- self.assertEqual(cstr(shopify_order.get("order").get("id")), sales_order.shopify_order_id)
-
- # Check for customer
- shopify_order_customer_id = cstr(shopify_order.get("order").get("customer").get("id"))
- sales_order_customer_id = frappe.get_value("Customer", sales_order.customer, "shopify_customer_id")
-
- self.assertEqual(shopify_order_customer_id, sales_order_customer_id)
-
- # Check sales invoice
- sales_invoice = frappe.get_doc("Sales Invoice", {"shopify_order_id": sales_order.shopify_order_id})
- self.assertEqual(sales_invoice.rounded_total, sales_order.rounded_total)
-
- # Check delivery note
- delivery_note_count = frappe.db.sql("""select count(*) from `tabDelivery Note`
- where shopify_order_id = %s""", sales_order.shopify_order_id)[0][0]
-
- self.assertEqual(delivery_note_count, len(shopify_order.get("order").get("fulfillments")))
diff --git a/erpnext/erpnext_integrations/doctype/shopify_tax_account/shopify_tax_account.json b/erpnext/erpnext_integrations/doctype/shopify_tax_account/shopify_tax_account.json
deleted file mode 100644
index 63c674c..0000000
--- a/erpnext/erpnext_integrations/doctype/shopify_tax_account/shopify_tax_account.json
+++ /dev/null
@@ -1,133 +0,0 @@
-{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2015-10-05 16:55:20.455371",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 0,
- "fields": [
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "shopify_tax",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Shopify Tax/Shipping Title",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_2",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "tax_account",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "ERPNext Account",
- "length": 0,
- "no_copy": 0,
- "options": "Account",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- }
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2018-04-09 11:36:49.272815",
- "modified_by": "Administrator",
- "module": "ERPNext Integrations",
- "name": "Shopify Tax Account",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 0,
- "track_seen": 0
-}
\ No newline at end of file
diff --git a/erpnext/erpnext_integrations/doctype/shopify_tax_account/shopify_tax_account.py b/erpnext/erpnext_integrations/doctype/shopify_tax_account/shopify_tax_account.py
deleted file mode 100644
index 74c13c0..0000000
--- a/erpnext/erpnext_integrations/doctype/shopify_tax_account/shopify_tax_account.py
+++ /dev/null
@@ -1,10 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-from __future__ import unicode_literals
-import frappe
-from frappe.model.document import Document
-
-class ShopifyTaxAccount(Document):
- pass
diff --git a/erpnext/erpnext_integrations/doctype/shopify_webhook_detail/__init__.py b/erpnext/erpnext_integrations/doctype/shopify_webhook_detail/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/erpnext_integrations/doctype/shopify_webhook_detail/__init__.py
+++ /dev/null
diff --git a/erpnext/erpnext_integrations/doctype/shopify_webhook_detail/shopify_webhook_detail.json b/erpnext/erpnext_integrations/doctype/shopify_webhook_detail/shopify_webhook_detail.json
deleted file mode 100644
index e47ecdc..0000000
--- a/erpnext/erpnext_integrations/doctype/shopify_webhook_detail/shopify_webhook_detail.json
+++ /dev/null
@@ -1,103 +0,0 @@
-{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2018-04-10 17:06:22.697427",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 0,
- "engine": "InnoDB",
- "fields": [
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "webhook_id",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Webhook ID",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "method",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Method",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- }
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2018-04-11 12:43:09.456449",
- "modified_by": "Administrator",
- "module": "ERPNext Integrations",
- "name": "Shopify Webhook Detail",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0
-}
\ No newline at end of file
diff --git a/erpnext/erpnext_integrations/doctype/shopify_webhook_detail/shopify_webhook_detail.py b/erpnext/erpnext_integrations/doctype/shopify_webhook_detail/shopify_webhook_detail.py
deleted file mode 100644
index e127989..0000000
--- a/erpnext/erpnext_integrations/doctype/shopify_webhook_detail/shopify_webhook_detail.py
+++ /dev/null
@@ -1,10 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-from __future__ import unicode_literals
-import frappe
-from frappe.model.document import Document
-
-class ShopifyWebhookDetail(Document):
- pass
diff --git a/erpnext/erpnext_integrations/workspace/erpnext_integrations/erpnext_integrations.json b/erpnext/erpnext_integrations/workspace/erpnext_integrations/erpnext_integrations.json
index 4a5e54e..24b8e48 100644
--- a/erpnext/erpnext_integrations/workspace/erpnext_integrations/erpnext_integrations.json
+++ b/erpnext/erpnext_integrations/workspace/erpnext_integrations/erpnext_integrations.json
@@ -41,16 +41,6 @@
"type": "Link"
},
{
- "dependencies": "",
- "hidden": 0,
- "is_query_report": 0,
- "label": "Shopify Settings",
- "link_to": "Shopify Settings",
- "link_type": "DocType",
- "onboard": 0,
- "type": "Link"
- },
- {
"hidden": 0,
"is_query_report": 0,
"label": "Payments",
@@ -113,4 +103,4 @@
"pin_to_bottom": 0,
"pin_to_top": 0,
"shortcuts": []
-}
\ No newline at end of file
+}
diff --git a/erpnext/erpnext_integrations/workspace/erpnext_integrations_settings/erpnext_integrations_settings.json b/erpnext/erpnext_integrations/workspace/erpnext_integrations_settings/erpnext_integrations_settings.json
index d258d57..d656b3c 100644
--- a/erpnext/erpnext_integrations/workspace/erpnext_integrations_settings/erpnext_integrations_settings.json
+++ b/erpnext/erpnext_integrations/workspace/erpnext_integrations_settings/erpnext_integrations_settings.json
@@ -34,16 +34,6 @@
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
- "label": "Shopify Settings",
- "link_to": "Shopify Settings",
- "link_type": "DocType",
- "onboard": 0,
- "type": "Link"
- },
- {
- "dependencies": "",
- "hidden": 0,
- "is_query_report": 0,
"label": "Amazon MWS Settings",
"link_to": "Amazon MWS Settings",
"link_type": "DocType",
@@ -79,4 +69,4 @@
"pin_to_bottom": 0,
"pin_to_top": 0,
"shortcuts": []
-}
\ No newline at end of file
+}
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index 59b011d..8f7c7db 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -341,7 +341,6 @@
"erpnext.projects.doctype.project.project.hourly_reminder",
"erpnext.projects.doctype.project.project.collect_project_status",
"erpnext.hr.doctype.shift_type.shift_type.process_auto_attendance_for_all_shifts",
- "erpnext.erpnext_integrations.connectors.shopify_connection.sync_old_orders",
"erpnext.support.doctype.service_level_agreement.service_level_agreement.set_service_level_agreement_variance"
],
"hourly_long": [
@@ -438,7 +437,8 @@
'erpnext.hr.utils.calculate_annual_eligible_hra_exemption': 'erpnext.regional.india.utils.calculate_annual_eligible_hra_exemption',
'erpnext.hr.utils.calculate_hra_exemption_for_period': 'erpnext.regional.india.utils.calculate_hra_exemption_for_period',
'erpnext.controllers.accounts_controller.validate_einvoice_fields': 'erpnext.regional.india.e_invoice.utils.validate_einvoice_fields',
- 'erpnext.assets.doctype.asset.asset.get_depreciation_amount': 'erpnext.regional.india.utils.get_depreciation_amount'
+ 'erpnext.assets.doctype.asset.asset.get_depreciation_amount': 'erpnext.regional.india.utils.get_depreciation_amount',
+ 'erpnext.stock.doctype.item.item.set_item_tax_from_hsn_code': 'erpnext.regional.india.utils.set_item_tax_from_hsn_code'
},
'United Arab Emirates': {
'erpnext.controllers.taxes_and_totals.update_itemised_tax_data': 'erpnext.regional.united_arab_emirates.utils.update_itemised_tax_data',
diff --git a/erpnext/loan_management/doctype/loan_application/loan_application.js b/erpnext/loan_management/doctype/loan_application/loan_application.js
index 017026c..5142178 100644
--- a/erpnext/loan_management/doctype/loan_application/loan_application.js
+++ b/erpnext/loan_management/doctype/loan_application/loan_application.js
@@ -14,7 +14,7 @@
refresh: function(frm) {
frm.trigger("toggle_fields");
frm.trigger("add_toolbar_buttons");
- frm.set_query("loan_type", () => {
+ frm.set_query('loan_type', () => {
return {
filters: {
company: frm.doc.company
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index c68198b..0ba8507 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -717,9 +717,8 @@
"ignore_conversion_rate": True
})
item_doc = frappe.get_cached_doc("Item", args.get("item_code"))
- out = frappe._dict()
- get_price_list_rate(bom_args, item_doc, out)
- rate = out.price_list_rate
+ price_list_data = get_price_list_rate(bom_args, item_doc)
+ rate = price_list_data.price_list_rate
return rate
@@ -748,7 +747,7 @@
if valuation_rate <= 0:
last_valuation_rate = frappe.db.sql("""select valuation_rate
from `tabStock Ledger Entry`
- where item_code = %s and valuation_rate > 0
+ where item_code = %s and valuation_rate > 0 and is_cancelled = 0
order by posting_date desc, posting_time desc, creation desc limit 1""", args['item_code'])
valuation_rate = flt(last_valuation_rate[0][0]) if last_valuation_rate else 0
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py
index 69c7f5c..66e2394 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/job_card.py
@@ -608,6 +608,11 @@
target.set_missing_values()
target.set_stock_entry_type()
+ wo_allows_alternate_item = frappe.db.get_value("Work Order", target.work_order, "allow_alternative_item")
+ for item in target.items:
+ item.allow_alternative_item = int(wo_allows_alternate_item and
+ frappe.get_cached_value("Item", item.item_code, "allow_alternative_item"))
+
doclist = get_mapped_doc("Job Card", source_name, {
"Job Card": {
"doctype": "Stock Entry",
@@ -698,4 +703,4 @@
}
}, target_doc, set_missing_values)
- return doclist
\ No newline at end of file
+ return doclist
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index a029627a..04f05ed 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -45,7 +45,6 @@
erpnext.patches.v11_0.make_asset_finance_book_against_old_entries
erpnext.patches.v11_0.check_buying_selling_in_currency_exchange
erpnext.patches.v11_0.move_item_defaults_to_child_table_for_multicompany #02-07-2018 #19-06-2019
-erpnext.patches.v11_0.refactor_erpnext_shopify #2018-09-07
erpnext.patches.v11_0.rename_overproduction_percent_field
erpnext.patches.v11_0.update_backflush_subcontract_rm_based_on_bom
erpnext.patches.v11_0.inter_state_field_for_gst
@@ -143,7 +142,6 @@
erpnext.patches.v12_0.rename_bank_account_field_in_journal_entry_account
erpnext.patches.v12_0.create_default_energy_point_rules
erpnext.patches.v12_0.set_produced_qty_field_in_sales_order_for_work_order
-erpnext.patches.v12_0.set_default_shopify_app_type
erpnext.patches.v12_0.set_cwip_and_delete_asset_settings
erpnext.patches.v12_0.set_expense_account_in_landed_cost_voucher_taxes
erpnext.patches.v12_0.replace_accounting_with_accounts_in_home_settings
@@ -244,7 +242,6 @@
erpnext.patches.v13_0.update_reason_for_resignation_in_employee
execute:frappe.delete_doc("Report", "Quoted Item Comparison")
erpnext.patches.v13_0.update_member_email_address
-erpnext.patches.v13_0.update_custom_fields_for_shopify
erpnext.patches.v13_0.updates_for_multi_currency_payroll
erpnext.patches.v13_0.create_leave_policy_assignment_based_on_employee_current_leave_policy
erpnext.patches.v13_0.update_pos_closing_entry_in_merge_log
@@ -297,3 +294,7 @@
erpnext.patches.v13_0.add_missing_fg_item_for_stock_entry
erpnext.patches.v13_0.update_subscription_status_in_memberships
erpnext.patches.v13_0.update_amt_in_work_order_required_items
+erpnext.patches.v13_0.update_export_type_for_gst
+erpnext.patches.v13_0.update_tds_check_field #3
+erpnext.patches.v13_0.add_custom_field_for_south_africa
+erpnext.patches.v13_0.shopify_deprecation_warning
diff --git a/erpnext/patches/v11_0/refactor_erpnext_shopify.py b/erpnext/patches/v11_0/refactor_erpnext_shopify.py
deleted file mode 100644
index 340e9fc..0000000
--- a/erpnext/patches/v11_0/refactor_erpnext_shopify.py
+++ /dev/null
@@ -1,43 +0,0 @@
-from __future__ import unicode_literals
-import frappe
-from frappe.installer import remove_from_installed_apps
-
-def execute():
- frappe.reload_doc('erpnext_integrations', 'doctype', 'shopify_settings')
- frappe.reload_doc('erpnext_integrations', 'doctype', 'shopify_tax_account')
- frappe.reload_doc('erpnext_integrations', 'doctype', 'shopify_log')
- frappe.reload_doc('erpnext_integrations', 'doctype', 'shopify_webhook_detail')
-
- if 'erpnext_shopify' in frappe.get_installed_apps():
- remove_from_installed_apps('erpnext_shopify')
-
- frappe.delete_doc("Module Def", 'erpnext_shopify')
-
- frappe.db.commit()
-
- frappe.db.sql("truncate `tabShopify Log`")
-
- setup_app_type()
- else:
- disable_shopify()
-
-def setup_app_type():
- try:
- shopify_settings = frappe.get_doc("Shopify Settings")
- shopify_settings.app_type = 'Private'
- shopify_settings.update_price_in_erpnext_price_list = 0 if getattr(shopify_settings, 'push_prices_to_shopify', None) else 1
- shopify_settings.flags.ignore_mandatory = True
- shopify_settings.ignore_permissions = True
- shopify_settings.save()
- except Exception:
- frappe.db.set_value("Shopify Settings", None, "enable_shopify", 0)
- frappe.log_error(frappe.get_traceback())
-
-def disable_shopify():
- # due to frappe.db.set_value wrongly written and enable_shopify being default 1
- # Shopify Settings isn't properly configured and leads to error
- shopify = frappe.get_doc('Shopify Settings')
-
- if shopify.app_type == "Public" or shopify.app_type == None or \
- (shopify.enable_shopify and not (shopify.shopify_url or shopify.api_key)):
- frappe.db.set_value("Shopify Settings", None, "enable_shopify", 0)
diff --git a/erpnext/patches/v12_0/set_default_shopify_app_type.py b/erpnext/patches/v12_0/set_default_shopify_app_type.py
deleted file mode 100644
index d040ea7..0000000
--- a/erpnext/patches/v12_0/set_default_shopify_app_type.py
+++ /dev/null
@@ -1,6 +0,0 @@
-from __future__ import unicode_literals
-import frappe
-
-def execute():
- frappe.reload_doc('erpnext_integrations', 'doctype', 'shopify_settings')
- frappe.db.set_value('Shopify Settings', None, 'app_type', 'Private')
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/add_custom_field_for_south_africa.py b/erpnext/patches/v13_0/add_custom_field_for_south_africa.py
new file mode 100644
index 0000000..f882fde
--- /dev/null
+++ b/erpnext/patches/v13_0/add_custom_field_for_south_africa.py
@@ -0,0 +1,13 @@
+# Copyright (c) 2020, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+import frappe
+from erpnext.regional.south_africa.setup import make_custom_fields
+
+def execute():
+ company = frappe.get_all('Company', filters = {'country': 'South Africa'})
+ if not company:
+ return
+
+ make_custom_fields()
diff --git a/erpnext/patches/v13_0/add_missing_fg_item_for_stock_entry.py b/erpnext/patches/v13_0/add_missing_fg_item_for_stock_entry.py
index d7ad1fc..0d8109c 100644
--- a/erpnext/patches/v13_0/add_missing_fg_item_for_stock_entry.py
+++ b/erpnext/patches/v13_0/add_missing_fg_item_for_stock_entry.py
@@ -30,19 +30,20 @@
return
repost_stock_entries = []
+
stock_entries = frappe.db.sql_list('''
SELECT
se.name
FROM
`tabStock Entry` se
WHERE
- se.purpose = 'Manufacture' and se.docstatus < 2 and se.work_order in {work_orders}
+ se.purpose = 'Manufacture' and se.docstatus < 2 and se.work_order in %s
and not exists(
select name from `tabStock Entry Detail` sed where sed.parent = se.name and sed.is_finished_item = 1
)
- Order BY
+ ORDER BY
se.posting_date, se.posting_time
- '''.format(work_orders=tuple(work_orders)))
+ ''', (work_orders,))
if stock_entries:
print('Length of stock entries', len(stock_entries))
diff --git a/erpnext/patches/v13_0/shopify_deprecation_warning.py b/erpnext/patches/v13_0/shopify_deprecation_warning.py
new file mode 100644
index 0000000..245d1a9
--- /dev/null
+++ b/erpnext/patches/v13_0/shopify_deprecation_warning.py
@@ -0,0 +1,10 @@
+import click
+
+
+def execute():
+
+ click.secho(
+ "Shopify Integration is moved to a separate app and will be removed from ERPNext in version-14.\n"
+ "Please install the app to continue using the integration: https://github.com/frappe/ecommerce_integrations",
+ fg="yellow",
+ )
diff --git a/erpnext/patches/v13_0/update_custom_fields_for_shopify.py b/erpnext/patches/v13_0/update_custom_fields_for_shopify.py
deleted file mode 100644
index f1d2ea2..0000000
--- a/erpnext/patches/v13_0/update_custom_fields_for_shopify.py
+++ /dev/null
@@ -1,10 +0,0 @@
-# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
-# MIT License. See license.txt
-
-from __future__ import unicode_literals
-import frappe
-from erpnext.erpnext_integrations.doctype.shopify_settings.shopify_settings import setup_custom_fields
-
-def execute():
- if frappe.db.get_single_value('Shopify Settings', 'enable_shopify'):
- setup_custom_fields()
diff --git a/erpnext/patches/v13_0/update_export_type_for_gst.py b/erpnext/patches/v13_0/update_export_type_for_gst.py
new file mode 100644
index 0000000..478a2a6
--- /dev/null
+++ b/erpnext/patches/v13_0/update_export_type_for_gst.py
@@ -0,0 +1,24 @@
+import frappe
+
+def execute():
+ company = frappe.get_all('Company', filters = {'country': 'India'})
+ if not company:
+ return
+
+ # Update custom fields
+ fieldname = frappe.db.get_value('Custom Field', {'dt': 'Customer', 'fieldname': 'export_type'})
+ if fieldname:
+ frappe.db.set_value('Custom Field', fieldname, 'default', '')
+
+ fieldname = frappe.db.get_value('Custom Field', {'dt': 'Supplier', 'fieldname': 'export_type'})
+ if fieldname:
+ frappe.db.set_value('Custom Field', fieldname, 'default', '')
+
+ # Update Customer/Supplier Masters
+ frappe.db.sql("""
+ UPDATE `tabCustomer` set export_type = '' WHERE gst_category NOT IN ('SEZ', 'Overseas', 'Deemed Export')
+ """)
+
+ frappe.db.sql("""
+ UPDATE `tabSupplier` set export_type = '' WHERE gst_category NOT IN ('SEZ', 'Overseas')
+ """)
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/update_tds_check_field.py b/erpnext/patches/v13_0/update_tds_check_field.py
new file mode 100644
index 0000000..3d14958
--- /dev/null
+++ b/erpnext/patches/v13_0/update_tds_check_field.py
@@ -0,0 +1,9 @@
+import frappe
+
+def execute():
+ if frappe.db.has_table("Tax Withholding Category") \
+ and frappe.db.has_column("Tax Withholding Category", "round_off_tax_amount"):
+ frappe.db.sql("""
+ UPDATE `tabTax Withholding Category` set round_off_tax_amount = 0
+ WHERE round_off_tax_amount IS NULL
+ """)
\ No newline at end of file
diff --git a/erpnext/payroll/doctype/additional_salary/additional_salary.py b/erpnext/payroll/doctype/additional_salary/additional_salary.py
index 381f399..b978cbe 100644
--- a/erpnext/payroll/doctype/additional_salary/additional_salary.py
+++ b/erpnext/payroll/doctype/additional_salary/additional_salary.py
@@ -112,11 +112,11 @@
no_of_days = date_diff(getdate(end_date), getdate(start_date)) + 1
return amount_per_day * no_of_days
+@frappe.whitelist()
def get_additional_salaries(employee, start_date, end_date, component_type):
additional_salary_list = frappe.db.sql("""
- select name, salary_component as component, type, amount,
- overwrite_salary_structure_amount as overwrite,
- deduct_full_tax_on_selected_payroll_date
+ select name, salary_component as component, type, amount, overwrite_salary_structure_amount as overwrite,
+ deduct_full_tax_on_selected_payroll_date, is_recurring
from `tabAdditional Salary`
where employee=%(employee)s
and docstatus = 1
diff --git a/erpnext/payroll/doctype/salary_detail/salary_detail.json b/erpnext/payroll/doctype/salary_detail/salary_detail.json
index 393f647..97608d7 100644
--- a/erpnext/payroll/doctype/salary_detail/salary_detail.json
+++ b/erpnext/payroll/doctype/salary_detail/salary_detail.json
@@ -12,6 +12,7 @@
"year_to_date",
"section_break_5",
"additional_salary",
+ "is_recurring_additional_salary",
"statistical_component",
"depends_on_payment_days",
"exempted_from_income_tax",
@@ -235,11 +236,19 @@
"label": "Year To Date",
"options": "currency",
"read_only": 1
+ },
+ {
+ "default": "0",
+ "depends_on": "eval:doc.parenttype=='Salary Slip' && doc.parentfield=='earnings' && doc.additional_salary",
+ "fieldname": "is_recurring_additional_salary",
+ "fieldtype": "Check",
+ "label": "Is Recurring Additional Salary",
+ "read_only": 1
}
],
"istable": 1,
"links": [],
- "modified": "2021-01-14 13:39:15.847158",
+ "modified": "2021-03-14 13:39:15.847158",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Salary Detail",
diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py
index c321f6f..f0ca64f 100644
--- a/erpnext/payroll/doctype/salary_slip/salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py
@@ -7,12 +7,12 @@
from frappe.utils import add_days, cint, cstr, flt, getdate, rounded, date_diff, money_in_words, formatdate, get_first_day
from frappe.model.naming import make_autoname
+from frappe.utils.background_jobs import enqueue
from frappe import msgprint, _
from erpnext.payroll.doctype.payroll_entry.payroll_entry import get_start_end_dates
from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
from erpnext.utilities.transaction_base import TransactionBase
-from frappe.utils.background_jobs import enqueue
from erpnext.payroll.doctype.additional_salary.additional_salary import get_additional_salaries
from erpnext.payroll.doctype.payroll_period.payroll_period import get_period_factor, get_payroll_period
from erpnext.payroll.doctype.employee_benefit_application.employee_benefit_application import get_benefit_component_amount
@@ -618,7 +618,8 @@
get_salary_component_data(additional_salary.component),
additional_salary.amount,
component_type,
- additional_salary
+ additional_salary,
+ is_recurring = additional_salary.is_recurring
)
def add_tax_components(self, payroll_period):
@@ -639,17 +640,20 @@
tax_row = get_salary_component_data(d)
self.update_component_row(tax_row, tax_amount, "deductions")
- def update_component_row(self, component_data, amount, component_type, additional_salary=None):
+ def update_component_row(self, component_data, amount, component_type, additional_salary=None, is_recurring = 0):
component_row = None
for d in self.get(component_type):
if d.salary_component != component_data.salary_component:
continue
if (
- (not d.additional_salary
- and (not additional_salary or additional_salary.overwrite))
- or (additional_salary
- and additional_salary.name == d.additional_salary)
+ (
+ not d.additional_salary
+ and (not additional_salary or additional_salary.overwrite)
+ ) or (
+ additional_salary
+ and additional_salary.name == d.additional_salary
+ )
):
component_row = d
break
@@ -677,8 +681,13 @@
component_row.set(attr, component_data.get(attr))
if additional_salary:
- component_row.default_amount = 0
- component_row.additional_amount = amount
+ component_row.is_recurring_additional_salary = is_recurring
+ if additional_salary.overwrite:
+ component_row.additional_amount = flt(flt(amount) - flt(component_row.get("default_amount", 0)),
+ component_row.precision("additional_amount"))
+ else:
+ component_row.default_amount = 0
+ component_row.additional_amount = amount
component_row.additional_salary = additional_salary.name
component_row.deduct_full_tax_on_selected_payroll_date = \
additional_salary.deduct_full_tax_on_selected_payroll_date
@@ -710,6 +719,7 @@
# get remaining numbers of sub-period (period for which one salary is processed)
remaining_sub_periods = get_period_factor(self.employee,
self.start_date, self.end_date, self.payroll_frequency, payroll_period)[1]
+
# get taxable_earnings, paid_taxes for previous period
previous_taxable_earnings = self.get_taxable_earnings_for_prev_period(payroll_period.start_date,
self.start_date, tax_slab.allow_tax_exemption)
@@ -869,8 +879,16 @@
if earning.is_tax_applicable:
if additional_amount:
- taxable_earnings += (amount - additional_amount)
- additional_income += additional_amount
+ if not earning.is_recurring_additional_salary:
+ taxable_earnings += (amount - additional_amount)
+ additional_income += additional_amount
+ else:
+ to_date = frappe.db.get_value("Additional Salary", earning.additional_salary, 'to_date')
+ period = (getdate(to_date).month - getdate(self.start_date).month) + 1
+ if period > 0:
+ taxable_earnings += (amount - additional_amount) * period
+ additional_income += additional_amount * period
+
if earning.deduct_full_tax_on_selected_payroll_date:
additional_income_with_full_tax += additional_amount
continue
diff --git a/erpnext/payroll/doctype/salary_structure/test_salary_structure.py b/erpnext/payroll/doctype/salary_structure/test_salary_structure.py
index 374dd7e..3957d83 100644
--- a/erpnext/payroll/doctype/salary_structure/test_salary_structure.py
+++ b/erpnext/payroll/doctype/salary_structure/test_salary_structure.py
@@ -129,7 +129,7 @@
"earnings": make_earning_salary_component(setup=True, test_tax=test_tax, company_list=["_Test Company"]),
"deductions": make_deduction_salary_component(setup=True, test_tax=test_tax, company_list=["_Test Company"]),
"payroll_frequency": payroll_frequency,
- "payment_account": get_random("Account", filters={"account_currency": currency}),
+ "payment_account": get_random("Account", filters={'account_currency': currency}),
"currency": currency
}
if other_details and isinstance(other_details, dict):
diff --git a/erpnext/regional/address_template/templates/france.html b/erpnext/regional/address_template/templates/france.html
new file mode 100644
index 0000000..752331e
--- /dev/null
+++ b/erpnext/regional/address_template/templates/france.html
@@ -0,0 +1,5 @@
+{% if address_line1 %}{{ address_line1 }}{% endif -%}
+{% if address_line2 %}<br>{{ address_line2 }}{% endif -%}
+{% if pincode %}<br>{{ pincode }}{% endif -%}
+{% if city %} {{ city }}{% endif -%}
+{% if country %}<br>{{ country }}{% endif -%}
diff --git a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py
index ea39fe1..0ee5b09 100644
--- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py
+++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py
@@ -280,9 +280,15 @@
if self.get('invoice_items'):
# Build itemised tax for export invoices, nil and exempted where tax table is blank
for invoice, items in iteritems(self.invoice_items):
- if invoice not in self.items_based_on_tax_rate and (self.invoice_detail_map.get(invoice, {}).get('export_type')
- == "Without Payment of Tax"):
+ if invoice not in self.items_based_on_tax_rate and self.invoice_detail_map.get(invoice, {}).get('export_type') \
+ == "Without Payment of Tax" and self.invoice_detail_map.get(invoice, {}).get('gst_category') == "Overseas":
self.items_based_on_tax_rate.setdefault(invoice, {}).setdefault(0, items.keys())
+ else:
+ for item in items.keys():
+ if item in self.is_nil_exempt + self.is_non_gst and \
+ item not in self.items_based_on_tax_rate.get(invoice, {}).get(0, []):
+ self.items_based_on_tax_rate.setdefault(invoice, {}).setdefault(0, [])
+ self.items_based_on_tax_rate[invoice][0].append(item)
def set_outward_taxable_supplies(self):
inter_state_supply_details = {}
diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/__init__.py b/erpnext/regional/doctype/south_africa_vat_settings/__init__.py
similarity index 100%
rename from erpnext/erpnext_integrations/doctype/shopify_settings/__init__.py
rename to erpnext/regional/doctype/south_africa_vat_settings/__init__.py
diff --git a/erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.js b/erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.js
new file mode 100644
index 0000000..e37a61a
--- /dev/null
+++ b/erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.js
@@ -0,0 +1,23 @@
+// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('South Africa VAT Settings', {
+ refresh: function(frm) {
+ frm.set_query("company", function() {
+ return {
+ filters: {
+ country: "South Africa",
+ }
+ };
+ });
+ frm.set_query("account", "vat_accounts", function() {
+ return {
+ filters: {
+ company: frm.doc.company,
+ account_type: "Tax",
+ is_group: 0
+ }
+ };
+ });
+ }
+});
diff --git a/erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.json b/erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.json
new file mode 100644
index 0000000..8a51829
--- /dev/null
+++ b/erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.json
@@ -0,0 +1,76 @@
+{
+ "actions": [],
+ "autoname": "field:company",
+ "creation": "2021-07-08 22:34:33.668015",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "company",
+ "vat_accounts"
+ ],
+ "fields": [
+ {
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Company",
+ "options": "Company",
+ "reqd": 1,
+ "unique": 1
+ },
+ {
+ "fieldname": "vat_accounts",
+ "fieldtype": "Table",
+ "label": "VAT Accounts",
+ "options": "South Africa VAT Account",
+ "reqd": 1
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2021-07-14 02:17:52.476762",
+ "modified_by": "Administrator",
+ "module": "Regional",
+ "name": "South Africa VAT Settings",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Accounts Manager",
+ "share": 1,
+ "write": 1
+ },
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Accounts User",
+ "share": 1,
+ "write": 1
+ },
+ {
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Auditor",
+ "share": 1
+ }
+ ],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.py b/erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.py
new file mode 100644
index 0000000..d74154b
--- /dev/null
+++ b/erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.py
@@ -0,0 +1,8 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+# import frappe
+from frappe.model.document import Document
+
+class SouthAfricaVATSettings(Document):
+ pass
diff --git a/erpnext/regional/doctype/south_africa_vat_settings/test_south_africa_vat_settings.py b/erpnext/regional/doctype/south_africa_vat_settings/test_south_africa_vat_settings.py
new file mode 100644
index 0000000..1c36652
--- /dev/null
+++ b/erpnext/regional/doctype/south_africa_vat_settings/test_south_africa_vat_settings.py
@@ -0,0 +1,8 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+
+# import frappe
+import unittest
+
+class TestSouthAfricaVATSettings(unittest.TestCase):
+ pass
diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py
index 81c7a6b..f497613 100644
--- a/erpnext/regional/india/e_invoice/utils.py
+++ b/erpnext/regional/india/e_invoice/utils.py
@@ -316,10 +316,6 @@
))
def get_return_doc_reference(invoice):
- if not invoice.return_against:
- frappe.throw(_('For generating IRN, reference to the original invoice is mandatory for a credit note. Please set {} field to generate e-invoice.')
- .format(frappe.bold('Return Against')), title=_('Missing Field'))
-
invoice_date = frappe.db.get_value('Sales Invoice', invoice.return_against, 'posting_date')
return frappe._dict(dict(
invoice_name=invoice.return_against, invoice_date=format_date(invoice_date, 'dd/mm/yyyy')
@@ -438,7 +434,7 @@
if invoice.is_pos and invoice.base_paid_amount:
payment_details = get_payment_details(invoice)
- if invoice.is_return:
+ if invoice.is_return and invoice.return_against:
prev_doc_details = get_return_doc_reference(invoice)
if invoice.transporter and not invoice.is_return:
@@ -969,7 +965,7 @@
"attached_to_doctype": doctype,
"attached_to_name": docname,
"attached_to_field": "qrcode_image",
- "is_private": 1,
+ "is_private": 0,
"content": qr_image.getvalue()})
_file.save()
frappe.db.commit()
diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py
index 9265460..e9372f9 100644
--- a/erpnext/regional/india/setup.py
+++ b/erpnext/regional/india/setup.py
@@ -641,7 +641,6 @@
'label': 'Export Type',
'fieldtype': 'Select',
'insert_after': 'gst_category',
- 'default': 'Without Payment of Tax',
'depends_on':'eval:in_list(["SEZ", "Overseas"], doc.gst_category)',
'options': '\nWith Payment of Tax\nWithout Payment of Tax'
}
@@ -660,7 +659,6 @@
'label': 'Export Type',
'fieldtype': 'Select',
'insert_after': 'gst_category',
- 'default': 'Without Payment of Tax',
'depends_on':'eval:in_list(["SEZ", "Overseas", "Deemed Export"], doc.gst_category)',
'options': '\nWith Payment of Tax\nWithout Payment of Tax'
}
diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py
index fbe47d0..88c350a 100644
--- a/erpnext/regional/india/utils.py
+++ b/erpnext/regional/india/utils.py
@@ -859,4 +859,15 @@
depreciation_amount = flt(depreciable_value * (flt(rate_of_depreciation) / 100))
- return depreciation_amount
\ No newline at end of file
+ return depreciation_amount
+
+def set_item_tax_from_hsn_code(item):
+ if not item.taxes and item.gst_hsn_code:
+ hsn_doc = frappe.get_doc("GST HSN Code", item.gst_hsn_code)
+
+ for tax in hsn_doc.taxes:
+ item.append('taxes', {
+ 'item_tax_template': tax.item_tax_template,
+ 'tax_category': tax.tax_category,
+ 'valid_from': tax.valid_from
+ })
\ No newline at end of file
diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py
index b81fa81..4b73094 100644
--- a/erpnext/regional/report/gstr_1/gstr_1.py
+++ b/erpnext/regional/report/gstr_1/gstr_1.py
@@ -286,7 +286,8 @@
# Build itemised tax for export invoices where tax table is blank
for invoice, items in iteritems(self.invoice_items):
if invoice not in self.items_based_on_tax_rate and invoice not in unidentified_gst_accounts_invoice \
- and frappe.db.get_value(self.doctype, invoice, "export_type") == "Without Payment of Tax":
+ and self.invoices.get(invoice, {}).get('export_type') == "Without Payment of Tax" \
+ and self.invoices.get(invoice, {}).get('gst_category') == "Overseas":
self.items_based_on_tax_rate.setdefault(invoice, {}).setdefault(0, items.keys())
def get_columns(self):
diff --git a/erpnext/erpnext_integrations/doctype/shopify_log/__init__.py b/erpnext/regional/report/vat_audit_report/__init__.py
similarity index 100%
rename from erpnext/erpnext_integrations/doctype/shopify_log/__init__.py
rename to erpnext/regional/report/vat_audit_report/__init__.py
diff --git a/erpnext/regional/report/vat_audit_report/vat_audit_report.js b/erpnext/regional/report/vat_audit_report/vat_audit_report.js
new file mode 100644
index 0000000..39ef9b5
--- /dev/null
+++ b/erpnext/regional/report/vat_audit_report/vat_audit_report.js
@@ -0,0 +1,31 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["VAT Audit Report"] = {
+ "filters": [
+ {
+ "fieldname": "company",
+ "label": __("Company"),
+ "fieldtype": "Link",
+ "options": "Company",
+ "reqd": 1,
+ "default": frappe.defaults.get_user_default("Company")
+ },
+ {
+ "fieldname": "from_date",
+ "label": __("From Date"),
+ "fieldtype": "Date",
+ "reqd": 1,
+ "default": frappe.datetime.add_months(frappe.datetime.get_today(), -2),
+ "width": "80"
+ },
+ {
+ "fieldname": "to_date",
+ "label": __("To Date"),
+ "fieldtype": "Date",
+ "reqd": 1,
+ "default": frappe.datetime.get_today()
+ }
+ ]
+};
diff --git a/erpnext/regional/report/vat_audit_report/vat_audit_report.json b/erpnext/regional/report/vat_audit_report/vat_audit_report.json
new file mode 100644
index 0000000..8917e8f
--- /dev/null
+++ b/erpnext/regional/report/vat_audit_report/vat_audit_report.json
@@ -0,0 +1,32 @@
+{
+ "add_total_row": 0,
+ "columns": [],
+ "creation": "2021-07-09 11:07:43.473518",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 0,
+ "is_standard": "Yes",
+ "modified": "2021-07-09 11:07:43.473518",
+ "modified_by": "Administrator",
+ "module": "Regional",
+ "name": "VAT Audit Report",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "GL Entry",
+ "report_name": "VAT Audit Report",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "Accounts User"
+ },
+ {
+ "role": "Accounts Manager"
+ },
+ {
+ "role": "Auditor"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/regional/report/vat_audit_report/vat_audit_report.py b/erpnext/regional/report/vat_audit_report/vat_audit_report.py
new file mode 100644
index 0000000..f45ba01
--- /dev/null
+++ b/erpnext/regional/report/vat_audit_report/vat_audit_report.py
@@ -0,0 +1,253 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+import json
+from frappe import _
+from frappe.utils import formatdate
+
+def execute(filters=None):
+ return VATAuditReport(filters).run()
+
+class VATAuditReport(object):
+
+ def __init__(self, filters=None):
+ self.filters = frappe._dict(filters or {})
+ self.columns = []
+ self.data = []
+ self.doctypes = ["Purchase Invoice", "Sales Invoice"]
+
+ def run(self):
+ self.get_sa_vat_accounts()
+ self.get_columns()
+ for doctype in self.doctypes:
+ self.select_columns = """
+ name as voucher_no,
+ posting_date, remarks"""
+ columns = ", supplier as party, credit_to as account" if doctype=="Purchase Invoice" \
+ else ", customer as party, debit_to as account"
+ self.select_columns += columns
+
+ self.get_invoice_data(doctype)
+
+ if self.invoices:
+ self.get_invoice_items(doctype)
+ self.get_items_based_on_tax_rate(doctype)
+ self.get_data(doctype)
+
+ return self.columns, self.data
+
+ def get_sa_vat_accounts(self):
+ self.sa_vat_accounts = frappe.get_list("South Africa VAT Account",
+ filters = {"parent": self.filters.company}, pluck="account")
+ if not self.sa_vat_accounts and not frappe.flags.in_test and not frappe.flags.in_migrate:
+ frappe.throw(_("Please set VAT Accounts in South Africa VAT Settings"))
+
+ def get_invoice_data(self, doctype):
+ conditions = self.get_conditions()
+ self.invoices = frappe._dict()
+
+ invoice_data = frappe.db.sql("""
+ SELECT
+ {select_columns}
+ FROM
+ `tab{doctype}`
+ WHERE
+ docstatus = 1 {where_conditions}
+ and is_opening = "No"
+ ORDER BY
+ posting_date DESC
+ """.format(select_columns=self.select_columns, doctype=doctype,
+ where_conditions=conditions), self.filters, as_dict=1)
+
+ for d in invoice_data:
+ self.invoices.setdefault(d.voucher_no, d)
+
+ def get_invoice_items(self, doctype):
+ self.invoice_items = frappe._dict()
+
+ items = frappe.db.sql("""
+ SELECT
+ item_code, parent, taxable_value, base_net_amount, is_zero_rated
+ FROM
+ `tab%s Item`
+ WHERE
+ parent in (%s)
+ """ % (doctype, ", ".join(["%s"]*len(self.invoices))), tuple(self.invoices), as_dict=1)
+ for d in items:
+ if d.item_code not in self.invoice_items.get(d.parent, {}):
+ self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, {
+ 'net_amount': 0.0})
+ self.invoice_items[d.parent][d.item_code]['net_amount'] += d.get('taxable_value', 0) or d.get('base_net_amount', 0)
+ self.invoice_items[d.parent][d.item_code]['is_zero_rated'] = d.is_zero_rated
+
+ def get_items_based_on_tax_rate(self, doctype):
+ self.items_based_on_tax_rate = frappe._dict()
+ self.item_tax_rate = frappe._dict()
+ self.tax_doctype = "Purchase Taxes and Charges" if doctype=="Purchase Invoice" \
+ else "Sales Taxes and Charges"
+
+ self.tax_details = frappe.db.sql("""
+ SELECT
+ parent, account_head, item_wise_tax_detail, base_tax_amount_after_discount_amount
+ FROM
+ `tab%s`
+ WHERE
+ parenttype = %s and docstatus = 1
+ and parent in (%s)
+ ORDER BY
+ account_head
+ """ % (self.tax_doctype, "%s", ", ".join(["%s"]*len(self.invoices.keys()))),
+ tuple([doctype] + list(self.invoices.keys())))
+
+ for parent, account, item_wise_tax_detail, tax_amount in self.tax_details:
+ if item_wise_tax_detail:
+ try:
+ if account in self.sa_vat_accounts:
+ item_wise_tax_detail = json.loads(item_wise_tax_detail)
+ else:
+ continue
+ for item_code, taxes in item_wise_tax_detail.items():
+ is_zero_rated = self.invoice_items.get(parent).get(item_code).get("is_zero_rated")
+ #to skip items with non-zero tax rate in multiple rows
+ if taxes[0] == 0 and not is_zero_rated:
+ continue
+ tax_rate, item_amount_map = self.get_item_amount_map(parent, item_code, taxes)
+
+ if tax_rate is not None:
+ rate_based_dict = self.items_based_on_tax_rate.setdefault(parent, {}) \
+ .setdefault(tax_rate, [])
+ if item_code not in rate_based_dict:
+ rate_based_dict.append(item_code)
+ except ValueError:
+ continue
+
+ def get_item_amount_map(self, parent, item_code, taxes):
+ net_amount = self.invoice_items.get(parent).get(item_code).get("net_amount")
+ tax_rate = taxes[0]
+ tax_amount = taxes[1]
+ gross_amount = net_amount + tax_amount
+ item_amount_map = self.item_tax_rate.setdefault(parent, {}) \
+ .setdefault(item_code, [])
+ amount_dict = {
+ "tax_rate": tax_rate,
+ "gross_amount": gross_amount,
+ "tax_amount": tax_amount,
+ "net_amount": net_amount
+ }
+ item_amount_map.append(amount_dict)
+
+ return tax_rate, item_amount_map
+
+ def get_conditions(self):
+ conditions = ""
+ for opts in (("company", " and company=%(company)s"),
+ ("from_date", " and posting_date>=%(from_date)s"),
+ ("to_date", " and posting_date<=%(to_date)s")):
+ if self.filters.get(opts[0]):
+ conditions += opts[1]
+
+ return conditions
+
+ def get_data(self, doctype):
+ consolidated_data = self.get_consolidated_data(doctype)
+ section_name = _("Purchases") if doctype == "Purchase Invoice" else _("Sales")
+
+ for rate, section in consolidated_data.items():
+ rate = int(rate)
+ label = frappe.bold(section_name + "- " + "Rate" + " " + str(rate) + "%")
+ section_head = {"posting_date": label}
+ total_gross = total_tax = total_net = 0
+ self.data.append(section_head)
+ for row in section.get("data"):
+ self.data.append(row)
+ total_gross += row["gross_amount"]
+ total_tax += row["tax_amount"]
+ total_net += row["net_amount"]
+
+ total = {
+ "posting_date": frappe.bold(_("Total")),
+ "gross_amount": total_gross,
+ "tax_amount": total_tax,
+ "net_amount": total_net,
+ "bold":1
+ }
+ self.data.append(total)
+ self.data.append({})
+
+ def get_consolidated_data(self, doctype):
+ consolidated_data_map={}
+ for inv, inv_data in self.invoices.items():
+ if self.items_based_on_tax_rate.get(inv):
+ for rate, items in self.items_based_on_tax_rate.get(inv).items():
+ consolidated_data_map.setdefault(rate, {"data": []})
+ for item in items:
+ row = {}
+ item_details = self.item_tax_rate.get(inv).get(item)
+ row["account"] = inv_data.get("account")
+ row["posting_date"] = formatdate(inv_data.get("posting_date"), "dd-mm-yyyy")
+ row["voucher_type"] = doctype
+ row["voucher_no"] = inv
+ row["remarks"] = inv_data.get("remarks")
+ row["gross_amount"]= item_details[0].get("gross_amount")
+ row["tax_amount"]= item_details[0].get("tax_amount")
+ row["net_amount"]= item_details[0].get("net_amount")
+ consolidated_data_map[rate]["data"].append(row)
+
+ return consolidated_data_map
+
+ def get_columns(self):
+ self.columns = [
+ {
+ "fieldname": "posting_date",
+ "label": "Posting Date",
+ "fieldtype": "Data",
+ "width": 200
+ },
+ {
+ "fieldname": "account",
+ "label": "Account",
+ "fieldtype": "Link",
+ "options": "Account",
+ "width": 150
+ },
+ {
+ "fieldname": "voucher_type",
+ "label": "Voucher Type",
+ "fieldtype": "Data",
+ "width": 140,
+ "hidden": 1
+ },
+ {
+ "fieldname": "voucher_no",
+ "label": "Reference",
+ "fieldtype": "Dynamic Link",
+ "options": "voucher_type",
+ "width": 150
+ },
+ {
+ "fieldname": "remarks",
+ "label": "Details",
+ "fieldtype": "Data",
+ "width": 150
+ },
+ {
+ "fieldname": "net_amount",
+ "label": "Net Amount",
+ "fieldtype": "Currency",
+ "width": 150
+ },
+ {
+ "fieldname": "tax_amount",
+ "label": "Tax Amount",
+ "fieldtype": "Currency",
+ "width": 150
+ },
+ {
+ "fieldname": "gross_amount",
+ "label": "Gross Amount",
+ "fieldtype": "Currency",
+ "width": 150
+ },
+ ]
diff --git a/erpnext/erpnext_integrations/doctype/shopify_log/__init__.py b/erpnext/regional/south_africa/__init__.py
similarity index 100%
copy from erpnext/erpnext_integrations/doctype/shopify_log/__init__.py
copy to erpnext/regional/south_africa/__init__.py
diff --git a/erpnext/regional/south_africa/setup.py b/erpnext/regional/south_africa/setup.py
new file mode 100644
index 0000000..ac783b8
--- /dev/null
+++ b/erpnext/regional/south_africa/setup.py
@@ -0,0 +1,36 @@
+# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+
+# import frappe, os, json
+from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
+from frappe.permissions import add_permission, update_permission_property
+
+def setup(company=None, patch=True):
+ add_permissions()
+
+def make_custom_fields(update=True):
+ is_zero_rated = dict(fieldname='is_zero_rated', label='Is Zero Rated',
+ fieldtype='Check', fetch_from='item_code.is_zero_rated',
+ insert_after='description', print_hide=1)
+ custom_fields = {
+ 'Item': [
+ dict(fieldname='is_zero_rated', label='Is Zero Rated',
+ fieldtype='Check', insert_after='item_group',
+ print_hide=1)
+ ],
+ 'Sales Invoice Item': is_zero_rated,
+ 'Purchase Invoice Item': is_zero_rated
+ }
+
+ create_custom_fields(custom_fields, update=update)
+
+def add_permissions():
+ """Add Permissions for South Africa VAT Settings and South Africa VAT Account"""
+ for doctype in ('South Africa VAT Settings', 'South Africa VAT Account'):
+ add_permission(doctype, 'All', 0)
+ for role in ('Accounts Manager', 'Accounts User', 'System Manager'):
+ add_permission(doctype, role, 0)
+ update_permission_property(doctype, role, 0, 'write', 1)
+ update_permission_property(doctype, role, 0, 'create', 1)
\ No newline at end of file
diff --git a/erpnext/selling/page/point_of_sale/pos_item_cart.js b/erpnext/selling/page/point_of_sale/pos_item_cart.js
index 6e36d28..a4a4b0e 100644
--- a/erpnext/selling/page/point_of_sale/pos_item_cart.js
+++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js
@@ -564,7 +564,6 @@
)
set_dynamic_rate_header_width();
- this.scroll_to_item($item_to_update);
function set_dynamic_rate_header_width() {
const rate_cols = Array.from(me.$cart_items_wrapper.find(".item-rate-amount"));
@@ -639,12 +638,6 @@
$($img).parent().replaceWith(`<div class="item-image item-abbr">${item_abbr}</div>`);
}
- scroll_to_item($item) {
- if ($item.length === 0) return;
- const scrollTop = $item.offset().top - this.$cart_items_wrapper.offset().top + this.$cart_items_wrapper.scrollTop();
- this.$cart_items_wrapper.animate({ scrollTop });
- }
-
update_selector_value_in_cart_item(selector, value, item) {
const $item_to_update = this.get_cart_item(item);
$item_to_update.attr(`data-${selector}`, escape(value));
diff --git a/erpnext/selling/page/point_of_sale/pos_payment.js b/erpnext/selling/page/point_of_sale/pos_payment.js
index f1a166b..63306ad 100644
--- a/erpnext/selling/page/point_of_sale/pos_payment.js
+++ b/erpnext/selling/page/point_of_sale/pos_payment.js
@@ -198,6 +198,7 @@
const is_cash_shortcuts_invisible = !this.$payment_modes.find('.cash-shortcuts').is(':visible');
this.attach_cash_shortcuts(frm.doc);
!is_cash_shortcuts_invisible && this.$payment_modes.find('.cash-shortcuts').css('display', 'grid');
+ this.render_payment_mode_dom();
});
frappe.ui.form.on('POS Invoice', 'loyalty_amount', (frm) => {
diff --git a/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py b/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py
index c5c01c5..4ff2dd7 100644
--- a/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py
+++ b/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py
@@ -62,12 +62,12 @@
exchange_rate = get_exchange_rate("USD", "INR", "2016-01-30", "for_selling")
self.assertEqual(exchange_rate, 62.9)
-
- # Exchange rate as on 15th Dec, 2015, should be fetched from fixer.io
+
+ # Exchange rate as on 15th Dec, 2015
self.clear_cache()
exchange_rate = get_exchange_rate("USD", "INR", "2015-12-15", "for_selling")
self.assertFalse(exchange_rate == 60)
- self.assertEqual(flt(exchange_rate, 3), 66.894)
+ self.assertEqual(flt(exchange_rate, 3), 66.999)
def test_exchange_rate_strict(self):
# strict currency settings
@@ -77,28 +77,17 @@
exchange_rate = get_exchange_rate("USD", "INR", "2016-01-01", "for_buying")
self.assertEqual(exchange_rate, 60.0)
- # Will fetch from fixer.io
self.clear_cache()
exchange_rate = get_exchange_rate("USD", "INR", "2016-01-15", "for_buying")
- self.assertEqual(flt(exchange_rate, 3), 67.79)
+ self.assertEqual(flt(exchange_rate, 3), 67.235)
exchange_rate = get_exchange_rate("USD", "INR", "2016-01-30", "for_selling")
self.assertEqual(exchange_rate, 62.9)
- # Exchange rate as on 15th Dec, 2015, should be fetched from fixer.io
+ # Exchange rate as on 15th Dec, 2015
self.clear_cache()
exchange_rate = get_exchange_rate("USD", "INR", "2015-12-15", "for_buying")
- self.assertEqual(flt(exchange_rate, 3), 66.894)
-
- exchange_rate = get_exchange_rate("INR", "NGN", "2016-01-10", "for_selling")
- self.assertEqual(exchange_rate, 65.1)
-
- # NGN is not available on fixer.io so these should return 0
- exchange_rate = get_exchange_rate("INR", "NGN", "2016-01-09", "for_selling")
- self.assertEqual(exchange_rate, 0)
-
- exchange_rate = get_exchange_rate("INR", "NGN", "2016-01-11", "for_selling")
- self.assertEqual(exchange_rate, 0)
+ self.assertEqual(flt(exchange_rate, 3), 66.999)
def test_exchange_rate_strict_switched(self):
# Start with allow_stale is True
@@ -111,4 +100,4 @@
# Will fetch from fixer.io
self.clear_cache()
exchange_rate = get_exchange_rate("USD", "INR", "2016-01-15", "for_buying")
- self.assertEqual(flt(exchange_rate, 3), 67.79)
+ self.assertEqual(flt(exchange_rate, 3), 67.235)
diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json
index 9313f95..23e5947 100644
--- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json
+++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json
@@ -54,7 +54,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2021-05-08 23:13:48.049879",
+ "modified": "2021-08-04 20:15:59.071493",
"modified_by": "Administrator",
"module": "Setup",
"name": "Transaction Deletion Record",
@@ -70,6 +70,7 @@
"report": 1,
"role": "System Manager",
"share": 1,
+ "submit": 1,
"write": 1
}
],
diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py
index 691d331..8a49155 100644
--- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py
+++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py
@@ -18,7 +18,7 @@
doctypes_to_be_ignored_list = get_doctypes_to_be_ignored()
for doctype in self.doctypes_to_be_ignored:
if doctype.doctype_name not in doctypes_to_be_ignored_list:
- frappe.throw(_("DocTypes should not be added manually to the 'Excluded DocTypes' table. You are only allowed to remove entries from it. "),
+ frappe.throw(_("DocTypes should not be added manually to the 'Excluded DocTypes' table. You are only allowed to remove entries from it."),
title=_("Not Allowed"))
def before_submit(self):
@@ -31,7 +31,7 @@
clear_notifications()
self.delete_company_transactions()
- def populate_doctypes_to_be_ignored_table(self):
+ def populate_doctypes_to_be_ignored_table(self):
doctypes_to_be_ignored_list = get_doctypes_to_be_ignored()
for doctype in doctypes_to_be_ignored_list:
self.append('doctypes_to_be_ignored', {
@@ -74,7 +74,7 @@
doctypes_to_be_ignored_list = self.get_doctypes_to_be_ignored_list()
docfields = self.get_doctypes_with_company_field(doctypes_to_be_ignored_list)
- tables = self.get_all_child_doctypes()
+ tables = self.get_all_child_doctypes()
for docfield in docfields:
if docfield['parent'] != self.doctype:
no_of_docs = self.get_number_of_docs_linked_with_specified_company(docfield['parent'], docfield['fieldname'])
@@ -90,7 +90,7 @@
naming_series = frappe.db.get_value('DocType', docfield['parent'], 'autoname')
if naming_series:
if '#' in naming_series:
- self.update_naming_series(naming_series, docfield['parent'])
+ self.update_naming_series(naming_series, docfield['parent'])
def get_doctypes_to_be_ignored_list(self):
singles = frappe.get_all('DocType', filters = {'issingle': 1}, pluck = 'name')
@@ -101,9 +101,9 @@
return doctypes_to_be_ignored_list
def get_doctypes_with_company_field(self, doctypes_to_be_ignored_list):
- docfields = frappe.get_all('DocField',
+ docfields = frappe.get_all('DocField',
filters = {
- 'fieldtype': 'Link',
+ 'fieldtype': 'Link',
'options': 'Company',
'parent': ['not in', doctypes_to_be_ignored_list]},
fields=['parent', 'fieldname'])
@@ -121,7 +121,7 @@
self.append('doctypes', {
'doctype_name' : doctype,
'no_of_docs' : no_of_docs
- })
+ })
def delete_child_tables(self, doctype, company_fieldname):
parent_docs_to_be_deleted = frappe.get_all(doctype, {
@@ -129,7 +129,7 @@
}, pluck = 'name')
child_tables = frappe.get_all('DocField', filters = {
- 'fieldtype': 'Table',
+ 'fieldtype': 'Table',
'parent': doctype
}, pluck = 'options')
diff --git a/erpnext/setup/utils.py b/erpnext/setup/utils.py
index d5dbd4c..e49259e 100644
--- a/erpnext/setup/utils.py
+++ b/erpnext/setup/utils.py
@@ -93,21 +93,21 @@
try:
cache = frappe.cache()
- key = "currency_exchange_rate_{0}:{1}:{2}".format(transaction_date,from_currency, to_currency)
+ key = "currency_exchange_rate_{0}:{1}:{2}".format(transaction_date, from_currency, to_currency)
value = cache.get(key)
if not value:
import requests
- api_url = "https://frankfurter.app/{0}".format(transaction_date)
+ api_url = "https://api.exchangerate.host/convert"
response = requests.get(api_url, params={
- "base": from_currency,
- "symbols": to_currency
+ "date": transaction_date,
+ "from": from_currency,
+ "to": to_currency
})
# expire in 6 hours
response.raise_for_status()
- value = response.json()["rates"][to_currency]
-
- cache.set_value(key, value, expires_in_sec=6 * 60 * 60)
+ value = response.json()["result"]
+ cache.setex(name=key, time=21600, value=flt(value))
return flt(value)
except:
frappe.log_error(title="Get Exchange Rate")
diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py
index b6eef6c..b37ae3f 100644
--- a/erpnext/stock/doctype/batch/batch.py
+++ b/erpnext/stock/doctype/batch/batch.py
@@ -162,19 +162,19 @@
out = float(frappe.db.sql("""select sum(actual_qty)
from `tabStock Ledger Entry`
- where warehouse=%s and batch_no=%s {0}""".format(cond),
+ where is_cancelled = 0 and warehouse=%s and batch_no=%s {0}""".format(cond),
(warehouse, batch_no))[0][0] or 0)
if batch_no and not warehouse:
out = frappe.db.sql('''select warehouse, sum(actual_qty) as qty
from `tabStock Ledger Entry`
- where batch_no=%s
+ where is_cancelled = 0 and batch_no=%s
group by warehouse''', batch_no, as_dict=1)
if not batch_no and item_code and warehouse:
out = frappe.db.sql('''select batch_no, sum(actual_qty) as qty
from `tabStock Ledger Entry`
- where item_code = %s and warehouse=%s
+ where is_cancelled = 0 and item_code = %s and warehouse=%s
group by batch_no''', (item_code, warehouse), as_dict=1)
return out
diff --git a/erpnext/stock/doctype/batch/test_batch.py b/erpnext/stock/doctype/batch/test_batch.py
index cbd272d..a85a022 100644
--- a/erpnext/stock/doctype/batch/test_batch.py
+++ b/erpnext/stock/doctype/batch/test_batch.py
@@ -269,11 +269,14 @@
batch2 = create_batch('_Test Batch Price Item', 300, 1)
batch3 = create_batch('_Test Batch Price Item', 400, 0)
+ company = "_Test Company with perpetual inventory"
+ currency = frappe.get_cached_value("Company", company, "default_currency")
+
args = frappe._dict({
"item_code": "_Test Batch Price Item",
- "company": "_Test Company with perpetual inventory",
+ "company": company,
"price_list": "_Test Price List",
- "currency": "_Test Currency",
+ "currency": currency,
"doctype": "Sales Invoice",
"conversion_rate": 1,
"price_list_currency": "_Test Currency",
@@ -333,4 +336,4 @@
except frappe.DuplicateEntryError:
batch = frappe.get_doc("Batch", args.batch_id)
- return batch
\ No newline at end of file
+ return batch
diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js
index 87bd9e6..fd080fd 100644
--- a/erpnext/stock/doctype/item/item.js
+++ b/erpnext/stock/doctype/item/item.js
@@ -138,20 +138,6 @@
frm.toggle_reqd('customer', frm.doc.is_customer_provided_item ? 1:0);
},
- gst_hsn_code: function(frm) {
- if((!frm.doc.taxes || !frm.doc.taxes.length) && frm.doc.gst_hsn_code) {
- frappe.db.get_doc("GST HSN Code", frm.doc.gst_hsn_code).then(hsn_doc => {
- $.each(hsn_doc.taxes || [], function(i, tax) {
- let a = frappe.model.add_child(cur_frm.doc, 'Item Tax', 'taxes');
- a.item_tax_template = tax.item_tax_template;
- a.tax_category = tax.tax_category;
- a.valid_from = tax.valid_from;
- frm.refresh_field('taxes');
- });
- });
- }
- },
-
is_fixed_asset: function(frm) {
// set serial no to false & toggles its visibility
frm.set_value('has_serial_no', 0);
diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py
index 42cc67c..614c53a 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -123,6 +123,7 @@
self.cant_change()
self.update_show_in_website()
self.validate_item_tax_net_rate_range()
+ set_item_tax_from_hsn_code(self)
if not self.is_new():
self.old_item_group = frappe.db.get_value(self.doctype, self.name, "item_group")
@@ -1305,3 +1306,7 @@
def on_doctype_update():
# since route is a Text column, it needs a length for indexing
frappe.db.add_index("Item", ["route(500)"])
+
+@erpnext.allow_regional
+def set_item_tax_from_hsn_code(item):
+ pass
\ No newline at end of file
diff --git a/erpnext/stock/doctype/item/regional/india.js b/erpnext/stock/doctype/item/regional/india.js
new file mode 100644
index 0000000..77ae51f
--- /dev/null
+++ b/erpnext/stock/doctype/item/regional/india.js
@@ -0,0 +1,15 @@
+frappe.ui.form.on('Item', {
+ gst_hsn_code: function(frm) {
+ if ((!frm.doc.taxes || !frm.doc.taxes.length) && frm.doc.gst_hsn_code) {
+ frappe.db.get_doc("GST HSN Code", frm.doc.gst_hsn_code).then(hsn_doc => {
+ $.each(hsn_doc.taxes || [], function(i, tax) {
+ let a = frappe.model.add_child(cur_frm.doc, 'Item Tax', 'taxes');
+ a.item_tax_template = tax.item_tax_template;
+ a.tax_category = tax.tax_category;
+ a.valid_from = tax.valid_from;
+ frm.refresh_field('taxes');
+ });
+ });
+ }
+ },
+});
\ No newline at end of file
diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py
index 922049f..7a9985d 100644
--- a/erpnext/stock/doctype/item/test_item.py
+++ b/erpnext/stock/doctype/item/test_item.py
@@ -83,14 +83,17 @@
make_test_objects("Item Price")
+ company = "_Test Company"
+ currency = frappe.get_cached_value("Company", company, "default_currency")
+
details = get_item_details({
"item_code": "_Test Item",
- "company": "_Test Company",
+ "company": company,
"price_list": "_Test Price List",
- "currency": "_Test Currency",
+ "currency": currency,
"doctype": "Sales Order",
"conversion_rate": 1,
- "price_list_currency": "_Test Currency",
+ "price_list_currency": currency,
"plc_conversion_rate": 1,
"order_type": "Sales",
"customer": "_Test Customer",
diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py
index e795742..516ae43 100644
--- a/erpnext/stock/doctype/pick_list/pick_list.py
+++ b/erpnext/stock/doctype/pick_list/pick_list.py
@@ -239,6 +239,7 @@
and sle.`item_code`=%(item_code)s
and sle.`company` = %(company)s
and batch.disabled = 0
+ and sle.is_cancelled=0
and IFNULL(batch.`expiry_date`, '2200-01-01') > %(today)s
{warehouse_condition}
GROUP BY
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
index 48da785..899d7e8 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -352,7 +352,7 @@
if self.is_return or flt(d.item_tax_amount):
loss_account = expenses_included_in_valuation
else:
- loss_account = self.get_company_default("default_expense_account")
+ loss_account = self.get_company_default("default_expense_account", ignore_validation=True) or stock_rbnb
cost_center = d.cost_center or frappe.get_cached_value("Company", self.company, "cost_center")
diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
index dbba21f..bb4a710 100644
--- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
@@ -23,9 +23,7 @@
def test_reverse_purchase_receipt_sle(self):
- frappe.db.set_value('UOM', '_Test UOM', 'must_be_whole_number', 0)
-
- pr = make_purchase_receipt(qty=0.5)
+ pr = make_purchase_receipt(qty=0.5, item_code="_Test Item Home Desktop 200")
sl_entry = frappe.db.get_all("Stock Ledger Entry", {"voucher_type": "Purchase Receipt",
"voucher_no": pr.name}, ['actual_qty'])
@@ -41,8 +39,6 @@
self.assertEqual(len(sl_entry_cancelled), 2)
self.assertEqual(sl_entry_cancelled[1].actual_qty, -0.5)
- frappe.db.set_value('UOM', '_Test UOM', 'must_be_whole_number', 1)
-
def test_make_purchase_invoice(self):
if not frappe.db.exists('Payment Terms Template', '_Test Payment Terms Template For Purchase Invoice'):
frappe.get_doc({
@@ -336,10 +332,13 @@
se3.cancel()
po.reload()
pr2.load_from_db()
- pr2.cancel()
- po.load_from_db()
- po.cancel()
+ if pr2.docstatus == 1 and frappe.db.get_value('Stock Ledger Entry',
+ {'voucher_no': pr2.name, 'is_cancelled': 0}, 'name'):
+ pr2.cancel()
+
+ po.load_from_db()
+ po.cancel()
def test_serial_no_supplier(self):
pr = make_purchase_receipt(item_code="_Test Serialized Item With Series", qty=1)
@@ -1044,7 +1043,7 @@
'account': srbnb_account,
'voucher_detail_no': pr.items[1].name
}, pluck="name")
-
+
# check if the entries are not merged into one
# seperate entries should be made since voucher_detail_no is different
self.assertEqual(len(item_one_gl_entry), 1)
@@ -1055,13 +1054,13 @@
def test_purchase_receipt_with_exchange_rate_difference(self):
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice as create_purchase_invoice
from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import make_purchase_receipt as create_purchase_receipt
-
+
pi = create_purchase_invoice(company="_Test Company with perpetual inventory",
cost_center = "Main - TCP1",
warehouse = "Stores - TCP1",
expense_account ="_Test Account Cost for Goods Sold - TCP1",
currency = "USD", conversion_rate = 70)
-
+
pr = create_purchase_receipt(pi.name)
pr.conversion_rate = 80
pr.items[0].purchase_invoice = pi.name
diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js
index b3e4286..4cd40bf 100644
--- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js
+++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js
@@ -29,13 +29,50 @@
};
});
}
+
+ frm.trigger('setup_realtime_progress');
},
+
+ setup_realtime_progress: function(frm) {
+ frappe.realtime.on('item_reposting_progress', data => {
+ if (frm.doc.name !== data.name) {
+ return;
+ }
+
+ if (frm.doc.status == 'In Progress') {
+ frm.doc.current_index = data.current_index;
+ frm.doc.items_to_be_repost = data.items_to_be_repost;
+
+ frm.dashboard.reset();
+ frm.trigger('show_reposting_progress');
+ }
+ });
+ },
+
refresh: function(frm) {
if (frm.doc.status == "Failed" && frm.doc.docstatus==1) {
frm.add_custom_button(__('Restart'), function () {
frm.trigger("restart_reposting");
}).addClass("btn-primary");
}
+
+ frm.trigger('show_reposting_progress');
+ },
+
+ show_reposting_progress: function(frm) {
+ var bars = [];
+
+ let total_count = frm.doc.items_to_be_repost ? JSON.parse(frm.doc.items_to_be_repost).length : 0;
+ let progress = flt(cint(frm.doc.current_index) / total_count * 100, 2) || 0.5;
+ var title = __('Reposting Completed {0}%', [progress]);
+
+ bars.push({
+ 'title': title,
+ 'width': progress + '%',
+ 'progress_class': 'progress-bar-success'
+ });
+
+ frm.dashboard.add_progress(__('Reposting Progress'), bars);
},
restart_reposting: function(frm) {
diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json
index 071fc86..a800bf8 100644
--- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json
+++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json
@@ -21,7 +21,10 @@
"allow_zero_rate",
"amended_from",
"error_section",
- "error_log"
+ "error_log",
+ "items_to_be_repost",
+ "distinct_item_and_warehouse",
+ "current_index"
],
"fields": [
{
@@ -142,12 +145,39 @@
"fieldname": "allow_zero_rate",
"fieldtype": "Check",
"label": "Allow Zero Rate"
+ },
+ {
+ "fieldname": "items_to_be_repost",
+ "fieldtype": "Code",
+ "hidden": 1,
+ "label": "Items to Be Repost",
+ "no_copy": 1,
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "distinct_item_and_warehouse",
+ "fieldtype": "Code",
+ "hidden": 1,
+ "label": "Distinct Item and Warehouse",
+ "no_copy": 1,
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "current_index",
+ "fieldtype": "Int",
+ "hidden": 1,
+ "label": "Current Index",
+ "no_copy": 1,
+ "print_hide": 1,
+ "read_only": 1
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2020-12-10 07:52:12.476589",
+ "modified": "2021-07-22 18:59:43.057878",
"modified_by": "Administrator",
"module": "Stock",
"name": "Repost Item Valuation",
diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
index 5f31d9c..b22759d 100644
--- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
+++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
@@ -81,7 +81,7 @@
def repost_sl_entries(doc):
if doc.based_on == 'Transaction':
repost_future_sle(voucher_type=doc.voucher_type, voucher_no=doc.voucher_no,
- allow_negative_stock=doc.allow_negative_stock, via_landed_cost_voucher=doc.via_landed_cost_voucher)
+ allow_negative_stock=doc.allow_negative_stock, via_landed_cost_voucher=doc.via_landed_cost_voucher, doc=doc)
else:
repost_future_sle(args=[frappe._dict({
"item_code": doc.item_code,
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index 395f339..8293319 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -1790,7 +1790,7 @@
from `tabBatch` b, `tabStock Ledger Entry` sle
where b.expiry_date <= %s
and b.expiry_date is not NULL
- and b.batch_id = sle.batch_no
+ and b.batch_id = sle.batch_no and sle.is_cancelled = 0
group by sle.warehouse, sle.item_code, sle.batch_no""",(nowdate()), as_dict=1)
@frappe.whitelist()
diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
index 93482e8..b4f4583 100644
--- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
+++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
@@ -60,7 +60,7 @@
if self.batch_no and not self.get("allow_negative_stock"):
batch_bal_after_transaction = flt(frappe.db.sql("""select sum(actual_qty)
from `tabStock Ledger Entry`
- where warehouse=%s and item_code=%s and batch_no=%s""",
+ where is_cancelled =0 and warehouse=%s and item_code=%s and batch_no=%s""",
(self.warehouse, self.item_code, self.batch_no))[0][0])
if batch_bal_after_transaction < 0:
@@ -152,7 +152,7 @@
last_transaction_time = frappe.db.sql("""
select MAX(timestamp(posting_date, posting_time)) as posting_time
from `tabStock Ledger Entry`
- where docstatus = 1 and item_code = %s
+ where docstatus = 1 and is_cancelled = 0 and item_code = %s
and warehouse = %s""", (self.item_code, self.warehouse))[0][0]
cur_doc_posting_datetime = "%s %s" % (self.posting_date, self.get("posting_time") or "00:00:00")
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index cf52803..be8508a 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -74,8 +74,7 @@
update_party_blanket_order(args, out)
-
- get_price_list_rate(args, item, out)
+ out.update(get_price_list_rate(args, item))
if args.customer and cint(args.is_pos):
out.update(get_pos_profile_item_details(args.company, args, update_data=True))
@@ -312,8 +311,8 @@
"transaction_date": args.get("transaction_date"),
"against_blanket_order": args.get("against_blanket_order"),
"bom_no": item.get("default_bom"),
- "weight_per_unit": args.get("weight_per_unit") or item.get("weight_per_unit"),
- "weight_uom": args.get("weight_uom") or item.get("weight_uom")
+ "weight_per_unit": item.get("weight_per_unit"),
+ "weight_uom": item.get("weight_uom")
})
if item.get("enable_deferred_revenue") or item.get("enable_deferred_expense"):
@@ -638,7 +637,10 @@
or item_group.get("default_supplier")
or brand.get("default_supplier"))
-def get_price_list_rate(args, item_doc, out):
+def get_price_list_rate(args, item_doc, out=None):
+ if out is None:
+ out = frappe._dict()
+
meta = frappe.get_meta(args.parenttype or args.doctype)
if meta.get_field("currency") or args.get('currency'):
@@ -651,17 +653,17 @@
if meta.get_field("currency"):
validate_conversion_rate(args, meta)
- price_list_rate = get_price_list_rate_for(args, item_doc.name) or 0
+ price_list_rate = get_price_list_rate_for(args, item_doc.name)
# variant
- if not price_list_rate and item_doc.variant_of:
+ if price_list_rate is None and item_doc.variant_of:
price_list_rate = get_price_list_rate_for(args, item_doc.variant_of)
# insert in database
- if not price_list_rate:
+ if price_list_rate is None:
if args.price_list and args.rate:
insert_item_price(args)
- return {}
+ return out
out.price_list_rate = flt(price_list_rate) * flt(args.plc_conversion_rate) \
/ flt(args.conversion_rate)
@@ -671,6 +673,8 @@
out.update(get_last_purchase_details(item_doc.name,
args.name, args.conversion_rate))
+ return out
+
def insert_item_price(args):
"""Insert Item Price if Price List and Price List Rate are specified and currency is the same"""
if frappe.db.get_value("Price List", args.price_list, "currency", cache=True) == args.currency \
@@ -1073,9 +1077,8 @@
}
def apply_price_list_on_item(args):
- item_details = frappe._dict()
item_doc = frappe.get_doc("Item", args.item_code)
- get_price_list_rate(args, item_doc, item_details)
+ item_details = get_price_list_rate(args, item_doc)
item_details.update(get_pricing_rule_for_item(args, item_details.price_list_rate))
diff --git a/erpnext/erpnext_integrations/doctype/shopify_log/__init__.py b/erpnext/stock/report/cogs_by_item_group/__init__.py
similarity index 100%
copy from erpnext/erpnext_integrations/doctype/shopify_log/__init__.py
copy to erpnext/stock/report/cogs_by_item_group/__init__.py
diff --git a/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.js b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.js
new file mode 100644
index 0000000..d7c50a6
--- /dev/null
+++ b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.js
@@ -0,0 +1,31 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+
+frappe.query_reports["COGS By Item Group"] = {
+ filters: [
+ {
+ label: __("Company"),
+ fieldname: "company",
+ fieldtype: "Link",
+ options: "Company",
+ mandatory: true,
+ default: frappe.defaults.get_user_default("Company"),
+ },
+ {
+ label: __("From Date"),
+ fieldname: "from_date",
+ fieldtype: "Date",
+ mandatory: true,
+ default: frappe.datetime.year_start(),
+ },
+ {
+ label: __("To Date"),
+ fieldname: "to_date",
+ fieldtype: "Date",
+ mandatory: true,
+ default: frappe.datetime.get_today(),
+ },
+ ]
+};
diff --git a/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.json b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.json
new file mode 100644
index 0000000..a14adf8
--- /dev/null
+++ b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.json
@@ -0,0 +1,32 @@
+{
+ "add_total_row": 0,
+ "columns": [],
+ "creation": "2021-06-02 18:59:19.830928",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 0,
+ "is_standard": "Yes",
+ "modified": "2021-06-02 18:59:55.470621",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "COGS By Item Group",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "GL Entry",
+ "report_name": "COGS By Item Group",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "Accounts User"
+ },
+ {
+ "role": "Accounts Manager"
+ },
+ {
+ "role": "Auditor"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py
new file mode 100644
index 0000000..9e5e63e
--- /dev/null
+++ b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py
@@ -0,0 +1,188 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from collections import OrderedDict
+import datetime
+from typing import Dict, List, Tuple, Union
+
+import frappe
+from frappe import _
+from frappe.utils import date_diff
+
+from erpnext.accounts.report.general_ledger.general_ledger import get_gl_entries
+
+
+Filters = frappe._dict
+Row = frappe._dict
+Data = List[Row]
+Columns = List[Dict[str, str]]
+DateTime = Union[datetime.date, datetime.datetime]
+FilteredEntries = List[Dict[str, Union[str, float, DateTime, None]]]
+ItemGroupsDict = Dict[Tuple[int, int], Dict[str, Union[str, int]]]
+SVDList = List[frappe._dict]
+
+
+def execute(filters: Filters) -> Tuple[Columns, Data]:
+ update_filters_with_account(filters)
+ validate_filters(filters)
+ columns = get_columns()
+ data = get_data(filters)
+ return columns, data
+
+
+def update_filters_with_account(filters: Filters) -> None:
+ account = frappe.get_value("Company", filters.get("company"), "default_expense_account")
+ filters.update(dict(account=account))
+
+
+def validate_filters(filters: Filters) -> None:
+ if filters.from_date > filters.to_date:
+ frappe.throw(_("From Date must be before To Date"))
+
+
+def get_columns() -> Columns:
+ return [
+ {
+ 'label': 'Item Group',
+ 'fieldname': 'item_group',
+ 'fieldtype': 'Data',
+ 'width': '200'
+ },
+ {
+ 'label': 'COGS Debit',
+ 'fieldname': 'cogs_debit',
+ 'fieldtype': 'Currency',
+ 'width': '200'
+ }
+ ]
+
+
+def get_data(filters: Filters) -> Data:
+ filtered_entries = get_filtered_entries(filters)
+ svd_list = get_stock_value_difference_list(filtered_entries)
+ leveled_dict = get_leveled_dict()
+
+ assign_self_values(leveled_dict, svd_list)
+ assign_agg_values(leveled_dict)
+
+ data = []
+ for item in leveled_dict.items():
+ i = item[1]
+ if i['agg_value'] == 0:
+ continue
+ data.append(get_row(i['name'], i['agg_value'], i['is_group'], i['level']))
+ if i['self_value'] < i['agg_value'] and i['self_value'] > 0:
+ data.append(get_row(i['name'], i['self_value'], 0, i['level'] + 1))
+ return data
+
+
+def get_filtered_entries(filters: Filters) -> FilteredEntries:
+ gl_entries = get_gl_entries(filters, [])
+ filtered_entries = []
+ for entry in gl_entries:
+ posting_date = entry.get('posting_date')
+ from_date = filters.get('from_date')
+ if date_diff(from_date, posting_date) > 0:
+ continue
+ filtered_entries.append(entry)
+ return filtered_entries
+
+
+def get_stock_value_difference_list(filtered_entries: FilteredEntries) -> SVDList:
+ voucher_nos = [fe.get('voucher_no') for fe in filtered_entries]
+ svd_list = frappe.get_list(
+ 'Stock Ledger Entry', fields=['item_code','stock_value_difference'],
+ filters=[('voucher_no', 'in', voucher_nos)]
+ )
+ assign_item_groups_to_svd_list(svd_list)
+ return svd_list
+
+
+def get_leveled_dict() -> OrderedDict:
+ item_groups_dict = get_item_groups_dict()
+ lr_list = sorted(item_groups_dict, key=lambda x : int(x[0]))
+ leveled_dict = OrderedDict()
+ current_level = 0
+ nesting_r = []
+ for l, r in lr_list:
+ while current_level > 0 and nesting_r[-1] < l:
+ nesting_r.pop()
+ current_level -= 1
+
+ leveled_dict[(l,r)] = {
+ 'level' : current_level,
+ 'name' : item_groups_dict[(l,r)]['name'],
+ 'is_group' : item_groups_dict[(l,r)]['is_group']
+ }
+
+ if int(r) - int(l) > 1:
+ current_level += 1
+ nesting_r.append(r)
+
+ update_leveled_dict(leveled_dict)
+ return leveled_dict
+
+
+def assign_self_values(leveled_dict: OrderedDict, svd_list: SVDList) -> None:
+ key_dict = {v['name']:k for k, v in leveled_dict.items()}
+ for item in svd_list:
+ key = key_dict[item.get("item_group")]
+ leveled_dict[key]['self_value'] += -item.get("stock_value_difference")
+
+
+def assign_agg_values(leveled_dict: OrderedDict) -> None:
+ keys = list(leveled_dict.keys())[::-1]
+ prev_level = leveled_dict[keys[-1]]['level']
+ accu = [0]
+ for k in keys[:-1]:
+ curr_level = leveled_dict[k]['level']
+ if curr_level == prev_level:
+ accu[-1] += leveled_dict[k]['self_value']
+ leveled_dict[k]['agg_value'] = leveled_dict[k]['self_value']
+
+ elif curr_level > prev_level:
+ accu.append(leveled_dict[k]['self_value'])
+ leveled_dict[k]['agg_value'] = accu[-1]
+
+ elif curr_level < prev_level:
+ accu[-1] += leveled_dict[k]['self_value']
+ leveled_dict[k]['agg_value'] = accu[-1]
+
+ prev_level = curr_level
+
+ # root node
+ rk = keys[-1]
+ leveled_dict[rk]['agg_value'] = sum(accu) + leveled_dict[rk]['self_value']
+
+
+def get_row(name:str, value:float, is_bold:int, indent:int) -> Row:
+ item_group = name
+ if is_bold:
+ item_group = frappe.bold(item_group)
+ return frappe._dict(item_group=item_group, cogs_debit=value, indent=indent)
+
+
+def assign_item_groups_to_svd_list(svd_list: SVDList) -> None:
+ ig_map = get_item_groups_map(svd_list)
+ for item in svd_list:
+ item.item_group = ig_map[item.get("item_code")]
+
+
+def get_item_groups_map(svd_list: SVDList) -> Dict[str, str]:
+ item_codes = set(i['item_code'] for i in svd_list)
+ ig_list = frappe.get_list(
+ 'Item', fields=['item_code','item_group'],
+ filters=[('item_code', 'in', item_codes)]
+ )
+ return {i['item_code']:i['item_group'] for i in ig_list}
+
+
+def get_item_groups_dict() -> ItemGroupsDict:
+ item_groups_list = frappe.get_all("Item Group", fields=("name", "is_group", "lft", "rgt"))
+ return {(i['lft'],i['rgt']):{'name':i['name'], 'is_group':i['is_group']}
+ for i in item_groups_list}
+
+
+def update_leveled_dict(leveled_dict: OrderedDict) -> None:
+ for k in leveled_dict:
+ leveled_dict[k].update({'self_value':0, 'agg_value':0})
diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py
index b6a8063..9e56ad4 100644
--- a/erpnext/stock/report/stock_balance/stock_balance.py
+++ b/erpnext/stock/report/stock_balance/stock_balance.py
@@ -16,8 +16,6 @@
is_reposting_item_valuation_in_progress()
if not filters: filters = {}
- validate_filters(filters)
-
from_date = filters.get('from_date')
to_date = filters.get('to_date')
@@ -295,12 +293,6 @@
return dict((d.parent + d.warehouse, d) for d in item_reorder_details)
-def validate_filters(filters):
- if not (filters.get("item_code") or filters.get("warehouse")):
- sle_count = flt(frappe.db.sql("""select count(name) from `tabStock Ledger Entry`""")[0][0])
- if sle_count > 500000:
- frappe.throw(_("Please set filter based on Item or Warehouse due to a large amount of entries."))
-
def get_variants_attributes():
'''Return all item variant attributes.'''
return [i.name for i in frappe.get_all('Item Attribute')]
diff --git a/erpnext/stock/report/supplier_wise_sales_analytics/supplier_wise_sales_analytics.py b/erpnext/stock/report/supplier_wise_sales_analytics/supplier_wise_sales_analytics.py
index 5873a7a..4108a57 100644
--- a/erpnext/stock/report/supplier_wise_sales_analytics/supplier_wise_sales_analytics.py
+++ b/erpnext/stock/report/supplier_wise_sales_analytics/supplier_wise_sales_analytics.py
@@ -69,7 +69,7 @@
i.stock_uom, sle.actual_qty, sle.stock_value_difference,
sle.voucher_no, sle.voucher_type
from `tabStock Ledger Entry` sle, `tabItem` i
- where sle.item_code=i.name and sle.actual_qty < 0 %s""" % conditions, values, as_dict=1):
+ where sle.is_cancelled = 0 and sle.item_code=i.name and sle.actual_qty < 0 %s""" % conditions, values, as_dict=1):
consumed_details.setdefault(d.item_code, []).append(d)
return consumed_details
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index c15d1ed..f990ce0 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -127,30 +127,26 @@
sle.submit()
return sle
-def repost_future_sle(args=None, voucher_type=None, voucher_no=None, allow_negative_stock=None, via_landed_cost_voucher=False):
+def repost_future_sle(args=None, voucher_type=None, voucher_no=None, allow_negative_stock=None, via_landed_cost_voucher=False, doc=None):
if not args and voucher_type and voucher_no:
- args = get_args_for_voucher(voucher_type, voucher_no)
+ args = get_items_to_be_repost(voucher_type, voucher_no, doc)
- distinct_item_warehouses = {}
- for i, d in enumerate(args):
- distinct_item_warehouses.setdefault((d.item_code, d.warehouse), frappe._dict({
- "reposting_status": False,
- "sle": d,
- "args_idx": i
- }))
+ distinct_item_warehouses = get_distinct_item_warehouse(args, doc)
- i = 0
+ i = get_current_index(doc) or 0
while i < len(args):
+ validate_item_warehouse(args[i])
+
obj = update_entries_after({
- "item_code": args[i].item_code,
- "warehouse": args[i].warehouse,
- "posting_date": args[i].posting_date,
- "posting_time": args[i].posting_time,
- "creation": args[i].get("creation"),
- "distinct_item_warehouses": distinct_item_warehouses
+ 'item_code': args[i].get('item_code'),
+ 'warehouse': args[i].get('warehouse'),
+ 'posting_date': args[i].get('posting_date'),
+ 'posting_time': args[i].get('posting_time'),
+ 'creation': args[i].get('creation'),
+ 'distinct_item_warehouses': distinct_item_warehouses
}, allow_negative_stock=allow_negative_stock, via_landed_cost_voucher=via_landed_cost_voucher)
- distinct_item_warehouses[(args[i].item_code, args[i].warehouse)].reposting_status = True
+ distinct_item_warehouses[(args[i].get('item_code'), args[i].get('warehouse'))].reposting_status = True
if obj.new_items_found:
for item_wh, data in iteritems(distinct_item_warehouses):
@@ -159,11 +155,41 @@
args.append(data.sle)
elif data.sle_changed and not data.reposting_status:
args[data.args_idx] = data.sle
-
+
data.sle_changed = False
i += 1
-def get_args_for_voucher(voucher_type, voucher_no):
+ if doc and i % 2 == 0:
+ update_args_in_repost_item_valuation(doc, i, args, distinct_item_warehouses)
+
+ if doc and args:
+ update_args_in_repost_item_valuation(doc, i, args, distinct_item_warehouses)
+
+def validate_item_warehouse(args):
+ for field in ['item_code', 'warehouse', 'posting_date', 'posting_time']:
+ if not args.get(field):
+ validation_msg = f'The field {frappe.unscrub(args.get(field))} is required for the reposting'
+ frappe.throw(_(validation_msg))
+
+def update_args_in_repost_item_valuation(doc, index, args, distinct_item_warehouses):
+ frappe.db.set_value(doc.doctype, doc.name, {
+ 'items_to_be_repost': json.dumps(args, default=str),
+ 'distinct_item_and_warehouse': json.dumps({str(k): v for k,v in distinct_item_warehouses.items()}, default=str),
+ 'current_index': index
+ })
+
+ frappe.db.commit()
+
+ frappe.publish_realtime('item_reposting_progress', {
+ 'name': doc.name,
+ 'items_to_be_repost': json.dumps(args, default=str),
+ 'current_index': index
+ })
+
+def get_items_to_be_repost(voucher_type, voucher_no, doc=None):
+ if doc and doc.items_to_be_repost:
+ return json.loads(doc.items_to_be_repost) or []
+
return frappe.db.get_all("Stock Ledger Entry",
filters={"voucher_type": voucher_type, "voucher_no": voucher_no},
fields=["item_code", "warehouse", "posting_date", "posting_time", "creation"],
@@ -171,6 +197,25 @@
group_by="item_code, warehouse"
)
+def get_distinct_item_warehouse(args=None, doc=None):
+ distinct_item_warehouses = {}
+ if doc and doc.distinct_item_and_warehouse:
+ distinct_item_warehouses = json.loads(doc.distinct_item_and_warehouse)
+ distinct_item_warehouses = {frappe.safe_eval(k): frappe._dict(v) for k, v in distinct_item_warehouses.items()}
+ else:
+ for i, d in enumerate(args):
+ distinct_item_warehouses.setdefault((d.item_code, d.warehouse), frappe._dict({
+ "reposting_status": False,
+ "sle": d,
+ "args_idx": i
+ }))
+
+ return distinct_item_warehouses
+
+def get_current_index(doc=None):
+ if doc and doc.current_index:
+ return doc.current_index
+
class update_entries_after(object):
"""
update valution rate and qty after transaction