Merge branch 'develop' into manufacturing-work-order-closed

This commit is contained in:
Anupam Kumar 2021-11-22 12:56:15 +05:30 committed by GitHub
commit b8ef8bcde9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 182 additions and 55 deletions

View File

@ -176,7 +176,7 @@ frappe.treeview_settings["Account"] = {
&& node.expandable && !node.hide_add; && node.expandable && !node.hide_add;
}, },
click: function() { click: function() {
var me = frappe.treeview_settings['Account'].treeview; var me = frappe.views.trees['Account'];
me.new_node(); me.new_node();
}, },
btnClass: "hidden-xs" btnClass: "hidden-xs"

View File

@ -19,6 +19,9 @@ class AccountsSettings(Document):
frappe.db.set_default("add_taxes_from_item_tax_template", frappe.db.set_default("add_taxes_from_item_tax_template",
self.get("add_taxes_from_item_tax_template", 0)) 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.validate_stale_days()
self.enable_payment_schedule_in_print() self.enable_payment_schedule_in_print()
self.toggle_discount_accounting_fields() self.toggle_discount_accounting_fields()

View File

@ -25,3 +25,17 @@ class PartyLink(Document):
if existing_party_link: if existing_party_link:
frappe.throw(_('{} {} is already linked with another {}') frappe.throw(_('{} {} is already linked with another {}')
.format(self.primary_role, self.primary_party, existing_party_link[0])) .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

View File

@ -88,9 +88,10 @@ class PeriodClosingVoucher(AccountsController):
for acc in pl_accounts: for acc in pl_accounts:
if flt(acc.bal_in_company_currency): 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({ gl_entry = self.get_gl_dict({
"account": self.closing_account_head, "account": self.closing_account_head,
"cost_center": acc.cost_center or company_cost_center, "cost_center": cost_center,
"finance_book": acc.finance_book, "finance_book": acc.finance_book,
"account_currency": acc.account_currency, "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, "debit_in_account_currency": abs(flt(acc.bal_in_account_currency)) if flt(acc.bal_in_account_currency) > 0 else 0,

View File

@ -66,8 +66,8 @@ class TestPeriodClosingVoucher(unittest.TestCase):
company = create_company() company = create_company()
surplus_account = create_account() surplus_account = create_account()
cost_center1 = create_cost_center("Test Cost Center 1") cost_center1 = create_cost_center("Main")
cost_center2 = create_cost_center("Test Cost Center 2") cost_center2 = create_cost_center("Western Branch")
create_sales_invoice( create_sales_invoice(
company=company, company=company,
@ -86,7 +86,10 @@ class TestPeriodClosingVoucher(unittest.TestCase):
debit_to="Debtors - TPC" 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 surplus_account = pcv.closing_account_head
expected_gle = ( expected_gle = (
@ -149,7 +152,7 @@ class TestPeriodClosingVoucher(unittest.TestCase):
self.assertEqual(pcv_gle, expected_gle) self.assertEqual(pcv_gle, expected_gle)
def make_period_closing_voucher(self): def make_period_closing_voucher(self, submit=True):
surplus_account = create_account() surplus_account = create_account()
cost_center = create_cost_center("Test Cost Center 1") cost_center = create_cost_center("Test Cost Center 1")
pcv = frappe.get_doc({ pcv = frappe.get_doc({
@ -163,7 +166,8 @@ class TestPeriodClosingVoucher(unittest.TestCase):
"remarks": "test" "remarks": "test"
}) })
pcv.insert() pcv.insert()
pcv.submit() if submit:
pcv.submit()
return pcv return pcv

View File

@ -2300,6 +2300,7 @@ class TestSalesInvoice(unittest.TestCase):
from erpnext.accounts.doctype.opening_invoice_creation_tool.test_opening_invoice_creation_tool import ( from erpnext.accounts.doctype.opening_invoice_creation_tool.test_opening_invoice_creation_tool import (
make_customer, make_customer,
) )
from erpnext.accounts.doctype.party_link.party_link import create_party_link
from erpnext.buying.doctype.supplier.test_supplier import create_supplier from erpnext.buying.doctype.supplier.test_supplier import create_supplier
# create a customer # create a customer
@ -2308,13 +2309,7 @@ class TestSalesInvoice(unittest.TestCase):
supplier = create_supplier(supplier_name="_Test Common Supplier").name supplier = create_supplier(supplier_name="_Test Common Supplier").name
# create a party link between customer & supplier # create a party link between customer & supplier
# set primary role as supplier party_link = create_party_link("Supplier", supplier, customer)
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()
# enable common party accounting # enable common party accounting
frappe.db.set_value('Accounts Settings', None, 'enable_common_party_accounting', 1) frappe.db.set_value('Accounts Settings', None, 'enable_common_party_accounting', 1)

View File

@ -44,7 +44,7 @@ frappe.query_reports["Gross Profit"] = {
"formatter": function(value, row, column, data, default_formatter) { "formatter": function(value, row, column, data, default_formatter) {
value = default_formatter(value, row, column, data); 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>`); value = $(`<span>${value}</span>`);
var $value = $(value).css("font-weight", "bold"); var $value = $(value).css("font-weight", "bold");
value = $value.wrap("<p></p>").parent().html(); value = $value.wrap("<p></p>").parent().html();

View File

@ -9,7 +9,7 @@
"filters": [], "filters": [],
"idx": 3, "idx": 3,
"is_standard": "Yes", "is_standard": "Yes",
"modified": "2021-08-19 18:57:07.468202", "modified": "2021-11-13 19:14:23.730198",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Gross Profit", "name": "Gross Profit",

View File

@ -19,7 +19,7 @@ def execute(filters=None):
data = [] data = []
group_wise_columns = frappe._dict({ 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", "warehouse", "qty", "base_rate", "buying_rate", "base_amount",
"buying_amount", "gross_profit", "gross_profit_percent", "project"], "buying_amount", "gross_profit", "gross_profit_percent", "project"],
"item_code": ["item_code", "item_name", "brand", "description", "qty", "base_rate", "item_code": ["item_code", "item_name", "brand", "description", "qty", "base_rate",
@ -77,13 +77,15 @@ def get_data_when_not_grouped_by_invoice(gross_profit_data, filters, group_wise_
row.append(filters.currency) row.append(filters.currency)
if idx == len(gross_profit_data.grouped_data)-1: if idx == len(gross_profit_data.grouped_data)-1:
row[0] = frappe.bold("Total") row[0] = "Total"
data.append(row) data.append(row)
def get_columns(group_wise_columns, filters): def get_columns(group_wise_columns, filters):
columns = [] columns = []
column_map = frappe._dict({ column_map = frappe._dict({
"parent": _("Sales Invoice") + ":Link/Sales Invoice:120", "parent": _("Sales Invoice") + ":Link/Sales Invoice:120",
"invoice_or_item": _("Sales Invoice") + ":Link/Sales Invoice:120",
"posting_date": _("Posting Date") + ":Date:100", "posting_date": _("Posting Date") + ":Date:100",
"posting_time": _("Posting Time") + ":Data:100", "posting_time": _("Posting Time") + ":Data:100",
"item_code": _("Item Code") + ":Link/Item:100", "item_code": _("Item Code") + ":Link/Item:100",
@ -122,7 +124,7 @@ def get_columns(group_wise_columns, filters):
def get_column_names(): def get_column_names():
return frappe._dict({ return frappe._dict({
'parent': 'sales_invoice', 'invoice_or_item': 'sales_invoice',
'customer': 'customer', 'customer': 'customer',
'customer_group': 'customer_group', 'customer_group': 'customer_group',
'posting_date': 'posting_date', 'posting_date': 'posting_date',
@ -245,19 +247,28 @@ class GrossProfitGenerator(object):
self.add_to_totals(new_row) self.add_to_totals(new_row)
else: else:
for i, row in enumerate(self.grouped[key]): for i, row in enumerate(self.grouped[key]):
if row.parent in self.returned_invoices \ if row.indent == 1.0:
and row.item_code in self.returned_invoices[row.parent]: if row.parent in self.returned_invoices \
returned_item_rows = self.returned_invoices[row.parent][row.item_code] and row.item_code in self.returned_invoices[row.parent]:
for returned_item_row in returned_item_rows: returned_item_rows = self.returned_invoices[row.parent][row.item_code]
row.qty += flt(returned_item_row.qty) for returned_item_row in returned_item_rows:
row.base_amount += flt(returned_item_row.base_amount, self.currency_precision) row.qty += flt(returned_item_row.qty)
row.buying_amount = flt(flt(row.qty) * flt(row.buying_rate), self.currency_precision) row.base_amount += flt(returned_item_row.base_amount, self.currency_precision)
if (flt(row.qty) or row.base_amount) and self.is_not_invoice_row(row): row.buying_amount = flt(flt(row.qty) * flt(row.buying_rate), self.currency_precision)
row = self.set_average_rate(row) if (flt(row.qty) or row.base_amount):
self.grouped_data.append(row) row = self.set_average_rate(row)
self.add_to_totals(row) self.grouped_data.append(row)
self.add_to_totals(row)
self.set_average_gross_profit(self.totals) 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): 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" return (self.filters.get("group_by") == "Invoice" and row.indent != 0.0) or self.filters.get("group_by") != "Invoice"
@ -446,7 +457,7 @@ class GrossProfitGenerator(object):
if not row.indent: if not row.indent:
row.indent = 1.0 row.indent = 1.0
row.parent_invoice = row.parent 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): if frappe.db.exists('Product Bundle', row.item_code):
self.add_bundle_items(row, index) self.add_bundle_items(row, index)
@ -455,7 +466,8 @@ class GrossProfitGenerator(object):
return frappe._dict({ return frappe._dict({
'parent_invoice': "", 'parent_invoice': "",
'indent': 0.0, 'indent': 0.0,
'parent': row.parent, 'invoice_or_item': row.parent,
'parent': None,
'posting_date': row.posting_date, 'posting_date': row.posting_date,
'posting_time': row.posting_time, 'posting_time': row.posting_time,
'project': row.project, 'project': row.project,
@ -499,7 +511,8 @@ class GrossProfitGenerator(object):
return frappe._dict({ return frappe._dict({
'parent_invoice': product_bundle.item_code, 'parent_invoice': product_bundle.item_code,
'indent': product_bundle.indent + 1, 'indent': product_bundle.indent + 1,
'parent': item.item_code, 'parent': None,
'invoice_or_item': item.item_code,
'posting_date': product_bundle.posting_date, 'posting_date': product_bundle.posting_date,
'posting_time': product_bundle.posting_time, 'posting_time': product_bundle.posting_time,
'project': product_bundle.project, 'project': product_bundle.project,

View File

@ -14,6 +14,14 @@ frappe.ui.form.on('Asset Value Adjustment', {
} }
} }
}); });
frm.set_query('asset', function() {
return {
filters: {
calculate_depreciation: 1,
docstatus: 1
}
};
});
}, },
onload: function(frm) { onload: function(frm) {

View File

@ -10,7 +10,11 @@ from frappe.utils import cint, date_diff, flt, formatdate, getdate
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_checks_for_pl_and_bs_accounts, 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.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): class AssetValueAdjustment(Document):
@ -90,6 +94,7 @@ class AssetValueAdjustment(Document):
def reschedule_depreciations(self, asset_value): def reschedule_depreciations(self, asset_value):
asset = frappe.get_doc('Asset', self.asset) asset = frappe.get_doc('Asset', self.asset)
country = frappe.get_value('Company', self.company, 'country')
for d in asset.finance_books: for d in asset.finance_books:
d.value_after_depreciation = asset_value d.value_after_depreciation = asset_value
@ -111,8 +116,10 @@ class AssetValueAdjustment(Document):
depreciation_amount = days * rate_per_day depreciation_amount = days * rate_per_day
from_date = data.schedule_date from_date = data.schedule_date
else: else:
depreciation_amount = asset.get_depreciation_amount(value_after_depreciation, if country == "India":
no_of_depreciations, d) 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: if depreciation_amount:
value_after_depreciation -= flt(depreciation_amount) value_after_depreciation -= flt(depreciation_amount)

View File

@ -83,6 +83,12 @@ frappe.ui.form.on("Supplier", {
frm.trigger("get_supplier_group_details"); frm.trigger("get_supplier_group_details");
}, __('Actions')); }, __('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 // indicators
erpnext.utils.set_party_dashboard_indicators(frm); erpnext.utils.set_party_dashboard_indicators(frm);
} }
@ -128,5 +134,42 @@ frappe.ui.form.on("Supplier", {
else { else {
frm.toggle_reqd("represents_company", false); 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();
} }
}); });

View File

@ -676,5 +676,6 @@ def create_repost_item_valuation_entry(args):
repost_entry.company = args.company repost_entry.company = args.company
repost_entry.allow_zero_rate = args.allow_zero_rate repost_entry.allow_zero_rate = args.allow_zero_rate
repost_entry.flags.ignore_links = True repost_entry.flags.ignore_links = True
repost_entry.flags.ignore_permissions = True
repost_entry.save() repost_entry.save()
repost_entry.submit() repost_entry.submit()

View File

@ -28,14 +28,22 @@ def create_qr_code(doc, method):
for field in meta.get_image_fields(): for field in meta.get_image_fields():
if field.fieldname == 'qr_code': if field.fieldname == 'qr_code':
from urllib.parse import urlencode
# Creating public url to print format # 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") default_print_format = frappe.db.get_value('Property Setter', dict(property='default_print_format', doc_type=doc.doctype), "value")
# System Language # System Language
language = frappe.get_system_settings('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 # 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() qr_image = io.BytesIO()
url = qr_create(url, error='L') url = qr_create(url, error='L')
url.png(qr_image, scale=2, quiet_zone=1) url.png(qr_image, scale=2, quiet_zone=1)

View File

@ -134,6 +134,12 @@ frappe.ui.form.on("Customer", {
frm.trigger("get_customer_group_details"); frm.trigger("get_customer_group_details");
}, __('Actions')); }, __('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 // indicator
erpnext.utils.set_party_dashboard_indicators(frm); erpnext.utils.set_party_dashboard_indicators(frm);
@ -158,5 +164,42 @@ frappe.ui.form.on("Customer", {
} }
}); });
},
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();
} }
}); });

View File

@ -177,10 +177,11 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2021-07-22 18:59:43.057878", "modified": "2021-11-18 02:18:10.524560",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Repost Item Valuation", "name": "Repost Item Valuation",
"naming_rule": "Expression (old style)",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
@ -197,20 +198,6 @@
"submit": 1, "submit": 1,
"write": 1 "write": 1
}, },
{
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Stock User",
"share": 1,
"submit": 1,
"write": 1
},
{ {
"cancel": 1, "cancel": 1,
"create": 1, "create": 1,
@ -226,7 +213,6 @@
"write": 1 "write": 1
}, },
{ {
"cancel": 1,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
@ -234,7 +220,7 @@
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "Accounts User", "role": "Accounts Manager",
"share": 1, "share": 1,
"submit": 1, "submit": 1,
"write": 1 "write": 1

View File

@ -133,7 +133,7 @@ def repost_entries():
riv_entries = get_repost_item_valuation_entries() riv_entries = get_repost_item_valuation_entries()
for row in riv_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) repost(doc)
riv_entries = get_repost_item_valuation_entries() riv_entries = get_repost_item_valuation_entries()

View File

@ -111,6 +111,7 @@ def validate_cancellation(args):
frappe.throw(_("Cannot cancel the transaction. Reposting of item valuation on submission is not completed yet.")) frappe.throw(_("Cannot cancel the transaction. Reposting of item valuation on submission is not completed yet."))
if repost_entry.status == 'Queued': if repost_entry.status == 'Queued':
doc = frappe.get_doc("Repost Item Valuation", repost_entry.name) doc = frappe.get_doc("Repost Item Valuation", repost_entry.name)
doc.flags.ignore_permissions = True
doc.cancel() doc.cancel()
doc.delete() doc.delete()