Merge branch 'develop' into manufacturing-work-order-closed
diff --git a/erpnext/accounts/doctype/account/account_tree.js b/erpnext/accounts/doctype/account/account_tree.js
index b9ebb58..a3ef384 100644
--- a/erpnext/accounts/doctype/account/account_tree.js
+++ b/erpnext/accounts/doctype/account/account_tree.js
@@ -176,7 +176,7 @@
&& node.expandable && !node.hide_add;
},
click: function() {
- var me = frappe.treeview_settings['Account'].treeview;
+ var me = frappe.views.trees['Account'];
me.new_node();
},
btnClass: "hidden-xs"
diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py
index aa132a0..7451917 100644
--- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py
+++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py
@@ -19,6 +19,9 @@
frappe.db.set_default("add_taxes_from_item_tax_template",
self.get("add_taxes_from_item_tax_template", 0))
+ frappe.db.set_default("enable_common_party_accounting",
+ self.get("enable_common_party_accounting", 0))
+
self.validate_stale_days()
self.enable_payment_schedule_in_print()
self.toggle_discount_accounting_fields()
diff --git a/erpnext/accounts/doctype/party_link/party_link.py b/erpnext/accounts/doctype/party_link/party_link.py
index daf667c..e9f813c 100644
--- a/erpnext/accounts/doctype/party_link/party_link.py
+++ b/erpnext/accounts/doctype/party_link/party_link.py
@@ -25,3 +25,17 @@
if existing_party_link:
frappe.throw(_('{} {} is already linked with another {}')
.format(self.primary_role, self.primary_party, existing_party_link[0]))
+
+
+@frappe.whitelist()
+def create_party_link(primary_role, primary_party, secondary_party):
+ party_link = frappe.new_doc('Party Link')
+ party_link.primary_role = primary_role
+ party_link.primary_party = primary_party
+ party_link.secondary_role = 'Customer' if primary_role == 'Supplier' else 'Supplier'
+ party_link.secondary_party = secondary_party
+
+ party_link.save(ignore_permissions=True)
+
+ return party_link
+
diff --git a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py
index 34572fd..d0e555e 100644
--- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py
+++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py
@@ -88,9 +88,10 @@
for acc in pl_accounts:
if flt(acc.bal_in_company_currency):
+ cost_center = acc.cost_center if self.cost_center_wise_pnl else company_cost_center
gl_entry = self.get_gl_dict({
"account": self.closing_account_head,
- "cost_center": acc.cost_center or company_cost_center,
+ "cost_center": cost_center,
"finance_book": acc.finance_book,
"account_currency": acc.account_currency,
"debit_in_account_currency": abs(flt(acc.bal_in_account_currency)) if flt(acc.bal_in_account_currency) > 0 else 0,
diff --git a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py
index 0e29755..030b4ca 100644
--- a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py
+++ b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py
@@ -66,8 +66,8 @@
company = create_company()
surplus_account = create_account()
- cost_center1 = create_cost_center("Test Cost Center 1")
- cost_center2 = create_cost_center("Test Cost Center 2")
+ cost_center1 = create_cost_center("Main")
+ cost_center2 = create_cost_center("Western Branch")
create_sales_invoice(
company=company,
@@ -86,7 +86,10 @@
debit_to="Debtors - TPC"
)
- pcv = self.make_period_closing_voucher()
+ pcv = self.make_period_closing_voucher(submit=False)
+ pcv.cost_center_wise_pnl = 1
+ pcv.save()
+ pcv.submit()
surplus_account = pcv.closing_account_head
expected_gle = (
@@ -149,7 +152,7 @@
self.assertEqual(pcv_gle, expected_gle)
- def make_period_closing_voucher(self):
+ def make_period_closing_voucher(self, submit=True):
surplus_account = create_account()
cost_center = create_cost_center("Test Cost Center 1")
pcv = frappe.get_doc({
@@ -163,7 +166,8 @@
"remarks": "test"
})
pcv.insert()
- pcv.submit()
+ if submit:
+ pcv.submit()
return pcv
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index 02e2416..b5453ac 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -2300,6 +2300,7 @@
from erpnext.accounts.doctype.opening_invoice_creation_tool.test_opening_invoice_creation_tool import (
make_customer,
)
+ from erpnext.accounts.doctype.party_link.party_link import create_party_link
from erpnext.buying.doctype.supplier.test_supplier import create_supplier
# create a customer
@@ -2308,13 +2309,7 @@
supplier = create_supplier(supplier_name="_Test Common Supplier").name
# create a party link between customer & supplier
- # set primary role as supplier
- party_link = frappe.new_doc("Party Link")
- party_link.primary_role = "Supplier"
- party_link.primary_party = supplier
- party_link.secondary_role = "Customer"
- party_link.secondary_party = customer
- party_link.save()
+ party_link = create_party_link("Supplier", supplier, customer)
# enable common party accounting
frappe.db.set_value('Accounts Settings', None, 'enable_common_party_accounting', 1)
diff --git a/erpnext/accounts/report/gross_profit/gross_profit.js b/erpnext/accounts/report/gross_profit/gross_profit.js
index 856b97d..685f2d6 100644
--- a/erpnext/accounts/report/gross_profit/gross_profit.js
+++ b/erpnext/accounts/report/gross_profit/gross_profit.js
@@ -44,7 +44,7 @@
"formatter": function(value, row, column, data, default_formatter) {
value = default_formatter(value, row, column, data);
- if (data && data.indent == 0.0) {
+ if (data && (data.indent == 0.0 || row[1].content == "Total")) {
value = $(`<span>${value}</span>`);
var $value = $(value).css("font-weight", "bold");
value = $value.wrap("<p></p>").parent().html();
diff --git a/erpnext/accounts/report/gross_profit/gross_profit.json b/erpnext/accounts/report/gross_profit/gross_profit.json
index 5fff3fd..76c560a 100644
--- a/erpnext/accounts/report/gross_profit/gross_profit.json
+++ b/erpnext/accounts/report/gross_profit/gross_profit.json
@@ -9,7 +9,7 @@
"filters": [],
"idx": 3,
"is_standard": "Yes",
- "modified": "2021-08-19 18:57:07.468202",
+ "modified": "2021-11-13 19:14:23.730198",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Gross Profit",
diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py
index 9d5a242..20bc3ec 100644
--- a/erpnext/accounts/report/gross_profit/gross_profit.py
+++ b/erpnext/accounts/report/gross_profit/gross_profit.py
@@ -19,7 +19,7 @@
data = []
group_wise_columns = frappe._dict({
- "invoice": ["parent", "customer", "customer_group", "posting_date","item_code", "item_name","item_group", "brand", "description", \
+ "invoice": ["invoice_or_item", "customer", "customer_group", "posting_date","item_code", "item_name","item_group", "brand", "description",
"warehouse", "qty", "base_rate", "buying_rate", "base_amount",
"buying_amount", "gross_profit", "gross_profit_percent", "project"],
"item_code": ["item_code", "item_name", "brand", "description", "qty", "base_rate",
@@ -77,13 +77,15 @@
row.append(filters.currency)
if idx == len(gross_profit_data.grouped_data)-1:
- row[0] = frappe.bold("Total")
+ row[0] = "Total"
+
data.append(row)
def get_columns(group_wise_columns, filters):
columns = []
column_map = frappe._dict({
"parent": _("Sales Invoice") + ":Link/Sales Invoice:120",
+ "invoice_or_item": _("Sales Invoice") + ":Link/Sales Invoice:120",
"posting_date": _("Posting Date") + ":Date:100",
"posting_time": _("Posting Time") + ":Data:100",
"item_code": _("Item Code") + ":Link/Item:100",
@@ -122,7 +124,7 @@
def get_column_names():
return frappe._dict({
- 'parent': 'sales_invoice',
+ 'invoice_or_item': 'sales_invoice',
'customer': 'customer',
'customer_group': 'customer_group',
'posting_date': 'posting_date',
@@ -245,19 +247,28 @@
self.add_to_totals(new_row)
else:
for i, row in enumerate(self.grouped[key]):
- if row.parent in self.returned_invoices \
- and row.item_code in self.returned_invoices[row.parent]:
- returned_item_rows = self.returned_invoices[row.parent][row.item_code]
- for returned_item_row in returned_item_rows:
- row.qty += flt(returned_item_row.qty)
- row.base_amount += flt(returned_item_row.base_amount, self.currency_precision)
- row.buying_amount = flt(flt(row.qty) * flt(row.buying_rate), self.currency_precision)
- if (flt(row.qty) or row.base_amount) and self.is_not_invoice_row(row):
- row = self.set_average_rate(row)
- self.grouped_data.append(row)
- self.add_to_totals(row)
+ if row.indent == 1.0:
+ if row.parent in self.returned_invoices \
+ and row.item_code in self.returned_invoices[row.parent]:
+ returned_item_rows = self.returned_invoices[row.parent][row.item_code]
+ for returned_item_row in returned_item_rows:
+ row.qty += flt(returned_item_row.qty)
+ row.base_amount += flt(returned_item_row.base_amount, self.currency_precision)
+ row.buying_amount = flt(flt(row.qty) * flt(row.buying_rate), self.currency_precision)
+ if (flt(row.qty) or row.base_amount):
+ row = self.set_average_rate(row)
+ self.grouped_data.append(row)
+ self.add_to_totals(row)
+
self.set_average_gross_profit(self.totals)
- self.grouped_data.append(self.totals)
+
+ if self.filters.get("group_by") == "Invoice":
+ self.totals.indent = 0.0
+ self.totals.parent_invoice = ""
+ self.totals.parent = "Total"
+ self.si_list.append(self.totals)
+ else:
+ self.grouped_data.append(self.totals)
def is_not_invoice_row(self, row):
return (self.filters.get("group_by") == "Invoice" and row.indent != 0.0) or self.filters.get("group_by") != "Invoice"
@@ -446,7 +457,7 @@
if not row.indent:
row.indent = 1.0
row.parent_invoice = row.parent
- row.parent = row.item_code
+ row.invoice_or_item = row.item_code
if frappe.db.exists('Product Bundle', row.item_code):
self.add_bundle_items(row, index)
@@ -455,7 +466,8 @@
return frappe._dict({
'parent_invoice': "",
'indent': 0.0,
- 'parent': row.parent,
+ 'invoice_or_item': row.parent,
+ 'parent': None,
'posting_date': row.posting_date,
'posting_time': row.posting_time,
'project': row.project,
@@ -499,7 +511,8 @@
return frappe._dict({
'parent_invoice': product_bundle.item_code,
'indent': product_bundle.indent + 1,
- 'parent': item.item_code,
+ 'parent': None,
+ 'invoice_or_item': item.item_code,
'posting_date': product_bundle.posting_date,
'posting_time': product_bundle.posting_time,
'project': product_bundle.project,
diff --git a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.js b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.js
index 79c8861..36f510b 100644
--- a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.js
+++ b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.js
@@ -14,6 +14,14 @@
}
}
});
+ frm.set_query('asset', function() {
+ return {
+ filters: {
+ calculate_depreciation: 1,
+ docstatus: 1
+ }
+ };
+ });
},
onload: function(frm) {
diff --git a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py
index b93f474..0b646ed 100644
--- a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py
+++ b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py
@@ -10,7 +10,11 @@
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_checks_for_pl_and_bs_accounts,
)
+from erpnext.assets.doctype.asset.asset import get_depreciation_amount
from erpnext.assets.doctype.asset.depreciation import get_depreciation_accounts
+from erpnext.regional.india.utils import (
+ get_depreciation_amount as get_depreciation_amount_for_india,
+)
class AssetValueAdjustment(Document):
@@ -90,6 +94,7 @@
def reschedule_depreciations(self, asset_value):
asset = frappe.get_doc('Asset', self.asset)
+ country = frappe.get_value('Company', self.company, 'country')
for d in asset.finance_books:
d.value_after_depreciation = asset_value
@@ -111,8 +116,10 @@
depreciation_amount = days * rate_per_day
from_date = data.schedule_date
else:
- depreciation_amount = asset.get_depreciation_amount(value_after_depreciation,
- no_of_depreciations, d)
+ if country == "India":
+ depreciation_amount = get_depreciation_amount_for_india(asset, value_after_depreciation, d)
+ else:
+ depreciation_amount = get_depreciation_amount(asset, value_after_depreciation, d)
if depreciation_amount:
value_after_depreciation -= flt(depreciation_amount)
diff --git a/erpnext/buying/doctype/supplier/supplier.js b/erpnext/buying/doctype/supplier/supplier.js
index 7ee9196..f0899b0 100644
--- a/erpnext/buying/doctype/supplier/supplier.js
+++ b/erpnext/buying/doctype/supplier/supplier.js
@@ -83,6 +83,12 @@
frm.trigger("get_supplier_group_details");
}, __('Actions'));
+ if (cint(frappe.defaults.get_default("enable_common_party_accounting"))) {
+ frm.add_custom_button(__('Link with Customer'), function () {
+ frm.trigger('show_party_link_dialog');
+ }, __('Actions'));
+ }
+
// indicators
erpnext.utils.set_party_dashboard_indicators(frm);
}
@@ -128,5 +134,42 @@
else {
frm.toggle_reqd("represents_company", false);
}
+ },
+ show_party_link_dialog: function(frm) {
+ const dialog = new frappe.ui.Dialog({
+ title: __('Select a Customer'),
+ fields: [{
+ fieldtype: 'Link', label: __('Customer'),
+ options: 'Customer', fieldname: 'customer', reqd: 1
+ }],
+ primary_action: function({ customer }) {
+ frappe.call({
+ method: 'erpnext.accounts.doctype.party_link.party_link.create_party_link',
+ args: {
+ primary_role: 'Supplier',
+ primary_party: frm.doc.name,
+ secondary_party: customer
+ },
+ freeze: true,
+ callback: function() {
+ dialog.hide();
+ frappe.msgprint({
+ message: __('Successfully linked to Customer'),
+ alert: true
+ });
+ },
+ error: function() {
+ dialog.hide();
+ frappe.msgprint({
+ message: __('Linking to Customer Failed. Please try again.'),
+ title: __('Linking Failed'),
+ indicator: 'red'
+ });
+ }
+ });
+ },
+ primary_action_label: __('Create Link')
+ });
+ dialog.show();
}
});
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index 08d422d..aba15b4 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -676,5 +676,6 @@
repost_entry.company = args.company
repost_entry.allow_zero_rate = args.allow_zero_rate
repost_entry.flags.ignore_links = True
+ repost_entry.flags.ignore_permissions = True
repost_entry.save()
repost_entry.submit()
diff --git a/erpnext/regional/saudi_arabia/utils.py b/erpnext/regional/saudi_arabia/utils.py
index cc6c0af..0c036f9 100644
--- a/erpnext/regional/saudi_arabia/utils.py
+++ b/erpnext/regional/saudi_arabia/utils.py
@@ -28,14 +28,22 @@
for field in meta.get_image_fields():
if field.fieldname == 'qr_code':
+ from urllib.parse import urlencode
+
# Creating public url to print format
default_print_format = frappe.db.get_value('Property Setter', dict(property='default_print_format', doc_type=doc.doctype), "value")
# System Language
language = frappe.get_system_settings('language')
+ params = urlencode({
+ 'format': default_print_format or 'Standard',
+ '_lang': language,
+ 'key': doc.get_signature()
+ })
+
# creating qr code for the url
- url = f"{ frappe.utils.get_url() }/{ doc.doctype }/{ doc.name }?format={ default_print_format or 'Standard' }&_lang={ language }&key={ doc.get_signature() }"
+ url = f"{ frappe.utils.get_url() }/{ doc.doctype }/{ doc.name }?{ params }"
qr_image = io.BytesIO()
url = qr_create(url, error='L')
url.png(qr_image, scale=2, quiet_zone=1)
diff --git a/erpnext/selling/doctype/customer/customer.js b/erpnext/selling/doctype/customer/customer.js
index 4b0bbd5..107e4a4 100644
--- a/erpnext/selling/doctype/customer/customer.js
+++ b/erpnext/selling/doctype/customer/customer.js
@@ -134,6 +134,12 @@
frm.trigger("get_customer_group_details");
}, __('Actions'));
+ if (cint(frappe.defaults.get_default("enable_common_party_accounting"))) {
+ frm.add_custom_button(__('Link with Supplier'), function () {
+ frm.trigger('show_party_link_dialog');
+ }, __('Actions'));
+ }
+
// indicator
erpnext.utils.set_party_dashboard_indicators(frm);
@@ -158,5 +164,42 @@
}
});
+ },
+ show_party_link_dialog: function(frm) {
+ const dialog = new frappe.ui.Dialog({
+ title: __('Select a Supplier'),
+ fields: [{
+ fieldtype: 'Link', label: __('Supplier'),
+ options: 'Supplier', fieldname: 'supplier', reqd: 1
+ }],
+ primary_action: function({ supplier }) {
+ frappe.call({
+ method: 'erpnext.accounts.doctype.party_link.party_link.create_party_link',
+ args: {
+ primary_role: 'Customer',
+ primary_party: frm.doc.name,
+ secondary_party: supplier
+ },
+ freeze: true,
+ callback: function() {
+ dialog.hide();
+ frappe.msgprint({
+ message: __('Successfully linked to Supplier'),
+ alert: true
+ });
+ },
+ error: function() {
+ dialog.hide();
+ frappe.msgprint({
+ message: __('Linking to Supplier Failed. Please try again.'),
+ title: __('Linking Failed'),
+ indicator: 'red'
+ });
+ }
+ });
+ },
+ primary_action_label: __('Create Link')
+ });
+ dialog.show();
}
});
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 a800bf8..3ff0f60 100644
--- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json
+++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json
@@ -177,10 +177,11 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2021-07-22 18:59:43.057878",
+ "modified": "2021-11-18 02:18:10.524560",
"modified_by": "Administrator",
"module": "Stock",
"name": "Repost Item Valuation",
+ "naming_rule": "Expression (old style)",
"owner": "Administrator",
"permissions": [
{
@@ -206,27 +207,12 @@
"print": 1,
"read": 1,
"report": 1,
- "role": "Stock User",
- "share": 1,
- "submit": 1,
- "write": 1
- },
- {
- "cancel": 1,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "print": 1,
- "read": 1,
- "report": 1,
"role": "Stock Manager",
"share": 1,
"submit": 1,
"write": 1
},
{
- "cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
@@ -234,7 +220,7 @@
"print": 1,
"read": 1,
"report": 1,
- "role": "Accounts User",
+ "role": "Accounts Manager",
"share": 1,
"submit": 1,
"write": 1
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 170aa7f..59d191f 100644
--- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
+++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
@@ -133,7 +133,7 @@
riv_entries = get_repost_item_valuation_entries()
for row in riv_entries:
- doc = frappe.get_cached_doc('Repost Item Valuation', row.name)
+ doc = frappe.get_doc('Repost Item Valuation', row.name)
repost(doc)
riv_entries = get_repost_item_valuation_entries()
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index 9c4c676..9d40982 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -111,6 +111,7 @@
frappe.throw(_("Cannot cancel the transaction. Reposting of item valuation on submission is not completed yet."))
if repost_entry.status == 'Queued':
doc = frappe.get_doc("Repost Item Valuation", repost_entry.name)
+ doc.flags.ignore_permissions = True
doc.cancel()
doc.delete()