Merge branch 'develop' into do-not-manufacture-same-serial-no-two-develop

This commit is contained in:
Anurag Mishra 2020-12-17 16:01:42 +05:30 committed by GitHub
commit 5e8e0037d6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
118 changed files with 2277 additions and 769 deletions

View File

@ -21,8 +21,8 @@ def docs_link_exists(body):
if word.startswith('http') and uri_validator(word): if word.startswith('http') and uri_validator(word):
parsed_url = urlparse(word) parsed_url = urlparse(word)
if parsed_url.netloc == "github.com": if parsed_url.netloc == "github.com":
_, org, repo, _type, ref = parsed_url.path.split('/') parts = parsed_url.path.split('/')
if org == "frappe" and repo in docs_repos: if len(parts) == 5 and parts[1] == "frappe" and parts[2] in docs_repos:
return True return True

View File

@ -910,98 +910,8 @@
}, },
"is_group": 1 "is_group": 1
}, },
"Passiva": { "Passiva - Verbindlichkeiten": {
"root_type": "Liability", "root_type": "Liability",
"A - Eigenkapital": {
"account_type": "Equity",
"is_group": 1,
"I - Gezeichnetes Kapital": {
"account_type": "Equity",
"is_group": 1,
"Gezeichnetes Kapital": {
"account_type": "Equity",
"account_number": "2900"
},
"Ausstehende Einlagen auf das gezeichnete Kapital": {
"account_number": "2910",
"is_group": 1
}
},
"II - Kapitalr\u00fccklage": {
"account_type": "Equity",
"is_group": 1,
"Kapitalr\u00fccklage": {
"account_number": "2920"
}
},
"III - Gewinnr\u00fccklagen": {
"account_type": "Equity",
"1 - gesetzliche R\u00fccklage": {
"account_type": "Equity",
"is_group": 1,
"Gesetzliche R\u00fccklage": {
"account_number": "2930"
}
},
"2 - R\u00fccklage f. Anteile an einem herrschenden oder mehrheitlich beteiligten Unternehmen": {
"account_type": "Equity",
"is_group": 1
},
"3 - satzungsm\u00e4\u00dfige R\u00fccklagen": {
"account_type": "Equity",
"is_group": 1,
"Satzungsm\u00e4\u00dfige R\u00fccklagen": {
"account_number": "2950"
}
},
"4 - andere Gewinnr\u00fccklagen": {
"account_type": "Equity",
"is_group": 1,
"Gewinnr\u00fccklagen aus den \u00dcbergangsvorschriften BilMoG": {
"is_group": 1,
"Gewinnr\u00fccklagen (BilMoG)": {
"account_number": "2963"
},
"Gewinnr\u00fccklagen aus Zuschreibung Sachanlageverm\u00f6gen (BilMoG)": {
"account_number": "2964"
},
"Gewinnr\u00fccklagen aus Zuschreibung Finanzanlageverm\u00f6gen (BilMoG)": {
"account_number": "2965"
},
"Gewinnr\u00fccklagen aus Aufl\u00f6sung der Sonderposten mit R\u00fccklageanteil (BilMoG)": {
"account_number": "2966"
}
},
"Latente Steuern (Gewinnr\u00fccklage Haben) aus erfolgsneutralen Verrechnungen": {
"account_number": "2967"
},
"Latente Steuern (Gewinnr\u00fccklage Soll) aus erfolgsneutralen Verrechnungen": {
"account_number": "2968"
},
"Rechnungsabgrenzungsposten (Gewinnr\u00fccklage Soll) aus erfolgsneutralen Verrechnungen": {
"account_number": "2969"
}
},
"is_group": 1
},
"IV - Gewinnvortrag/Verlustvortrag": {
"account_type": "Equity",
"is_group": 1,
"Gewinnvortrag vor Verwendung": {
"account_number": "2970"
},
"Verlustvortrag vor Verwendung": {
"account_number": "2978"
}
},
"V - Jahres\u00fcberschu\u00df/Jahresfehlbetrag": {
"account_type": "Equity",
"is_group": 1
},
"Einlagen stiller Gesellschafter": {
"account_number": "9295"
}
},
"B - R\u00fcckstellungen": { "B - R\u00fcckstellungen": {
"is_group": 1, "is_group": 1,
"1 - R\u00fcckstellungen f. Pensionen und \u00e4hnliche Verplicht.": { "1 - R\u00fcckstellungen f. Pensionen und \u00e4hnliche Verplicht.": {
@ -1618,6 +1528,143 @@
}, },
"is_group": 1 "is_group": 1
}, },
"Passiva - Eigenkapital": {
"root_type": "Equity",
"A - Eigenkapital": {
"account_type": "Equity",
"is_group": 1,
"I - Gezeichnetes Kapital": {
"account_type": "Equity",
"is_group": 1,
"Gezeichnetes Kapital": {
"account_number": "2900",
"account_type": "Equity"
},
"Gesch\u00e4ftsguthaben der verbleibenden Mitglieder": {
"account_number": "2901"
},
"Gesch\u00e4ftsguthaben der ausscheidenden Mitglieder": {
"account_number": "2902"
},
"Gesch\u00e4ftsguthaben aus gek\u00fcndigten Gesch\u00e4ftsanteilen": {
"account_number": "2903"
},
"R\u00fcckst\u00e4ndige f\u00e4llige Einzahlungen auf Gesch\u00e4ftsanteile, vermerkt": {
"account_number": "2906"
},
"Gegenkonto R\u00fcckst\u00e4ndige f\u00e4llige Einzahlungen auf Gesch\u00e4ftsanteile, vermerkt": {
"account_number": "2907"
},
"Kapitalerh\u00f6hung aus Gesellschaftsmitteln": {
"account_number": "2908"
},
"Ausstehende Einlagen auf das gezeichnete Kapital, nicht eingefordert": {
"account_number": "2910"
}
},
"II - Kapitalr\u00fccklage": {
"account_type": "Equity",
"is_group": 1,
"Kapitalr\u00fccklage": {
"account_number": "2920"
},
"Kapitalr\u00fccklage durch Ausgabe von Anteilen \u00fcber Nennbetrag": {
"account_number": "2925"
},
"Kapitalr\u00fccklage durch Ausgabe von Schuldverschreibungen": {
"account_number": "2926"
},
"Kapitalr\u00fccklage durch Zuzahlungen gegen Gew\u00e4hrung eines Vorzugs": {
"account_number": "2927"
},
"Kapitalr\u00fccklage durch Zuzahlungen in das Eigenkapital": {
"account_number": "2928"
},
"Nachschusskapital (Gegenkonto 1299)": {
"account_number": "2929"
}
},
"III - Gewinnr\u00fccklagen": {
"account_type": "Equity",
"1 - gesetzliche R\u00fccklage": {
"account_type": "Equity",
"is_group": 1,
"Gesetzliche R\u00fccklage": {
"account_number": "2930"
}
},
"2 - R\u00fccklage f. Anteile an einem herrschenden oder mehrheitlich beteiligten Unternehmen": {
"account_type": "Equity",
"is_group": 1,
"R\u00fccklage f. Anteile an einem herrschenden oder mehrheitlich beteiligten Unternehmen": {
"account_number": "2935"
}
},
"3 - satzungsm\u00e4\u00dfige R\u00fccklagen": {
"account_type": "Equity",
"is_group": 1,
"Satzungsm\u00e4\u00dfige R\u00fccklagen": {
"account_number": "2950"
}
},
"4 - andere Gewinnr\u00fccklagen": {
"account_type": "Equity",
"is_group": 1,
"Andere Gewinnr\u00fccklagen": {
"account_number": "2960"
},
"Andere Gewinnr\u00fccklagen aus dem Erwerb eigener Anteile": {
"account_number": "2961"
},
"Eigenkapitalanteil von Wertaufholungen": {
"account_number": "2962"
},
"Gewinnr\u00fccklagen aus den \u00dcbergangsvorschriften BilMoG": {
"is_group": 1,
"Gewinnr\u00fccklagen (BilMoG)": {
"account_number": "2963"
},
"Gewinnr\u00fccklagen aus Zuschreibung Sachanlageverm\u00f6gen (BilMoG)": {
"account_number": "2964"
},
"Gewinnr\u00fccklagen aus Zuschreibung Finanzanlageverm\u00f6gen (BilMoG)": {
"account_number": "2965"
},
"Gewinnr\u00fccklagen aus Aufl\u00f6sung der Sonderposten mit R\u00fccklageanteil (BilMoG)": {
"account_number": "2966"
}
},
"Latente Steuern (Gewinnr\u00fccklage Haben) aus erfolgsneutralen Verrechnungen": {
"account_number": "2967"
},
"Latente Steuern (Gewinnr\u00fccklage Soll) aus erfolgsneutralen Verrechnungen": {
"account_number": "2968"
},
"Rechnungsabgrenzungsposten (Gewinnr\u00fccklage Soll) aus erfolgsneutralen Verrechnungen": {
"account_number": "2969"
}
},
"is_group": 1
},
"IV - Gewinnvortrag/Verlustvortrag": {
"account_type": "Equity",
"is_group": 1,
"Gewinnvortrag vor Verwendung": {
"account_number": "2970"
},
"Verlustvortrag vor Verwendung": {
"account_number": "2978"
}
},
"V - Jahres\u00fcberschu\u00df/Jahresfehlbetrag": {
"account_type": "Equity",
"is_group": 1
},
"Einlagen stiller Gesellschafter": {
"account_number": "9295"
}
}
},
"1 - Umsatzerl\u00f6se": { "1 - Umsatzerl\u00f6se": {
"root_type": "Income", "root_type": "Income",
"is_group": 1, "is_group": 1,

View File

@ -245,6 +245,9 @@ def get():
"account_number": "2200" "account_number": "2200"
}, },
_("Duties and Taxes"): { _("Duties and Taxes"): {
_("TDS Payable"): {
"account_number": "2310"
},
"account_type": "Tax", "account_type": "Tax",
"is_group": 1, "is_group": 1,
"account_number": "2300" "account_number": "2300"

View File

@ -9,11 +9,13 @@ from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sal
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
from erpnext.accounts.page.bank_reconciliation.bank_reconciliation import reconcile, get_linked_payments from erpnext.accounts.page.bank_reconciliation.bank_reconciliation import reconcile, get_linked_payments
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
test_dependencies = ["Item", "Cost Center"] test_dependencies = ["Item", "Cost Center"]
class TestBankTransaction(unittest.TestCase): class TestBankTransaction(unittest.TestCase):
def setUp(self): def setUp(self):
make_pos_profile()
add_transactions() add_transactions()
add_payments() add_payments()
@ -27,6 +29,9 @@ class TestBankTransaction(unittest.TestCase):
frappe.db.sql("""delete from `tabPayment Entry Reference`""") frappe.db.sql("""delete from `tabPayment Entry Reference`""")
frappe.db.sql("""delete from `tabPayment Entry`""") frappe.db.sql("""delete from `tabPayment Entry`""")
# Delete POS Profile
frappe.db.sql("delete from `tabPOS Profile`")
frappe.flags.test_bank_transactions_created = False frappe.flags.test_bank_transactions_created = False
frappe.flags.test_payments_created = False frappe.flags.test_payments_created = False

View File

@ -155,7 +155,8 @@ class OpeningInvoiceCreationTool(Document):
"posting_date": row.posting_date, "posting_date": row.posting_date,
frappe.scrub(row.party_type): row.party, frappe.scrub(row.party_type): row.party,
"is_pos": 0, "is_pos": 0,
"doctype": "Sales Invoice" if self.invoice_type == "Sales" else "Purchase Invoice" "doctype": "Sales Invoice" if self.invoice_type == "Sales" else "Purchase Invoice",
"update_stock": 0
}) })
accounting_dimension = get_accounting_dimensions() accounting_dimension = get_accounting_dimensions()

View File

@ -7,17 +7,24 @@ import frappe
import unittest import unittest
test_dependencies = ["Customer", "Supplier"] test_dependencies = ["Customer", "Supplier"]
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
from erpnext.accounts.doctype.opening_invoice_creation_tool.opening_invoice_creation_tool import get_temporary_opening_account from erpnext.accounts.doctype.opening_invoice_creation_tool.opening_invoice_creation_tool import get_temporary_opening_account
class TestOpeningInvoiceCreationTool(unittest.TestCase): class TestOpeningInvoiceCreationTool(unittest.TestCase):
def make_invoices(self, invoice_type="Sales"): def setUp(self):
if not frappe.db.exists("Company", "_Test Opening Invoice Company"):
make_company()
def make_invoices(self, invoice_type="Sales", company=None, party_1=None, party_2=None):
doc = frappe.get_single("Opening Invoice Creation Tool") doc = frappe.get_single("Opening Invoice Creation Tool")
args = get_opening_invoice_creation_dict(invoice_type=invoice_type) args = get_opening_invoice_creation_dict(invoice_type=invoice_type, company=company,
party_1=party_1, party_2=party_2)
doc.update(args) doc.update(args)
return doc.make_invoices() return doc.make_invoices()
def test_opening_sales_invoice_creation(self): def test_opening_sales_invoice_creation(self):
invoices = self.make_invoices() property_setter = make_property_setter("Sales Invoice", "update_stock", "default", 1, "Check")
invoices = self.make_invoices(company="_Test Opening Invoice Company")
self.assertEqual(len(invoices), 2) self.assertEqual(len(invoices), 2)
expected_value = { expected_value = {
@ -27,6 +34,13 @@ class TestOpeningInvoiceCreationTool(unittest.TestCase):
} }
self.check_expected_values(invoices, expected_value) self.check_expected_values(invoices, expected_value)
si = frappe.get_doc("Sales Invoice", invoices[0])
# Check if update stock is not enabled
self.assertEqual(si.update_stock, 0)
property_setter.delete()
def check_expected_values(self, invoices, expected_value, invoice_type="Sales"): def check_expected_values(self, invoices, expected_value, invoice_type="Sales"):
doctype = "Sales Invoice" if invoice_type == "Sales" else "Purchase Invoice" doctype = "Sales Invoice" if invoice_type == "Sales" else "Purchase Invoice"
@ -36,7 +50,7 @@ class TestOpeningInvoiceCreationTool(unittest.TestCase):
self.assertEqual(si.get(field, ""), expected_value[invoice_idx][field_idx]) self.assertEqual(si.get(field, ""), expected_value[invoice_idx][field_idx])
def test_opening_purchase_invoice_creation(self): def test_opening_purchase_invoice_creation(self):
invoices = self.make_invoices(invoice_type="Purchase") invoices = self.make_invoices(invoice_type="Purchase", company="_Test Opening Invoice Company")
self.assertEqual(len(invoices), 2) self.assertEqual(len(invoices), 2)
expected_value = { expected_value = {
@ -46,6 +60,32 @@ class TestOpeningInvoiceCreationTool(unittest.TestCase):
} }
self.check_expected_values(invoices, expected_value, "Purchase") self.check_expected_values(invoices, expected_value, "Purchase")
def test_opening_sales_invoice_creation_with_missing_debit_account(self):
company = "_Test Opening Invoice Company"
party_1, party_2 = make_customer("Customer A"), make_customer("Customer B")
old_default_receivable_account = frappe.db.get_value("Company", company, "default_receivable_account")
frappe.db.set_value("Company", company, "default_receivable_account", "")
if not frappe.db.exists("Cost Center", "_Test Opening Invoice Company - _TOIC"):
cc = frappe.get_doc({"doctype": "Cost Center", "cost_center_name": "_Test Opening Invoice Company",
"is_group": 1, "company": "_Test Opening Invoice Company"})
cc.insert(ignore_mandatory=True)
cc2 = frappe.get_doc({"doctype": "Cost Center", "cost_center_name": "Main", "is_group": 0,
"company": "_Test Opening Invoice Company", "parent_cost_center": cc.name})
cc2.insert()
frappe.db.set_value("Company", company, "cost_center", "Main - _TOIC")
self.make_invoices(company="_Test Opening Invoice Company", party_1=party_1, party_2=party_2)
# Check if missing debit account error raised
error_log = frappe.db.exists("Error Log", {"error": ["like", "%erpnext.controllers.accounts_controller.AccountMissingError%"]})
self.assertTrue(error_log)
# teardown
frappe.db.set_value("Company", company, "default_receivable_account", old_default_receivable_account)
def get_opening_invoice_creation_dict(**args): def get_opening_invoice_creation_dict(**args):
party = "Customer" if args.get("invoice_type", "Sales") == "Sales" else "Supplier" party = "Customer" if args.get("invoice_type", "Sales") == "Sales" else "Supplier"
company = args.get("company", "_Test Company") company = args.get("company", "_Test Company")
@ -57,7 +97,7 @@ def get_opening_invoice_creation_dict(**args):
{ {
"qty": 1.0, "qty": 1.0,
"outstanding_amount": 300, "outstanding_amount": 300,
"party": "_Test {0}".format(party), "party": args.get("party_1") or "_Test {0}".format(party),
"item_name": "Opening Item", "item_name": "Opening Item",
"due_date": "2016-09-10", "due_date": "2016-09-10",
"posting_date": "2016-09-05", "posting_date": "2016-09-05",
@ -66,7 +106,7 @@ def get_opening_invoice_creation_dict(**args):
{ {
"qty": 2.0, "qty": 2.0,
"outstanding_amount": 250, "outstanding_amount": 250,
"party": "_Test {0} 1".format(party), "party": args.get("party_2") or "_Test {0} 1".format(party),
"item_name": "Opening Item", "item_name": "Opening Item",
"due_date": "2016-09-10", "due_date": "2016-09-10",
"posting_date": "2016-09-05", "posting_date": "2016-09-05",
@ -76,4 +116,31 @@ def get_opening_invoice_creation_dict(**args):
}) })
invoice_dict.update(args) invoice_dict.update(args)
return invoice_dict return invoice_dict
def make_company():
if frappe.db.exists("Company", "_Test Opening Invoice Company"):
return frappe.get_doc("Company", "_Test Opening Invoice Company")
company = frappe.new_doc("Company")
company.company_name = "_Test Opening Invoice Company"
company.abbr = "_TOIC"
company.default_currency = "INR"
company.country = "India"
company.insert()
return company
def make_customer(customer=None):
customer_name = customer or "Opening Customer"
customer = frappe.get_doc({
"doctype": "Customer",
"customer_name": customer_name,
"customer_group": "All Customer Groups",
"customer_type": "Company",
"territory": "All Territories"
})
if not frappe.db.exists("Customer", customer_name):
customer.insert(ignore_permissions=True)
return customer.name
else:
return frappe.db.exists("Customer", customer_name)

View File

@ -14,7 +14,6 @@
"column_break_9", "column_break_9",
"update_stock", "update_stock",
"ignore_pricing_rule", "ignore_pricing_rule",
"hide_unavailable_items",
"warehouse", "warehouse",
"campaign", "campaign",
"company_address", "company_address",
@ -23,6 +22,9 @@
"section_break_11", "section_break_11",
"payments", "payments",
"section_break_14", "section_break_14",
"hide_images",
"hide_unavailable_items",
"auto_add_item_to_cart",
"item_groups", "item_groups",
"column_break_16", "column_break_16",
"customer_groups", "customer_groups",
@ -124,7 +126,8 @@
}, },
{ {
"fieldname": "section_break_14", "fieldname": "section_break_14",
"fieldtype": "Section Break" "fieldtype": "Section Break",
"label": "Configuration"
}, },
{ {
"description": "Only show Items from these Item Groups", "description": "Only show Items from these Item Groups",
@ -314,13 +317,25 @@
"fieldname": "hide_unavailable_items", "fieldname": "hide_unavailable_items",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Hide Unavailable Items" "label": "Hide Unavailable Items"
},
{
"default": "0",
"fieldname": "hide_images",
"fieldtype": "Check",
"label": "Hide Images"
},
{
"default": "0",
"fieldname": "auto_add_item_to_cart",
"fieldtype": "Check",
"label": "Automatically Add Filtered Item To Cart"
} }
], ],
"icon": "icon-cog", "icon": "icon-cog",
"idx": 1, "idx": 1,
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"links": [], "links": [],
"modified": "2020-10-29 13:18:38.795925", "modified": "2020-12-10 13:59:28.877572",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "POS Profile", "name": "POS Profile",

View File

@ -70,6 +70,7 @@ def get_items_list(pos_profile, company):
""".format(cond=cond), tuple([company] + args_list), as_dict=1) """.format(cond=cond), tuple([company] + args_list), as_dict=1)
def make_pos_profile(**args): def make_pos_profile(**args):
frappe.db.sql("delete from `tabPOS Payment Method`")
frappe.db.sql("delete from `tabPOS Profile`") frappe.db.sql("delete from `tabPOS Profile`")
args = frappe._dict(args) args = frappe._dict(args)

View File

@ -406,6 +406,7 @@
"fieldtype": "Column Break" "fieldtype": "Column Break"
}, },
{ {
"default": "0",
"depends_on": "eval:doc.rate_or_discount==\"Rate\"", "depends_on": "eval:doc.rate_or_discount==\"Rate\"",
"fieldname": "rate", "fieldname": "rate",
"fieldtype": "Currency", "fieldtype": "Currency",
@ -469,6 +470,7 @@
"options": "UOM" "options": "UOM"
}, },
{ {
"description": "If rate is zero them item will be treated as \"Free Item\"",
"fieldname": "free_item_rate", "fieldname": "free_item_rate",
"fieldtype": "Currency", "fieldtype": "Currency",
"label": "Rate" "label": "Rate"
@ -563,7 +565,7 @@
"icon": "fa fa-gift", "icon": "fa fa-gift",
"idx": 1, "idx": 1,
"links": [], "links": [],
"modified": "2020-10-28 16:53:14.416172", "modified": "2020-12-04 00:36:24.698219",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Pricing Rule", "name": "Pricing Rule",

View File

@ -521,6 +521,22 @@ class TestPricingRule(unittest.TestCase):
frappe.get_doc("Item Price", {"item_code": "Water Flask"}).delete() frappe.get_doc("Item Price", {"item_code": "Water Flask"}).delete()
item.delete() item.delete()
def test_pricing_rule_for_transaction(self):
make_item("Water Flask 1")
frappe.delete_doc_if_exists('Pricing Rule', '_Test Pricing Rule')
make_pricing_rule(selling=1, min_qty=5, price_or_product_discount="Product",
apply_on="Transaction", free_item="Water Flask 1", free_qty=1, free_item_rate=10)
si = create_sales_invoice(qty=5, do_not_submit=True)
self.assertEquals(len(si.items), 2)
self.assertEquals(si.items[1].rate, 10)
si1 = create_sales_invoice(qty=2, do_not_submit=True)
self.assertEquals(len(si1.items), 1)
for doc in [si, si1]:
doc.delete()
def make_pricing_rule(**args): def make_pricing_rule(**args):
args = frappe._dict(args) args = frappe._dict(args)
@ -539,20 +555,23 @@ def make_pricing_rule(**args):
"rate_or_discount": args.rate_or_discount or "Discount Percentage", "rate_or_discount": args.rate_or_discount or "Discount Percentage",
"discount_percentage": args.discount_percentage or 0.0, "discount_percentage": args.discount_percentage or 0.0,
"rate": args.rate or 0.0, "rate": args.rate or 0.0,
"margin_type": args.margin_type,
"margin_rate_or_amount": args.margin_rate_or_amount or 0.0, "margin_rate_or_amount": args.margin_rate_or_amount or 0.0,
"condition": args.condition or '', "condition": args.condition or '',
"apply_multiple_pricing_rules": args.apply_multiple_pricing_rules or 0 "apply_multiple_pricing_rules": args.apply_multiple_pricing_rules or 0
}) })
if args.get("priority"): for field in ["free_item", "free_qty", "free_item_rate", "priority",
doc.priority = args.get("priority") "margin_type", "price_or_product_discount"]:
if args.get(field):
doc.set(field, args.get(field))
apply_on = doc.apply_on.replace(' ', '_').lower() apply_on = doc.apply_on.replace(' ', '_').lower()
child_table = {'Item Code': 'items', 'Item Group': 'item_groups', 'Brand': 'brands'} child_table = {'Item Code': 'items', 'Item Group': 'item_groups', 'Brand': 'brands'}
doc.append(child_table.get(doc.apply_on), {
apply_on: args.get(apply_on) or "_Test Item" if doc.apply_on != "Transaction":
}) doc.append(child_table.get(doc.apply_on), {
apply_on: args.get(apply_on) or "_Test Item"
})
doc.insert(ignore_permissions=True) doc.insert(ignore_permissions=True)
if args.get(apply_on) and apply_on != "item_code": if args.get(apply_on) and apply_on != "item_code":

View File

@ -457,6 +457,9 @@ def apply_pricing_rule_on_transaction(doc):
pricing_rules = filter_pricing_rules_for_qty_amount(doc.total_qty, pricing_rules = filter_pricing_rules_for_qty_amount(doc.total_qty,
doc.total, pricing_rules) doc.total, pricing_rules)
if not pricing_rules:
remove_free_item(doc)
for d in pricing_rules: for d in pricing_rules:
if d.price_or_product_discount == 'Price': if d.price_or_product_discount == 'Price':
if d.apply_discount_on: if d.apply_discount_on:
@ -480,6 +483,12 @@ def apply_pricing_rule_on_transaction(doc):
get_product_discount_rule(d, item_details, doc=doc) get_product_discount_rule(d, item_details, doc=doc)
apply_pricing_rule_for_free_items(doc, item_details.free_item_data) apply_pricing_rule_for_free_items(doc, item_details.free_item_data)
doc.set_missing_values() doc.set_missing_values()
doc.calculate_taxes_and_totals()
def remove_free_item(doc):
for d in doc.items:
if d.is_free_item:
doc.remove(d)
def get_applied_pricing_rules(pricing_rules): def get_applied_pricing_rules(pricing_rules):
if pricing_rules: if pricing_rules:
@ -492,7 +501,7 @@ def get_applied_pricing_rules(pricing_rules):
def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None): def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None):
free_item = pricing_rule.free_item free_item = pricing_rule.free_item
if pricing_rule.same_item: if pricing_rule.same_item and pricing_rule.get("apply_on") != 'Transaction':
free_item = item_details.item_code or args.item_code free_item = item_details.item_code or args.item_code
if not free_item: if not free_item:

View File

@ -21,7 +21,7 @@ class TestProcessDeferredAccounting(unittest.TestCase):
item.no_of_months = 12 item.no_of_months = 12
item.save() item.save()
si = create_sales_invoice(item=item.name, posting_date="2019-01-10", do_not_submit=True) si = create_sales_invoice(item=item.name, update_stock=0, posting_date="2019-01-10", do_not_submit=True)
si.items[0].enable_deferred_revenue = 1 si.items[0].enable_deferred_revenue = 1
si.items[0].service_start_date = "2019-01-10" si.items[0].service_start_date = "2019-01-10"
si.items[0].service_end_date = "2019-03-15" si.items[0].service_end_date = "2019-03-15"

View File

@ -15,6 +15,16 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
return (doc.qty<=doc.received_qty) ? "green" : "orange"; return (doc.qty<=doc.received_qty) ? "green" : "orange";
}); });
} }
this.frm.set_query("unrealized_profit_loss_account", function() {
return {
filters: {
company: doc.company,
is_group: 0,
root_type: "Liability",
}
};
});
}, },
onload: function() { onload: function() {
this._super(); this._super();

View File

@ -1,6 +1,5 @@
{ {
"actions": [], "actions": [],
"allow_auto_repeat": 1,
"allow_import": 1, "allow_import": 1,
"autoname": "naming_series:", "autoname": "naming_series:",
"creation": "2013-05-21 16:16:39", "creation": "2013-05-21 16:16:39",
@ -127,6 +126,7 @@
"write_off_cost_center", "write_off_cost_center",
"advances_section", "advances_section",
"allocate_advances_automatically", "allocate_advances_automatically",
"adjust_advance_taxes",
"get_advances", "get_advances",
"advances", "advances",
"payment_schedule_section", "payment_schedule_section",
@ -152,9 +152,11 @@
"is_opening", "is_opening",
"against_expense_account", "against_expense_account",
"column_break_63", "column_break_63",
"unrealized_profit_loss_account",
"status", "status",
"inter_company_invoice_reference", "inter_company_invoice_reference",
"is_internal_supplier", "is_internal_supplier",
"represents_company",
"remarks", "remarks",
"subscription_section", "subscription_section",
"from_date", "from_date",
@ -1223,7 +1225,7 @@
"fieldtype": "Select", "fieldtype": "Select",
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Status", "label": "Status",
"options": "\nDraft\nReturn\nDebit Note Issued\nSubmitted\nPaid\nUnpaid\nOverdue\nCancelled", "options": "\nDraft\nReturn\nDebit Note Issued\nSubmitted\nPaid\nUnpaid\nOverdue\nCancelled\nInternal Transfer",
"print_hide": 1 "print_hide": 1
}, },
{ {
@ -1330,13 +1332,37 @@
"fieldtype": "Link", "fieldtype": "Link",
"label": "Project", "label": "Project",
"options": "Project" "options": "Project"
},
{
"default": "0",
"description": "Taxes paid while advance payment will be adjusted against this invoice",
"fieldname": "adjust_advance_taxes",
"fieldtype": "Check",
"label": "Adjust Advance Taxes"
},
{
"depends_on": "eval:doc.is_internal_supplier",
"description": "Unrealized Profit / Loss account for intra-company transfers",
"fieldname": "unrealized_profit_loss_account",
"fieldtype": "Link",
"label": "Unrealized Profit / Loss Account",
"options": "Account"
},
{
"depends_on": "eval:doc.is_internal_supplier",
"description": "Company which internal supplier represents",
"fetch_from": "supplier.represents_company",
"fieldname": "represents_company",
"fieldtype": "Link",
"label": "Represents Company",
"options": "Company"
} }
], ],
"icon": "fa fa-file-text", "icon": "fa fa-file-text",
"idx": 204, "idx": 204,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-10-30 13:57:18.266978", "modified": "2020-12-11 12:46:12.796378",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Purchase Invoice", "name": "Purchase Invoice",

View File

@ -147,6 +147,11 @@ class PurchaseInvoice(BuyingController):
throw(_("Conversion rate cannot be 0 or 1")) throw(_("Conversion rate cannot be 0 or 1"))
def validate_credit_to_acc(self): def validate_credit_to_acc(self):
if not self.credit_to:
self.credit_to = get_party_account("Supplier", self.supplier, self.company)
if not self.credit_to:
self.raise_missing_debit_credit_account_error("Supplier", self.supplier)
account = frappe.db.get_value("Account", self.credit_to, account = frappe.db.get_value("Account", self.credit_to,
["account_type", "report_type", "account_currency"], as_dict=True) ["account_type", "report_type", "account_currency"], as_dict=True)
@ -201,8 +206,8 @@ class PurchaseInvoice(BuyingController):
["Purchase Receipt", "purchase_receipt", "pr_detail"] ["Purchase Receipt", "purchase_receipt", "pr_detail"]
]) ])
def validate_warehouse(self): def validate_warehouse(self, for_validate=True):
if self.update_stock: if self.update_stock and for_validate:
for d in self.get('items'): for d in self.get('items'):
if not d.warehouse: if not d.warehouse:
frappe.throw(_("Warehouse required at Row No {0}, please set default warehouse for the item {1} for the company {2}"). frappe.throw(_("Warehouse required at Row No {0}, please set default warehouse for the item {1} for the company {2}").
@ -228,7 +233,7 @@ class PurchaseInvoice(BuyingController):
if self.update_stock: if self.update_stock:
self.validate_item_code() self.validate_item_code()
self.validate_warehouse() self.validate_warehouse(for_validate)
if auto_accounting_for_stock: if auto_accounting_for_stock:
warehouse_account = get_warehouse_account_map(self.company) warehouse_account = get_warehouse_account_map(self.company)
@ -444,6 +449,7 @@ class PurchaseInvoice(BuyingController):
self.get_asset_gl_entry(gl_entries) self.get_asset_gl_entry(gl_entries)
self.make_tax_gl_entries(gl_entries) self.make_tax_gl_entries(gl_entries)
self.make_internal_transfer_gl_entries(gl_entries)
gl_entries = make_regional_gl_entries(gl_entries, self) gl_entries = make_regional_gl_entries(gl_entries, self)
@ -452,7 +458,6 @@ class PurchaseInvoice(BuyingController):
self.make_payment_gl_entries(gl_entries) self.make_payment_gl_entries(gl_entries)
self.make_write_off_gl_entry(gl_entries) self.make_write_off_gl_entry(gl_entries)
self.make_gle_for_rounding_adjustment(gl_entries) self.make_gle_for_rounding_adjustment(gl_entries)
return gl_entries return gl_entries
def check_asset_cwip_enabled(self): def check_asset_cwip_enabled(self):
@ -469,31 +474,30 @@ class PurchaseInvoice(BuyingController):
# because rounded_total had value even before introcution of posting GLE based on rounded total # because rounded_total had value even before introcution of posting GLE based on rounded total
grand_total = self.rounded_total if (self.rounding_adjustment and self.rounded_total) else self.grand_total grand_total = self.rounded_total if (self.rounding_adjustment and self.rounded_total) else self.grand_total
if grand_total: if grand_total and not self.is_internal_transfer():
# Didnot use base_grand_total to book rounding loss gle # Didnot use base_grand_total to book rounding loss gle
grand_total_in_company_currency = flt(grand_total * self.conversion_rate, grand_total_in_company_currency = flt(grand_total * self.conversion_rate,
self.precision("grand_total")) self.precision("grand_total"))
gl_entries.append( gl_entries.append(
self.get_gl_dict({ self.get_gl_dict({
"account": self.credit_to, "account": self.credit_to,
"party_type": "Supplier", "party_type": "Supplier",
"party": self.supplier, "party": self.supplier,
"due_date": self.due_date, "due_date": self.due_date,
"against": self.against_expense_account, "against": self.against_expense_account,
"credit": grand_total_in_company_currency, "credit": grand_total_in_company_currency,
"credit_in_account_currency": grand_total_in_company_currency \ "credit_in_account_currency": grand_total_in_company_currency \
if self.party_account_currency==self.company_currency else grand_total, if self.party_account_currency==self.company_currency else grand_total,
"against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name, "against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name,
"against_voucher_type": self.doctype, "against_voucher_type": self.doctype,
"project": self.project, "project": self.project,
"cost_center": self.cost_center "cost_center": self.cost_center
}, self.party_account_currency, item=self) }, self.party_account_currency, item=self)
) )
def make_item_gl_entries(self, gl_entries): def make_item_gl_entries(self, gl_entries):
# item gl entries # item gl entries
stock_items = self.get_stock_items() stock_items = self.get_stock_items()
expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
if self.update_stock and self.auto_accounting_for_stock: if self.update_stock and self.auto_accounting_for_stock:
warehouse_account = get_warehouse_account_map(self.company) warehouse_account = get_warehouse_account_map(self.company)
@ -521,7 +525,6 @@ class PurchaseInvoice(BuyingController):
item, voucher_wise_stock_value, account_currency) item, voucher_wise_stock_value, account_currency)
if item.from_warehouse: if item.from_warehouse:
gl_entries.append(self.get_gl_dict({ gl_entries.append(self.get_gl_dict({
"account": warehouse_account[item.warehouse]['account'], "account": warehouse_account[item.warehouse]['account'],
"against": warehouse_account[item.from_warehouse]["account"], "against": warehouse_account[item.from_warehouse]["account"],
@ -541,16 +544,18 @@ class PurchaseInvoice(BuyingController):
"debit": -1 * flt(item.base_net_amount, item.precision("base_net_amount")), "debit": -1 * flt(item.base_net_amount, item.precision("base_net_amount")),
}, warehouse_account[item.from_warehouse]["account_currency"], item=item)) }, warehouse_account[item.from_warehouse]["account_currency"], item=item))
gl_entries.append( # Do not book expense for transfer within same company transfer
self.get_gl_dict({ if not self.is_internal_transfer():
"account": item.expense_account, gl_entries.append(
"against": self.supplier, self.get_gl_dict({
"debit": flt(item.base_net_amount, item.precision("base_net_amount")), "account": item.expense_account,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"), "against": self.supplier,
"cost_center": item.cost_center, "debit": flt(item.base_net_amount, item.precision("base_net_amount")),
"project": item.project "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
}, account_currency, item=item) "cost_center": item.cost_center,
) "project": item.project
}, account_currency, item=item)
)
else: else:
gl_entries.append( gl_entries.append(
@ -827,7 +832,8 @@ class PurchaseInvoice(BuyingController):
}, account_currency, item=tax) }, account_currency, item=tax)
) )
# accumulate valuation tax # accumulate valuation tax
if self.is_opening == "No" and tax.category in ("Valuation", "Valuation and Total") and flt(tax.base_tax_amount_after_discount_amount): if self.is_opening == "No" and tax.category in ("Valuation", "Valuation and Total") and flt(tax.base_tax_amount_after_discount_amount) \
and not self.is_internal_transfer():
if self.auto_accounting_for_stock and not tax.cost_center: if self.auto_accounting_for_stock and not tax.cost_center:
frappe.throw(_("Cost Center is required in row {0} in Taxes table for type {1}").format(tax.idx, _(tax.category))) frappe.throw(_("Cost Center is required in row {0} in Taxes table for type {1}").format(tax.idx, _(tax.category)))
valuation_tax.setdefault(tax.name, 0) valuation_tax.setdefault(tax.name, 0)
@ -871,8 +877,19 @@ class PurchaseInvoice(BuyingController):
"against": self.supplier, "against": self.supplier,
"credit": valuation_tax[tax.name], "credit": valuation_tax[tax.name],
"remarks": self.remarks or "Accounting Entry for Stock" "remarks": self.remarks or "Accounting Entry for Stock"
}, item=tax) }, item=tax))
)
def make_internal_transfer_gl_entries(self, gl_entries):
if self.is_internal_transfer() and flt(self.base_total_taxes_and_charges):
account_currency = get_account_currency(self.unrealized_profit_loss_account)
gl_entries.append(
self.get_gl_dict({
"account": self.unrealized_profit_loss_account,
"against": self.supplier,
"credit": flt(self.total_taxes_and_charges),
"credit_in_account_currency": flt(self.base_total_taxes_and_charges),
"cost_center": self.cost_center
}, account_currency, item=self))
def make_payment_gl_entries(self, gl_entries): def make_payment_gl_entries(self, gl_entries):
# Make Cash GL Entries # Make Cash GL Entries
@ -1032,7 +1049,9 @@ class PurchaseInvoice(BuyingController):
updated_pr += update_billed_amount_based_on_po(d.po_detail, update_modified) updated_pr += update_billed_amount_based_on_po(d.po_detail, update_modified)
for pr in set(updated_pr): for pr in set(updated_pr):
frappe.get_doc("Purchase Receipt", pr).update_billing_percentage(update_modified=update_modified) from erpnext.stock.doctype.purchase_receipt.purchase_receipt import update_billing_percentage
pr_doc = frappe.get_doc("Purchase Receipt", pr)
update_billing_percentage(pr_doc, update_modified=update_modified)
def on_recurring(self, reference_doc, auto_repeat_doc): def on_recurring(self, reference_doc, auto_repeat_doc):
self.due_date = None self.due_date = None
@ -1088,7 +1107,9 @@ class PurchaseInvoice(BuyingController):
if self.docstatus == 2: if self.docstatus == 2:
status = "Cancelled" status = "Cancelled"
elif self.docstatus == 1: elif self.docstatus == 1:
if outstanding_amount > 0 and due_date < nowdate: if self.is_internal_transfer():
self.status = 'Internal Transfer'
elif outstanding_amount > 0 and due_date < nowdate:
self.status = "Overdue" self.status = "Overdue"
elif outstanding_amount > 0 and due_date >= nowdate: elif outstanding_amount > 0 and due_date >= nowdate:
self.status = "Unpaid" self.status = "Unpaid"

View File

@ -4,23 +4,25 @@
// render // render
frappe.listview_settings['Purchase Invoice'] = { frappe.listview_settings['Purchase Invoice'] = {
add_fields: ["supplier", "supplier_name", "base_grand_total", "outstanding_amount", "due_date", "company", add_fields: ["supplier", "supplier_name", "base_grand_total", "outstanding_amount", "due_date", "company",
"currency", "is_return", "release_date", "on_hold"], "currency", "is_return", "release_date", "on_hold", "represents_company", "is_internal_supplier"],
get_indicator: function(doc) { get_indicator: function(doc) {
if( (flt(doc.outstanding_amount) <= 0) && doc.docstatus == 1 && doc.status == 'Debit Note Issued') { if ((flt(doc.outstanding_amount) <= 0) && doc.docstatus == 1 && doc.status == 'Debit Note Issued') {
return [__("Debit Note Issued"), "darkgrey", "outstanding_amount,<=,0"]; return [__("Debit Note Issued"), "darkgrey", "outstanding_amount,<=,0"];
} else if(flt(doc.outstanding_amount) > 0 && doc.docstatus==1) { } else if (flt(doc.outstanding_amount) > 0 && doc.docstatus==1) {
if(cint(doc.on_hold) && !doc.release_date) { if(cint(doc.on_hold) && !doc.release_date) {
return [__("On Hold"), "darkgrey"]; return [__("On Hold"), "darkgrey"];
} else if(cint(doc.on_hold) && doc.release_date && frappe.datetime.get_diff(doc.release_date, frappe.datetime.nowdate()) > 0) { } else if (cint(doc.on_hold) && doc.release_date && frappe.datetime.get_diff(doc.release_date, frappe.datetime.nowdate()) > 0) {
return [__("Temporarily on Hold"), "darkgrey"]; return [__("Temporarily on Hold"), "darkgrey"];
} else if(frappe.datetime.get_diff(doc.due_date) < 0) { } else if (frappe.datetime.get_diff(doc.due_date) < 0) {
return [__("Overdue"), "red", "outstanding_amount,>,0|due_date,<,Today"]; return [__("Overdue"), "red", "outstanding_amount,>,0|due_date,<,Today"];
} else { } else {
return [__("Unpaid"), "orange", "outstanding_amount,>,0|due_date,>=,Today"]; return [__("Unpaid"), "orange", "outstanding_amount,>,0|due_date,>=,Today"];
} }
} else if(cint(doc.is_return)) { } else if (cint(doc.is_return)) {
return [__("Return"), "darkgrey", "is_return,=,Yes"]; return [__("Return"), "darkgrey", "is_return,=,Yes"];
} else if(flt(doc.outstanding_amount)==0 && doc.docstatus==1) { } else if (doc.company == doc.represents_company && doc.is_internal_supplier) {
return [__("Internal Transfer"), "darkgrey", "outstanding_amount,=,0"];
} else if (flt(doc.outstanding_amount)==0 && doc.docstatus==1) {
return [__("Paid"), "green", "outstanding_amount,=,0"]; return [__("Paid"), "green", "outstanding_amount,=,0"];
} }
} }

View File

@ -580,6 +580,16 @@ frappe.ui.form.on('Sales Invoice', {
}; };
}); });
frm.set_query("unrealized_profit_loss_account", function() {
return {
filters: {
company: frm.doc.company,
is_group: 0,
root_type: "Liability",
}
};
});
frm.custom_make_buttons = { frm.custom_make_buttons = {
'Delivery Note': 'Delivery', 'Delivery Note': 'Delivery',
'Sales Invoice': 'Sales Return', 'Sales Invoice': 'Sales Return',

View File

@ -1,6 +1,5 @@
{ {
"actions": [], "actions": [],
"allow_auto_repeat": 1,
"allow_import": 1, "allow_import": 1,
"autoname": "naming_series:", "autoname": "naming_series:",
"creation": "2013-05-24 19:29:05", "creation": "2013-05-24 19:29:05",
@ -158,6 +157,7 @@
"more_information", "more_information",
"inter_company_invoice_reference", "inter_company_invoice_reference",
"is_internal_customer", "is_internal_customer",
"represents_company",
"customer_group", "customer_group",
"campaign", "campaign",
"is_discounted", "is_discounted",
@ -171,6 +171,7 @@
"c_form_applicable", "c_form_applicable",
"c_form_no", "c_form_no",
"column_break8", "column_break8",
"unrealized_profit_loss_account",
"remarks", "remarks",
"sales_team_section_break", "sales_team_section_break",
"sales_partner", "sales_partner",
@ -1655,7 +1656,7 @@
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Status", "label": "Status",
"no_copy": 1, "no_copy": 1,
"options": "\nDraft\nReturn\nCredit Note Issued\nSubmitted\nPaid\nUnpaid\nUnpaid and Discounted\nOverdue and Discounted\nOverdue\nCancelled", "options": "\nDraft\nReturn\nCredit Note Issued\nSubmitted\nPaid\nUnpaid\nUnpaid and Discounted\nOverdue and Discounted\nOverdue\nCancelled\nInternal Transfer",
"print_hide": 1, "print_hide": 1,
"read_only": 1 "read_only": 1
}, },
@ -1950,13 +1951,31 @@
"fieldtype": "Data", "fieldtype": "Data",
"label": "Company Tax ID", "label": "Company Tax ID",
"read_only": 1 "read_only": 1
},
{
"depends_on": "eval:doc.is_internal_customer",
"description": "Unrealized Profit / Loss account for intra-company transfers",
"fieldname": "unrealized_profit_loss_account",
"fieldtype": "Link",
"label": "Unrealized Profit / Loss Account",
"options": "Account"
},
{
"depends_on": "eval:doc.is_internal_customer",
"description": "Company which internal customer represents",
"fetch_from": "customer.represents_company",
"fieldname": "represents_company",
"fieldtype": "Link",
"label": "Represents Company",
"options": "Company",
"read_only": 1
} }
], ],
"icon": "fa fa-file-text", "icon": "fa fa-file-text",
"idx": 181, "idx": 181,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-10-30 13:57:45.086303", "modified": "2020-12-11 12:48:31.769958",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Sales Invoice", "name": "Sales Invoice",

View File

@ -405,6 +405,8 @@ class SalesInvoice(SellingController):
from erpnext.stock.get_item_details import get_pos_profile_item_details, get_pos_profile from erpnext.stock.get_item_details import get_pos_profile_item_details, get_pos_profile
if not self.pos_profile: if not self.pos_profile:
pos_profile = get_pos_profile(self.company) or {} pos_profile = get_pos_profile(self.company) or {}
if not pos_profile:
frappe.throw(_("No POS Profile found. Please create a New POS Profile first"))
self.pos_profile = pos_profile.get('name') self.pos_profile = pos_profile.get('name')
pos = {} pos = {}
@ -472,6 +474,11 @@ class SalesInvoice(SellingController):
return frappe.db.sql("select abbr from tabCompany where name=%s", self.company)[0][0] return frappe.db.sql("select abbr from tabCompany where name=%s", self.company)[0][0]
def validate_debit_to_acc(self): def validate_debit_to_acc(self):
if not self.debit_to:
self.debit_to = get_party_account("Customer", self.customer, self.company)
if not self.debit_to:
self.raise_missing_debit_credit_account_error("Customer", self.customer)
account = frappe.get_cached_value("Account", self.debit_to, account = frappe.get_cached_value("Account", self.debit_to,
["account_type", "report_type", "account_currency"], as_dict=True) ["account_type", "report_type", "account_currency"], as_dict=True)
@ -751,6 +758,7 @@ class SalesInvoice(SellingController):
self.make_customer_gl_entry(gl_entries) self.make_customer_gl_entry(gl_entries)
self.make_tax_gl_entries(gl_entries) self.make_tax_gl_entries(gl_entries)
self.make_internal_transfer_gl_entries(gl_entries)
self.make_item_gl_entries(gl_entries) self.make_item_gl_entries(gl_entries)
@ -770,7 +778,7 @@ class SalesInvoice(SellingController):
# Checked both rounding_adjustment and rounded_total # Checked both rounding_adjustment and rounded_total
# because rounded_total had value even before introcution of posting GLE based on rounded total # because rounded_total had value even before introcution of posting GLE based on rounded total
grand_total = self.rounded_total if (self.rounding_adjustment and self.rounded_total) else self.grand_total grand_total = self.rounded_total if (self.rounding_adjustment and self.rounded_total) else self.grand_total
if grand_total: if grand_total and not self.is_internal_transfer():
# Didnot use base_grand_total to book rounding loss gle # Didnot use base_grand_total to book rounding loss gle
grand_total_in_company_currency = flt(grand_total * self.conversion_rate, grand_total_in_company_currency = flt(grand_total * self.conversion_rate,
self.precision("grand_total")) self.precision("grand_total"))
@ -809,6 +817,18 @@ class SalesInvoice(SellingController):
}, account_currency, item=tax) }, account_currency, item=tax)
) )
def make_internal_transfer_gl_entries(self, gl_entries):
if self.is_internal_transfer() and flt(self.base_total_taxes_and_charges):
account_currency = get_account_currency(self.unrealized_profit_loss_account)
gl_entries.append(
self.get_gl_dict({
"account": self.unrealized_profit_loss_account,
"against": self.customer,
"debit": flt(self.total_taxes_and_charges),
"debit_in_account_currency": flt(self.base_total_taxes_and_charges),
"cost_center": self.cost_center
}, account_currency, item=self))
def make_item_gl_entries(self, gl_entries): def make_item_gl_entries(self, gl_entries):
# income account gl entries # income account gl entries
for item in self.get("items"): for item in self.get("items"):
@ -831,22 +851,24 @@ class SalesInvoice(SellingController):
asset.db_set("disposal_date", self.posting_date) asset.db_set("disposal_date", self.posting_date)
asset.set_status("Sold" if self.docstatus==1 else None) asset.set_status("Sold" if self.docstatus==1 else None)
else: else:
income_account = (item.income_account # Do not book income for transfer within same company
if (not item.enable_deferred_revenue or self.is_return) else item.deferred_revenue_account) if not self.is_internal_transfer():
income_account = (item.income_account
if (not item.enable_deferred_revenue or self.is_return) else item.deferred_revenue_account)
account_currency = get_account_currency(income_account) account_currency = get_account_currency(income_account)
gl_entries.append( gl_entries.append(
self.get_gl_dict({ self.get_gl_dict({
"account": income_account, "account": income_account,
"against": self.customer, "against": self.customer,
"credit": flt(item.base_net_amount, item.precision("base_net_amount")), "credit": flt(item.base_net_amount, item.precision("base_net_amount")),
"credit_in_account_currency": (flt(item.base_net_amount, item.precision("base_net_amount")) "credit_in_account_currency": (flt(item.base_net_amount, item.precision("base_net_amount"))
if account_currency==self.company_currency if account_currency==self.company_currency
else flt(item.net_amount, item.precision("net_amount"))), else flt(item.net_amount, item.precision("net_amount"))),
"cost_center": item.cost_center, "cost_center": item.cost_center,
"project": item.project or self.project "project": item.project or self.project
}, account_currency, item=item) }, account_currency, item=item)
) )
# expense account gl entries # expense account gl entries
if cint(self.update_stock) and \ if cint(self.update_stock) and \
@ -1258,7 +1280,9 @@ class SalesInvoice(SellingController):
if self.docstatus == 2: if self.docstatus == 2:
status = "Cancelled" status = "Cancelled"
elif self.docstatus == 1: elif self.docstatus == 1:
if outstanding_amount > 0 and due_date < nowdate and self.is_discounted and discountng_status=='Disbursed': if self.is_internal_transfer():
self.status = 'Internal Transfer'
elif outstanding_amount > 0 and due_date < nowdate and self.is_discounted and discountng_status=='Disbursed':
self.status = "Overdue and Discounted" self.status = "Overdue and Discounted"
elif outstanding_amount > 0 and due_date < nowdate: elif outstanding_amount > 0 and due_date < nowdate:
self.status = "Overdue" self.status = "Overdue"
@ -1523,9 +1547,13 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
if doctype in ["Sales Invoice", "Sales Order"]: if doctype in ["Sales Invoice", "Sales Order"]:
source_doc = frappe.get_doc(doctype, source_name) source_doc = frappe.get_doc(doctype, source_name)
target_doctype = "Purchase Invoice" if doctype == "Sales Invoice" else "Purchase Order" target_doctype = "Purchase Invoice" if doctype == "Sales Invoice" else "Purchase Order"
source_document_warehouse_field = 'target_warehouse'
target_document_warehouse_field = 'from_warehouse'
else: else:
source_doc = frappe.get_doc(doctype, source_name) source_doc = frappe.get_doc(doctype, source_name)
target_doctype = "Sales Invoice" if doctype == "Purchase Invoice" else "Sales Order" target_doctype = "Sales Invoice" if doctype == "Purchase Invoice" else "Sales Order"
source_document_warehouse_field = 'from_warehouse'
target_document_warehouse_field = 'target_warehouse'
validate_inter_company_transaction(source_doc, doctype) validate_inter_company_transaction(source_doc, doctype)
details = get_inter_company_details(source_doc, doctype) details = get_inter_company_details(source_doc, doctype)
@ -1552,6 +1580,26 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
if currency: if currency:
target_doc.currency = currency target_doc.currency = currency
item_field_map = {
"doctype": target_doctype + " Item",
"field_no_map": [
"income_account",
"expense_account",
"cost_center",
"warehouse"
]
}
if source_doc.get('update_stock'):
item_field_map.update({
'field_map': {
source_document_warehouse_field: target_document_warehouse_field,
'batch_no': 'batch_no',
'serial_no': 'serial_no'
}
})
doclist = get_mapped_doc(doctype, source_name, { doclist = get_mapped_doc(doctype, source_name, {
doctype: { doctype: {
"doctype": target_doctype, "doctype": target_doctype,
@ -1560,15 +1608,7 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
"taxes_and_charges" "taxes_and_charges"
] ]
}, },
doctype +" Item": { doctype +" Item": item_field_map
"doctype": target_doctype + " Item",
"field_no_map": [
"income_account",
"expense_account",
"cost_center",
"warehouse"
]
}
}, target_doc, set_missing_values) }, target_doc, set_missing_values)

View File

@ -14,8 +14,8 @@ frappe.listview_settings['Sales Invoice'] = {
"Credit Note Issued": "darkgrey", "Credit Note Issued": "darkgrey",
"Unpaid and Discounted": "orange", "Unpaid and Discounted": "orange",
"Overdue and Discounted": "red", "Overdue and Discounted": "red",
"Overdue": "red" "Overdue": "red",
"Internal Transfer": "darkgrey"
}; };
return [__(doc.status), status_color[doc.status], "status,=,"+doc.status]; return [__(doc.status), status_color[doc.status], "status,=,"+doc.status];
}, },

View File

@ -690,7 +690,8 @@ class TestSalesInvoice(unittest.TestCase):
self.assertTrue(gle) self.assertTrue(gle)
def test_pos_gl_entry_with_perpetual_inventory(self): def test_pos_gl_entry_with_perpetual_inventory(self):
make_pos_profile() make_pos_profile(company="_Test Company with perpetual inventory", income_account = "Sales - TCP1",
expense_account = "Cost of Goods Sold - TCP1", warehouse="Stores - TCP1", cost_center = "Main - TCP1", write_off_account="_Test Write Off - TCP1")
pr = make_purchase_receipt(company= "_Test Company with perpetual inventory", item_code= "_Test FG Item",warehouse= "Stores - TCP1",cost_center= "Main - TCP1") pr = make_purchase_receipt(company= "_Test Company with perpetual inventory", item_code= "_Test FG Item",warehouse= "Stores - TCP1",cost_center= "Main - TCP1")
@ -746,7 +747,8 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(pos_return.get('payments')[0].amount, -1000) self.assertEqual(pos_return.get('payments')[0].amount, -1000)
def test_pos_change_amount(self): def test_pos_change_amount(self):
make_pos_profile() make_pos_profile(company="_Test Company with perpetual inventory", income_account = "Sales - TCP1",
expense_account = "Cost of Goods Sold - TCP1", warehouse="Stores - TCP1", cost_center = "Main - TCP1", write_off_account="_Test Write Off - TCP1")
pr = make_purchase_receipt(company= "_Test Company with perpetual inventory", pr = make_purchase_receipt(company= "_Test Company with perpetual inventory",
item_code= "_Test FG Item",warehouse= "Stores - TCP1", cost_center= "Main - TCP1") item_code= "_Test FG Item",warehouse= "Stores - TCP1", cost_center= "Main - TCP1")
@ -1571,7 +1573,7 @@ class TestSalesInvoice(unittest.TestCase):
for gle in gl_entries: for gle in gl_entries:
self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center) self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
def test_sales_invoice_with_project_link(self): def test_sales_invoice_with_project_link(self):
from erpnext.projects.doctype.project.test_project import make_project from erpnext.projects.doctype.project.test_project import make_project
@ -1605,9 +1607,9 @@ class TestSalesInvoice(unittest.TestCase):
debit_in_account_currency, credit_in_account_currency debit_in_account_currency, credit_in_account_currency
from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s
order by account asc""", sales_invoice.name, as_dict=1) order by account asc""", sales_invoice.name, as_dict=1)
self.assertTrue(gl_entries) self.assertTrue(gl_entries)
for gle in gl_entries: for gle in gl_entries:
self.assertEqual(expected_values[gle.account]["project"], gle.project) self.assertEqual(expected_values[gle.account]["project"], gle.project)
@ -1779,6 +1781,60 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(target_doc.company, "_Test Company 1") self.assertEqual(target_doc.company, "_Test Company 1")
self.assertEqual(target_doc.supplier, "_Test Internal Supplier") self.assertEqual(target_doc.supplier, "_Test Internal Supplier")
def test_internal_transfer_gl_entry(self):
## Create internal transfer account
account = create_account(account_name="Unrealized Profit",
parent_account="Current Liabilities - TCP1", company="_Test Company with perpetual inventory")
frappe.db.set_value('Company', '_Test Company with perpetual inventory',
'unrealized_profit_loss_account', account)
customer = create_internal_customer("_Test Internal Customer 2", "_Test Company with perpetual inventory",
"_Test Company with perpetual inventory")
create_internal_supplier("_Test Internal Supplier 2", "_Test Company with perpetual inventory",
"_Test Company with perpetual inventory")
si = create_sales_invoice(
company = "_Test Company with perpetual inventory",
customer = customer,
debit_to = "Debtors - TCP1",
warehouse = "Stores - TCP1",
income_account = "Sales - TCP1",
expense_account = "Cost of Goods Sold - TCP1",
cost_center = "Main - TCP1",
currency = "INR",
do_not_save = 1
)
si.selling_price_list = "_Test Price List Rest of the World"
si.update_stock = 1
si.items[0].target_warehouse = 'Work In Progress - TCP1'
add_taxes(si)
si.save()
si.submit()
target_doc = make_inter_company_transaction("Sales Invoice", si.name)
target_doc.company = '_Test Company with perpetual inventory'
target_doc.items[0].warehouse = 'Finished Goods - TCP1'
add_taxes(target_doc)
target_doc.save()
target_doc.submit()
si_gl_entries = [
["_Test Account Excise Duty - TCP1", 0.0, 12.0, nowdate()],
["Unrealized Profit - TCP1", 12.0, 0.0, nowdate()]
]
check_gl_entries(self, si.name, si_gl_entries, add_days(nowdate(), -1))
pi_gl_entries = [
["_Test Account Excise Duty - TCP1", 12.0 , 0.0, nowdate()],
["Unrealized Profit - TCP1", 0.0, 12.0, nowdate()]
]
check_gl_entries(self, target_doc.name, pi_gl_entries, add_days(nowdate(), -1))
def test_eway_bill_json(self): def test_eway_bill_json(self):
if not frappe.db.exists('Address', '_Test Address for Eway bill-Billing'): if not frappe.db.exists('Address', '_Test Address for Eway bill-Billing'):
address = frappe.get_doc({ address = frappe.get_doc({
@ -2037,4 +2093,57 @@ def get_taxes_and_charges():
"parentfield": "taxes", "parentfield": "taxes",
"rate": 2, "rate": 2,
"row_id": 1 "row_id": 1
}] }]
def create_internal_customer(customer_name, represents_company, allowed_to_interact_with):
if not frappe.db.exists("Customer", customer_name):
customer = frappe.get_doc({
"customer_group": "_Test Customer Group",
"customer_name": customer_name,
"customer_type": "Individual",
"doctype": "Customer",
"territory": "_Test Territory",
"is_internal_customer": 1,
"represents_company": represents_company
})
customer.append("companies", {
"company": allowed_to_interact_with
})
customer.insert()
customer_name = customer.name
else:
customer_name = frappe.db.get_value("Customer", customer_name)
return customer_name
def create_internal_supplier(supplier_name, represents_company, allowed_to_interact_with):
if not frappe.db.exists("Supplier", supplier_name):
supplier = frappe.get_doc({
"supplier_group": "_Test Supplier Group",
"supplier_name": supplier_name,
"doctype": "Supplier",
"is_internal_supplier": 1,
"represents_company": represents_company
})
supplier.append("companies", {
"company": allowed_to_interact_with
})
supplier.insert()
supplier_name = supplier.name
else:
supplier_name = frappe.db.exists("Supplier", supplier_name)
return supplier_name
def add_taxes(doc):
doc.append('taxes', {
'account_head': '_Test Account Excise Duty - TCP1',
"charge_type": "On Net Total",
"cost_center": "Main - TCP1",
"description": "Excise Duty",
"rate": 12
})

View File

@ -42,11 +42,13 @@
{% if(filters.show_future_payments) { %} {% if(filters.show_future_payments) { %}
{% var balance_row = data.slice(-1).pop(); {% var balance_row = data.slice(-1).pop();
var range1 = report.columns[11].label; var start = filters.based_on_payment_terms ? 13 : 11;
var range2 = report.columns[12].label; var range1 = report.columns[start].label;
var range3 = report.columns[13].label; var range2 = report.columns[start+1].label;
var range4 = report.columns[14].label; var range3 = report.columns[start+2].label;
var range5 = report.columns[15].label; var range4 = report.columns[start+3].label;
var range5 = report.columns[start+4].label;
var range6 = report.columns[start+5].label;
%} %}
{% if(balance_row) { %} {% if(balance_row) { %}
<table class="table table-bordered table-condensed"> <table class="table table-bordered table-condensed">
@ -70,20 +72,34 @@
<th>{%= __(range3) %}</th> <th>{%= __(range3) %}</th>
<th>{%= __(range4) %}</th> <th>{%= __(range4) %}</th>
<th>{%= __(range5) %}</th> <th>{%= __(range5) %}</th>
<th>{%= __(range6) %}</th>
<th>{%= __("Total") %}</th> <th>{%= __("Total") %}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr> <tr>
<td>{%= __("Total Outstanding") %}</td> <td>{%= __("Total Outstanding") %}</td>
<td class="text-right">{%= format_number(balance_row["range1"], null, 2) %}</td> <td class="text-right">
<td class="text-right">{%= format_currency(balance_row["range2"]) %}</td> {%= format_number(balance_row["age"], null, 2) %}
<td class="text-right">{%= format_currency(balance_row["range3"]) %}</td> </td>
<td class="text-right">{%= format_currency(balance_row["range4"]) %}</td> <td class="text-right">
<td class="text-right">{%= format_currency(balance_row["range5"]) %}</td> {%= format_currency(balance_row["range1"], data[data.length-1]["currency"]) %}
</td>
<td class="text-right">
{%= format_currency(balance_row["range2"], data[data.length-1]["currency"]) %}
</td>
<td class="text-right">
{%= format_currency(balance_row["range3"], data[data.length-1]["currency"]) %}
</td>
<td class="text-right">
{%= format_currency(balance_row["range4"], data[data.length-1]["currency"]) %}
</td>
<td class="text-right">
{%= format_currency(balance_row["range5"], data[data.length-1]["currency"]) %}
</td>
<td class="text-right"> <td class="text-right">
{%= format_currency(flt(balance_row["outstanding"]), data[data.length-1]["currency"]) %} {%= format_currency(flt(balance_row["outstanding"]), data[data.length-1]["currency"]) %}
</td> </td>
</tr> </tr>
<td>{%= __("Future Payments") %}</td> <td>{%= __("Future Payments") %}</td>
<td></td> <td></td>
@ -91,6 +107,7 @@
<td></td> <td></td>
<td></td> <td></td>
<td></td> <td></td>
<td></td>
<td class="text-right"> <td class="text-right">
{%= format_currency(flt(balance_row[("future_amount")]), data[data.length-1]["currency"]) %} {%= format_currency(flt(balance_row[("future_amount")]), data[data.length-1]["currency"]) %}
</td> </td>
@ -101,6 +118,7 @@
<th></th> <th></th>
<th></th> <th></th>
<th></th> <th></th>
<th></th>
<th class="text-right"> <th class="text-right">
{%= format_currency(flt(balance_row["outstanding"] - balance_row[("future_amount")]), data[data.length-1]["currency"]) %}</th> {%= format_currency(flt(balance_row["outstanding"] - balance_row[("future_amount")]), data[data.length-1]["currency"]) %}</th>
</tr> </tr>
@ -218,15 +236,15 @@
<td></td> <td></td>
<td style="text-align: right"><b>{%= __("Total") %}</b></td> <td style="text-align: right"><b>{%= __("Total") %}</b></td>
<td style="text-align: right"> <td style="text-align: right">
{%= format_currency(data[i]["invoiced"], data[0]["currency"] ) %}</td> {%= format_currency(data[i]["invoiced"], data[i]["currency"] ) %}</td>
{% if(!filters.show_future_payments) { %} {% if(!filters.show_future_payments) { %}
<td style="text-align: right"> <td style="text-align: right">
{%= format_currency(data[i]["paid"], data[0]["currency"]) %}</td> {%= format_currency(data[i]["paid"], data[i]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["credit_note"], data[0]["currency"]) %} </td> <td style="text-align: right">{%= format_currency(data[i]["credit_note"], data[i]["currency"]) %} </td>
{% } %} {% } %}
<td style="text-align: right"> <td style="text-align: right">
{%= format_currency(data[i]["outstanding"], data[0]["currency"]) %}</td> {%= format_currency(data[i]["outstanding"], data[i]["currency"]) %}</td>
{% if(filters.show_future_payments) { %} {% if(filters.show_future_payments) { %}
{% if(report.report_name === "Accounts Receivable") { %} {% if(report.report_name === "Accounts Receivable") { %}
@ -234,8 +252,8 @@
{%= data[i]["po_no"] %}</td> {%= data[i]["po_no"] %}</td>
{% } %} {% } %}
<td style="text-align: right">{%= data[i]["future_ref"] %}</td> <td style="text-align: right">{%= data[i]["future_ref"] %}</td>
<td style="text-align: right">{%= format_currency(data[i]["future_amount"], data[0]["currency"]) %}</td> <td style="text-align: right">{%= format_currency(data[i]["future_amount"], data[i]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["remaining_balance"], data[0]["currency"]) %}</td> <td style="text-align: right">{%= format_currency(data[i]["remaining_balance"], data[i]["currency"]) %}</td>
{% } %} {% } %}
{% } %} {% } %}
{% } else { %} {% } else { %}
@ -256,10 +274,10 @@
{% } else { %} {% } else { %}
<td><b>{%= __("Total") %}</b></td> <td><b>{%= __("Total") %}</b></td>
{% } %} {% } %}
<td style="text-align: right">{%= format_currency(data[i]["invoiced"], data[0]["currency"]) %}</td> <td style="text-align: right">{%= format_currency(data[i]["invoiced"], data[i]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["paid"], data[0]["currency"]) %}</td> <td style="text-align: right">{%= format_currency(data[i]["paid"], data[i]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["credit_note"], data[0]["currency"]) %}</td> <td style="text-align: right">{%= format_currency(data[i]["credit_note"], data[i]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["outstanding"], data[0]["currency"]) %}</td> <td style="text-align: right">{%= format_currency(data[i]["outstanding"], data[i]["currency"]) %}</td>
{% } %} {% } %}
{% } %} {% } %}
</tr> </tr>

View File

@ -14,11 +14,93 @@ def execute(filters=None):
def get_column(): def get_column():
return [ return [
_("Delivery Note") + ":Link/Delivery Note:120", _("Status") + "::120", _("Date") + ":Date:100", {
_("Suplier") + ":Link/Customer:120", _("Customer Name") + "::120", "label": _("Delivery Note"),
_("Project") + ":Link/Project:120", _("Item Code") + ":Link/Item:120", "fieldname": "name",
_("Amount") + ":Currency:100", _("Billed Amount") + ":Currency:100", _("Pending Amount") + ":Currency:100", "fieldtype": "Link",
_("Item Name") + "::120", _("Description") + "::120", _("Company") + ":Link/Company:120", "options": "Delivery Note",
"width": 160
},
{
"label": _("Date"),
"fieldname": "date",
"fieldtype": "Date",
"width": 100
},
{
"label": _("Customer"),
"fieldname": "customer",
"fieldtype": "Link",
"options": "Customer",
"width": 120
},
{
"label": _("Customer Name"),
"fieldname": "customer_name",
"fieldtype": "Data",
"width": 120
},
{
"label": _("Item Code"),
"fieldname": "item_code",
"fieldtype": "Link",
"options": "Item",
"width": 120
},
{
"label": _("Amount"),
"fieldname": "amount",
"fieldtype": "Currency",
"width": 100,
"options": "Company:company:default_currency"
},
{
"label": _("Billed Amount"),
"fieldname": "billed_amount",
"fieldtype": "Currency",
"width": 100,
"options": "Company:company:default_currency"
},
{
"label": _("Returned Amount"),
"fieldname": "returned_amount",
"fieldtype": "Currency",
"width": 120,
"options": "Company:company:default_currency"
},
{
"label": _("Pending Amount"),
"fieldname": "pending_amount",
"fieldtype": "Currency",
"width": 120,
"options": "Company:company:default_currency"
},
{
"label": _("Item Name"),
"fieldname": "item_name",
"fieldtype": "Data",
"width": 120
},
{
"label": _("Description"),
"fieldname": "description",
"fieldtype": "Data",
"width": 120
},
{
"label": _("Project"),
"fieldname": "project",
"fieldtype": "Link",
"options": "Project",
"width": 120
},
{
"label": _("Company"),
"fieldname": "company",
"fieldtype": "Link",
"options": "Company",
"width": 120
}
] ]
def get_args(): def get_args():

View File

@ -8,6 +8,7 @@ from frappe.utils import flt
from erpnext.accounts.report.item_wise_sales_register.item_wise_sales_register import (get_tax_accounts, from erpnext.accounts.report.item_wise_sales_register.item_wise_sales_register import (get_tax_accounts,
get_grand_total, add_total_row, get_display_value, get_group_by_and_display_fields, add_sub_total_row, get_grand_total, add_total_row, get_display_value, get_group_by_and_display_fields, add_sub_total_row,
get_group_by_conditions) get_group_by_conditions)
from erpnext.selling.report.item_wise_sales_history.item_wise_sales_history import get_item_details
def execute(filters=None): def execute(filters=None):
return _execute(filters) return _execute(filters)
@ -22,7 +23,7 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
aii_account_map = get_aii_accounts() aii_account_map = get_aii_accounts()
if item_list: if item_list:
itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency, itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency,
doctype="Purchase Invoice", tax_doctype="Purchase Taxes and Charges") doctype='Purchase Invoice', tax_doctype='Purchase Taxes and Charges')
po_pr_map = get_purchase_receipts_against_purchase_order(item_list) po_pr_map = get_purchase_receipts_against_purchase_order(item_list)
@ -34,10 +35,14 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
if filters.get('group_by'): if filters.get('group_by'):
grand_total = get_grand_total(filters, 'Purchase Invoice') grand_total = get_grand_total(filters, 'Purchase Invoice')
item_details = get_item_details()
for d in item_list: for d in item_list:
if not d.stock_qty: if not d.stock_qty:
continue continue
item_record = item_details.get(d.item_code)
purchase_receipt = None purchase_receipt = None
if d.purchase_receipt: if d.purchase_receipt:
purchase_receipt = d.purchase_receipt purchase_receipt = d.purchase_receipt
@ -48,8 +53,8 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
row = { row = {
'item_code': d.item_code, 'item_code': d.item_code,
'item_name': d.item_name, 'item_name': item_record.item_name,
'item_group': d.item_group, 'item_group': item_record.item_group,
'description': d.description, 'description': d.description,
'invoice': d.parent, 'invoice': d.parent,
'posting_date': d.posting_date, 'posting_date': d.posting_date,
@ -81,10 +86,10 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
for tax in tax_columns: for tax in tax_columns:
item_tax = itemised_tax.get(d.name, {}).get(tax, {}) item_tax = itemised_tax.get(d.name, {}).get(tax, {})
row.update({ row.update({
frappe.scrub(tax + ' Rate'): item_tax.get("tax_rate", 0), frappe.scrub(tax + ' Rate'): item_tax.get('tax_rate', 0),
frappe.scrub(tax + ' Amount'): item_tax.get("tax_amount", 0), frappe.scrub(tax + ' Amount'): item_tax.get('tax_amount', 0),
}) })
total_tax += flt(item_tax.get("tax_amount")) total_tax += flt(item_tax.get('tax_amount'))
row.update({ row.update({
'total_tax': total_tax, 'total_tax': total_tax,
@ -309,8 +314,8 @@ def get_items(filters, additional_query_columns):
select select
`tabPurchase Invoice Item`.`name`, `tabPurchase Invoice Item`.`parent`, `tabPurchase Invoice Item`.`name`, `tabPurchase Invoice Item`.`parent`,
`tabPurchase Invoice`.posting_date, `tabPurchase Invoice`.credit_to, `tabPurchase Invoice`.company, `tabPurchase Invoice`.posting_date, `tabPurchase Invoice`.credit_to, `tabPurchase Invoice`.company,
`tabPurchase Invoice`.supplier, `tabPurchase Invoice`.remarks, `tabPurchase Invoice`.base_net_total, `tabPurchase Invoice Item`.`item_code`, `tabPurchase Invoice`.supplier, `tabPurchase Invoice`.remarks, `tabPurchase Invoice`.base_net_total,
`tabPurchase Invoice Item`.`item_name`, `tabPurchase Invoice Item`.`item_group`, `tabPurchase Invoice Item`.description, `tabPurchase Invoice Item`.`item_code`, `tabPurchase Invoice Item`.description,
`tabPurchase Invoice Item`.`project`, `tabPurchase Invoice Item`.`purchase_order`, `tabPurchase Invoice Item`.`project`, `tabPurchase Invoice Item`.`purchase_order`,
`tabPurchase Invoice Item`.`purchase_receipt`, `tabPurchase Invoice Item`.`po_detail`, `tabPurchase Invoice Item`.`purchase_receipt`, `tabPurchase Invoice Item`.`po_detail`,
`tabPurchase Invoice Item`.`expense_account`, `tabPurchase Invoice Item`.`stock_qty`, `tabPurchase Invoice Item`.`expense_account`, `tabPurchase Invoice Item`.`stock_qty`,

View File

@ -8,6 +8,7 @@ from frappe.utils import flt, cstr
from frappe.model.meta import get_field_precision from frappe.model.meta import get_field_precision
from frappe.utils.xlsxutils import handle_html from frappe.utils.xlsxutils import handle_html
from erpnext.accounts.report.sales_register.sales_register import get_mode_of_payments from erpnext.accounts.report.sales_register.sales_register import get_mode_of_payments
from erpnext.selling.report.item_wise_sales_history.item_wise_sales_history import get_item_details, get_customer_details
def execute(filters=None): def execute(filters=None):
return _execute(filters) return _execute(filters)
@ -16,7 +17,7 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
if not filters: filters = {} if not filters: filters = {}
columns = get_columns(additional_table_columns, filters) columns = get_columns(additional_table_columns, filters)
company_currency = frappe.get_cached_value('Company', filters.get("company"), "default_currency") company_currency = frappe.get_cached_value('Company', filters.get('company'), 'default_currency')
item_list = get_items(filters, additional_query_columns) item_list = get_items(filters, additional_query_columns)
if item_list: if item_list:
@ -33,7 +34,13 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
if filters.get('group_by'): if filters.get('group_by'):
grand_total = get_grand_total(filters, 'Sales Invoice') grand_total = get_grand_total(filters, 'Sales Invoice')
customer_details = get_customer_details()
item_details = get_item_details()
for d in item_list: for d in item_list:
customer_record = customer_details.get(d.customer)
item_record = item_details.get(d.item_code)
delivery_note = None delivery_note = None
if d.delivery_note: if d.delivery_note:
delivery_note = d.delivery_note delivery_note = d.delivery_note
@ -45,14 +52,14 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
row = { row = {
'item_code': d.item_code, 'item_code': d.item_code,
'item_name': d.item_name, 'item_name': item_record.item_name,
'item_group': d.item_group, 'item_group': item_record.item_group,
'description': d.description, 'description': d.description,
'invoice': d.parent, 'invoice': d.parent,
'posting_date': d.posting_date, 'posting_date': d.posting_date,
'customer': d.customer, 'customer': d.customer,
'customer_name': d.customer_name, 'customer_name': customer_record.customer_name,
'customer_group': d.customer_group, 'customer_group': customer_record.customer_group,
} }
if additional_query_columns: if additional_query_columns:
@ -90,10 +97,10 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
for tax in tax_columns: for tax in tax_columns:
item_tax = itemised_tax.get(d.name, {}).get(tax, {}) item_tax = itemised_tax.get(d.name, {}).get(tax, {})
row.update({ row.update({
frappe.scrub(tax + ' Rate'): item_tax.get("tax_rate", 0), frappe.scrub(tax + ' Rate'): item_tax.get('tax_rate', 0),
frappe.scrub(tax + ' Amount'): item_tax.get("tax_amount", 0), frappe.scrub(tax + ' Amount'): item_tax.get('tax_amount', 0),
}) })
total_tax += flt(item_tax.get("tax_amount")) total_tax += flt(item_tax.get('tax_amount'))
row.update({ row.update({
'total_tax': total_tax, 'total_tax': total_tax,
@ -226,7 +233,7 @@ def get_columns(additional_table_columns, filters):
if filters.get('group_by') != 'Territory': if filters.get('group_by') != 'Territory':
columns.extend([ columns.extend([
{ {
'label': _("Territory"), 'label': _('Territory'),
'fieldname': 'territory', 'fieldname': 'territory',
'fieldtype': 'Link', 'fieldtype': 'Link',
'options': 'Territory', 'options': 'Territory',
@ -374,13 +381,12 @@ def get_items(filters, additional_query_columns):
`tabSales Invoice`.posting_date, `tabSales Invoice`.debit_to, `tabSales Invoice`.posting_date, `tabSales Invoice`.debit_to,
`tabSales Invoice`.project, `tabSales Invoice`.customer, `tabSales Invoice`.remarks, `tabSales Invoice`.project, `tabSales Invoice`.customer, `tabSales Invoice`.remarks,
`tabSales Invoice`.territory, `tabSales Invoice`.company, `tabSales Invoice`.base_net_total, `tabSales Invoice`.territory, `tabSales Invoice`.company, `tabSales Invoice`.base_net_total,
`tabSales Invoice Item`.item_code, `tabSales Invoice Item`.item_name, `tabSales Invoice Item`.item_code, `tabSales Invoice Item`.description,
`tabSales Invoice Item`.item_group, `tabSales Invoice Item`.description, `tabSales Invoice Item`.sales_order, `tabSales Invoice Item`.sales_order, `tabSales Invoice Item`.delivery_note,
`tabSales Invoice Item`.delivery_note, `tabSales Invoice Item`.income_account, `tabSales Invoice Item`.income_account, `tabSales Invoice Item`.cost_center,
`tabSales Invoice Item`.cost_center, `tabSales Invoice Item`.stock_qty, `tabSales Invoice Item`.stock_qty, `tabSales Invoice Item`.stock_uom,
`tabSales Invoice Item`.stock_uom, `tabSales Invoice Item`.base_net_rate, `tabSales Invoice Item`.base_net_rate, `tabSales Invoice Item`.base_net_amount,
`tabSales Invoice Item`.base_net_amount, `tabSales Invoice`.customer_name, `tabSales Invoice`.customer_name, `tabSales Invoice`.customer_group, `tabSales Invoice Item`.so_detail,
`tabSales Invoice`.customer_group, `tabSales Invoice Item`.so_detail,
`tabSales Invoice`.update_stock, `tabSales Invoice Item`.uom, `tabSales Invoice Item`.qty {0} `tabSales Invoice`.update_stock, `tabSales Invoice Item`.uom, `tabSales Invoice Item`.qty {0}
from `tabSales Invoice`, `tabSales Invoice Item` from `tabSales Invoice`, `tabSales Invoice Item`
where `tabSales Invoice`.name = `tabSales Invoice Item`.parent where `tabSales Invoice`.name = `tabSales Invoice Item`.parent
@ -417,14 +423,14 @@ def get_deducted_taxes():
return frappe.db.sql_list("select name from `tabPurchase Taxes and Charges` where add_deduct_tax = 'Deduct'") return frappe.db.sql_list("select name from `tabPurchase Taxes and Charges` where add_deduct_tax = 'Deduct'")
def get_tax_accounts(item_list, columns, company_currency, def get_tax_accounts(item_list, columns, company_currency,
doctype="Sales Invoice", tax_doctype="Sales Taxes and Charges"): doctype='Sales Invoice', tax_doctype='Sales Taxes and Charges'):
import json import json
item_row_map = {} item_row_map = {}
tax_columns = [] tax_columns = []
invoice_item_row = {} invoice_item_row = {}
itemised_tax = {} itemised_tax = {}
tax_amount_precision = get_field_precision(frappe.get_meta(tax_doctype).get_field("tax_amount"), tax_amount_precision = get_field_precision(frappe.get_meta(tax_doctype).get_field('tax_amount'),
currency=company_currency) or 2 currency=company_currency) or 2
for d in item_list: for d in item_list:
@ -469,8 +475,8 @@ def get_tax_accounts(item_list, columns, company_currency,
tax_rate = tax_data tax_rate = tax_data
tax_amount = 0 tax_amount = 0
if charge_type == "Actual" and not tax_rate: if charge_type == 'Actual' and not tax_rate:
tax_rate = "NA" tax_rate = 'NA'
item_net_amount = sum([flt(d.base_net_amount) item_net_amount = sum([flt(d.base_net_amount)
for d in item_row_map.get(parent, {}).get(item_code, [])]) for d in item_row_map.get(parent, {}).get(item_code, [])])
@ -484,17 +490,17 @@ def get_tax_accounts(item_list, columns, company_currency,
if (doctype == 'Purchase Invoice' and name in deducted_tax) else tax_value) if (doctype == 'Purchase Invoice' and name in deducted_tax) else tax_value)
itemised_tax.setdefault(d.name, {})[description] = frappe._dict({ itemised_tax.setdefault(d.name, {})[description] = frappe._dict({
"tax_rate": tax_rate, 'tax_rate': tax_rate,
"tax_amount": tax_value 'tax_amount': tax_value
}) })
except ValueError: except ValueError:
continue continue
elif charge_type == "Actual" and tax_amount: elif charge_type == 'Actual' and tax_amount:
for d in invoice_item_row.get(parent, []): for d in invoice_item_row.get(parent, []):
itemised_tax.setdefault(d.name, {})[description] = frappe._dict({ itemised_tax.setdefault(d.name, {})[description] = frappe._dict({
"tax_rate": "NA", 'tax_rate': 'NA',
"tax_amount": flt((tax_amount * d.base_net_amount) / d.base_net_total, 'tax_amount': flt((tax_amount * d.base_net_amount) / d.base_net_total,
tax_amount_precision) tax_amount_precision)
}) })
@ -563,7 +569,7 @@ def add_total_row(data, filters, prev_group_by_value, item, total_row_map,
}) })
total_row_map.setdefault('total_row', { total_row_map.setdefault('total_row', {
subtotal_display_field: "Total", subtotal_display_field: 'Total',
'stock_qty': 0.0, 'stock_qty': 0.0,
'amount': 0.0, 'amount': 0.0,
'bold': 1, 'bold': 1,

View File

@ -17,18 +17,26 @@ def get_ordered_to_be_billed_data(args):
return frappe.db.sql(""" return frappe.db.sql("""
Select Select
`{parent_tab}`.name, `{parent_tab}`.status, `{parent_tab}`.{date_field}, `{parent_tab}`.{party}, `{parent_tab}`.{party}_name, `{parent_tab}`.name, `{parent_tab}`.{date_field},
{project_field}, `{child_tab}`.item_code, `{child_tab}`.base_amount, `{parent_tab}`.{party}, `{parent_tab}`.{party}_name,
`{child_tab}`.item_code,
`{child_tab}`.base_amount,
(`{child_tab}`.billed_amt * ifnull(`{parent_tab}`.conversion_rate, 1)), (`{child_tab}`.billed_amt * ifnull(`{parent_tab}`.conversion_rate, 1)),
(`{child_tab}`.base_amount - (`{child_tab}`.billed_amt * ifnull(`{parent_tab}`.conversion_rate, 1))), (`{child_tab}`.base_rate * ifnull(`{child_tab}`.returned_qty, 0)),
`{child_tab}`.item_name, `{child_tab}`.description, `{parent_tab}`.company (`{child_tab}`.base_amount -
(`{child_tab}`.billed_amt * ifnull(`{parent_tab}`.conversion_rate, 1)) -
(`{child_tab}`.base_rate * ifnull(`{child_tab}`.returned_qty, 0))),
`{child_tab}`.item_name, `{child_tab}`.description,
{project_field}, `{parent_tab}`.company
from from
`{parent_tab}`, `{child_tab}` `{parent_tab}`, `{child_tab}`
where where
`{parent_tab}`.name = `{child_tab}`.parent and `{parent_tab}`.docstatus = 1 `{parent_tab}`.name = `{child_tab}`.parent and `{parent_tab}`.docstatus = 1
and `{parent_tab}`.status not in ('Closed', 'Completed') and `{parent_tab}`.status not in ('Closed', 'Completed')
and `{child_tab}`.amount > 0 and round(`{child_tab}`.billed_amt * and `{child_tab}`.amount > 0
ifnull(`{parent_tab}`.conversion_rate, 1), {precision}) < `{child_tab}`.base_amount and (`{child_tab}`.base_amount -
round(`{child_tab}`.billed_amt * ifnull(`{parent_tab}`.conversion_rate, 1), {precision}) -
(`{child_tab}`.base_rate * ifnull(`{child_tab}`.returned_qty, 0))) > 0
order by order by
`{parent_tab}`.{order} {order_by} `{parent_tab}`.{order} {order_by}
""".format(parent_tab = 'tab' + doctype, child_tab = 'tab' + child_tab, precision= precision, party = party, """.format(parent_tab = 'tab' + doctype, child_tab = 'tab' + child_tab, precision= precision, party = party,

View File

@ -14,11 +14,93 @@ def execute(filters=None):
def get_column(): def get_column():
return [ return [
_("Purchase Receipt") + ":Link/Purchase Receipt:120", _("Status") + "::120", _("Date") + ":Date:100", {
_("Supplier") + ":Link/Supplier:120", _("Supplier Name") + "::120", "label": _("Purchase Receipt"),
_("Project") + ":Link/Project:120", _("Item Code") + ":Link/Item:120", "fieldname": "name",
_("Amount") + ":Currency:100", _("Billed Amount") + ":Currency:100", _("Amount to Bill") + ":Currency:100", "fieldtype": "Link",
_("Item Name") + "::120", _("Description") + "::120", _("Company") + ":Link/Company:120", "options": "Purchase Receipt",
"width": 160
},
{
"label": _("Date"),
"fieldname": "date",
"fieldtype": "Date",
"width": 100
},
{
"label": _("Supplier"),
"fieldname": "supplier",
"fieldtype": "Link",
"options": "Supplier",
"width": 120
},
{
"label": _("Supplier Name"),
"fieldname": "supplier_name",
"fieldtype": "Data",
"width": 120
},
{
"label": _("Item Code"),
"fieldname": "item_code",
"fieldtype": "Link",
"options": "Item",
"width": 120
},
{
"label": _("Amount"),
"fieldname": "amount",
"fieldtype": "Currency",
"width": 100,
"options": "Company:company:default_currency"
},
{
"label": _("Billed Amount"),
"fieldname": "billed_amount",
"fieldtype": "Currency",
"width": 100,
"options": "Company:company:default_currency"
},
{
"label": _("Returned Amount"),
"fieldname": "returned_amount",
"fieldtype": "Currency",
"width": 120,
"options": "Company:company:default_currency"
},
{
"label": _("Pending Amount"),
"fieldname": "pending_amount",
"fieldtype": "Currency",
"width": 120,
"options": "Company:company:default_currency"
},
{
"label": _("Item Name"),
"fieldname": "item_name",
"fieldtype": "Data",
"width": 120
},
{
"label": _("Description"),
"fieldname": "description",
"fieldtype": "Data",
"width": 120
},
{
"label": _("Project"),
"fieldname": "project",
"fieldtype": "Link",
"options": "Project",
"width": 120
},
{
"label": _("Company"),
"fieldname": "company",
"fieldtype": "Link",
"options": "Company",
"width": 120
}
] ]
def get_args(): def get_args():

View File

@ -78,7 +78,10 @@ def get_fiscal_years(transaction_date=None, fiscal_year=None, label="Date", verb
else: else:
return ((fy.name, fy.year_start_date, fy.year_end_date),) return ((fy.name, fy.year_start_date, fy.year_end_date),)
error_msg = _("""{0} {1} not in any active Fiscal Year.""").format(label, formatdate(transaction_date)) error_msg = _("""{0} {1} is not in any active Fiscal Year""").format(label, formatdate(transaction_date))
if company:
error_msg = _("""{0} for {1}""").format(error_msg, frappe.bold(company))
if verbose==1: frappe.msgprint(error_msg) if verbose==1: frappe.msgprint(error_msg)
raise FiscalYearError(error_msg) raise FiscalYearError(error_msg)

View File

@ -13,8 +13,8 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import g
class AssetValueAdjustment(Document): class AssetValueAdjustment(Document):
def validate(self): def validate(self):
self.validate_date() self.validate_date()
self.set_difference_amount()
self.set_current_asset_value() self.set_current_asset_value()
self.set_difference_amount()
def on_submit(self): def on_submit(self):
self.make_depreciation_entry() self.make_depreciation_entry()
@ -25,7 +25,7 @@ class AssetValueAdjustment(Document):
frappe.throw(_("Cancel the journal entry {0} first").format(self.journal_entry)) frappe.throw(_("Cancel the journal entry {0} first").format(self.journal_entry))
self.reschedule_depreciations(self.current_asset_value) self.reschedule_depreciations(self.current_asset_value)
def validate_date(self): def validate_date(self):
asset_purchase_date = frappe.db.get_value('Asset', self.asset, 'purchase_date') asset_purchase_date = frappe.db.get_value('Asset', self.asset, 'purchase_date')
if getdate(self.date) < getdate(asset_purchase_date): if getdate(self.date) < getdate(asset_purchase_date):
@ -53,6 +53,7 @@ class AssetValueAdjustment(Document):
je.posting_date = self.date je.posting_date = self.date
je.company = self.company je.company = self.company
je.remark = "Depreciation Entry against {0} worth {1}".format(self.asset, self.difference_amount) je.remark = "Depreciation Entry against {0} worth {1}".format(self.asset, self.difference_amount)
je.finance_book = self.finance_book
credit_entry = { credit_entry = {
"account": accumulated_depreciation_account, "account": accumulated_depreciation_account,
@ -78,7 +79,7 @@ class AssetValueAdjustment(Document):
debit_entry.update({ debit_entry.update({
dimension['fieldname']: self.get(dimension['fieldname']) or dimension.get('default_dimension') dimension['fieldname']: self.get(dimension['fieldname']) or dimension.get('default_dimension')
}) })
je.append("accounts", credit_entry) je.append("accounts", credit_entry)
je.append("accounts", debit_entry) je.append("accounts", debit_entry)

View File

@ -75,24 +75,23 @@ def get_data(filters):
for asset in assets_record: for asset in assets_record:
asset_value = asset.gross_purchase_amount - flt(asset.opening_accumulated_depreciation) \ asset_value = asset.gross_purchase_amount - flt(asset.opening_accumulated_depreciation) \
- flt(depreciation_amount_map.get(asset.name)) - flt(depreciation_amount_map.get(asset.name))
if asset_value: row = {
row = { "asset_id": asset.asset_id,
"asset_id": asset.asset_id, "asset_name": asset.asset_name,
"asset_name": asset.asset_name, "status": asset.status,
"status": asset.status, "department": asset.department,
"department": asset.department, "cost_center": asset.cost_center,
"cost_center": asset.cost_center, "vendor_name": pr_supplier_map.get(asset.purchase_receipt) or pi_supplier_map.get(asset.purchase_invoice),
"vendor_name": pr_supplier_map.get(asset.purchase_receipt) or pi_supplier_map.get(asset.purchase_invoice), "gross_purchase_amount": asset.gross_purchase_amount,
"gross_purchase_amount": asset.gross_purchase_amount, "opening_accumulated_depreciation": asset.opening_accumulated_depreciation,
"opening_accumulated_depreciation": asset.opening_accumulated_depreciation, "depreciated_amount": depreciation_amount_map.get(asset.asset_id) or 0.0,
"depreciated_amount": depreciation_amount_map.get(asset.asset_id) or 0.0, "available_for_use_date": asset.available_for_use_date,
"available_for_use_date": asset.available_for_use_date, "location": asset.location,
"location": asset.location, "asset_category": asset.asset_category,
"asset_category": asset.asset_category, "purchase_date": asset.purchase_date,
"purchase_date": asset.purchase_date, "asset_value": asset_value
"asset_value": asset_value }
} data.append(row)
data.append(row)
return data return data

View File

@ -168,6 +168,7 @@
"bold": 1, "bold": 1,
"fieldname": "supplier", "fieldname": "supplier",
"fieldtype": "Link", "fieldtype": "Link",
"in_global_search": 1,
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Supplier", "label": "Supplier",
"oldfieldname": "supplier", "oldfieldname": "supplier",
@ -1106,7 +1107,7 @@
"idx": 105, "idx": 105,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-10-30 13:58:14.697921", "modified": "2020-12-03 16:46:44.229351",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Buying", "module": "Buying",
"name": "Purchase Order", "name": "Purchase Order",

View File

@ -290,11 +290,17 @@ erpnext.buying.RequestforQuotationController = erpnext.buying.BuyingController.e
dialog.show(); dialog.show();
}, __("Get Items From")); }, __("Get Items From"));
// Link Material Requests
this.frm.add_custom_button(__('Link to Material Requests'),
function() {
erpnext.buying.link_to_mrs(me.frm);
}, __("Tools"));
// Get Suppliers // Get Suppliers
this.frm.add_custom_button(__('Get Suppliers'), this.frm.add_custom_button(__('Get Suppliers'),
function() { function() {
me.get_suppliers_button(me.frm); me.get_suppliers_button(me.frm);
}); }, __("Tools"));
} }
}, },

View File

@ -18,7 +18,6 @@
"suppliers", "suppliers",
"items_section", "items_section",
"items", "items",
"link_to_mrs",
"supplier_response_section", "supplier_response_section",
"salutation", "salutation",
"subject", "subject",
@ -118,13 +117,6 @@
"reqd": 1 "reqd": 1
}, },
{ {
"depends_on": "eval:doc.docstatus===0 && (doc.items && doc.items.length)",
"fieldname": "link_to_mrs",
"fieldtype": "Button",
"label": "Link to Material Requests"
},
{
"depends_on": "eval:!doc.__islocal",
"fieldname": "supplier_response_section", "fieldname": "supplier_response_section",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Email Details" "label": "Email Details"
@ -260,7 +252,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-11-04 22:04:29.017134", "modified": "2020-11-05 22:04:29.017134",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Buying", "module": "Buying",
"name": "Request for Quotation", "name": "Request for Quotation",

View File

@ -49,6 +49,12 @@ class Supplier(TransactionBase):
msgprint(_("Series is mandatory"), raise_exception=1) msgprint(_("Series is mandatory"), raise_exception=1)
validate_party_accounts(self) validate_party_accounts(self)
self.validate_internal_supplier()
def validate_internal_supplier(self):
if self.is_internal_supplier and frappe.db.get_value("Supplier", {"represents_company": self.represents_company}, "name"):
frappe.throw(_("Internal Supplier for company {0} already exists").format(
frappe.bold(self.represents_company)))
def on_trash(self): def on_trash(self):
delete_contact_and_address('Supplier', self.name) delete_contact_and_address('Supplier', self.name)

View File

@ -50,6 +50,12 @@ erpnext.buying.SupplierQuotationController = erpnext.buying.BuyingController.ext
}) })
}, __("Get Items From")); }, __("Get Items From"));
// Link Material Requests
this.frm.add_custom_button(__('Link to Material Requests'),
function() {
erpnext.buying.link_to_mrs(me.frm);
}, __("Tools"));
this.frm.add_custom_button(__("Request for Quotation"), this.frm.add_custom_button(__("Request for Quotation"),
function() { function() {
if (!me.frm.doc.supplier) { if (!me.frm.doc.supplier) {

View File

@ -35,7 +35,6 @@
"ignore_pricing_rule", "ignore_pricing_rule",
"items_section", "items_section",
"items", "items",
"link_to_mrs",
"pricing_rule_details", "pricing_rule_details",
"pricing_rules", "pricing_rules",
"section_break_22", "section_break_22",
@ -322,12 +321,6 @@
"options": "Supplier Quotation Item", "options": "Supplier Quotation Item",
"reqd": 1 "reqd": 1
}, },
{
"depends_on": "eval:doc.docstatus===0 && (doc.items && doc.items.length)",
"fieldname": "link_to_mrs",
"fieldtype": "Button",
"label": "Link to material requests"
},
{ {
"fieldname": "pricing_rule_details", "fieldname": "pricing_rule_details",
"fieldtype": "Section Break", "fieldtype": "Section Break",
@ -806,9 +799,10 @@
], ],
"icon": "fa fa-shopping-cart", "icon": "fa fa-shopping-cart",
"idx": 29, "idx": 29,
"index_web_pages_for_search": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-10-30 13:58:33.043971", "modified": "2020-12-03 15:18:29.073368",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Buying", "module": "Buying",
"name": "Supplier Quotation", "name": "Supplier Quotation",

View File

@ -23,6 +23,8 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import g
from erpnext.stock.get_item_details import get_item_warehouse, _get_item_tax_template, get_item_tax_map from erpnext.stock.get_item_details import get_item_warehouse, _get_item_tax_template, get_item_tax_map
from erpnext.stock.doctype.packed_item.packed_item import make_packing_list from erpnext.stock.doctype.packed_item.packed_item import make_packing_list
class AccountMissingError(frappe.ValidationError): pass
force_item_fields = ("item_group", "brand", "stock_uom", "is_fixed_asset", "item_tax_rate", "pricing_rules") force_item_fields = ("item_group", "brand", "stock_uom", "is_fixed_asset", "item_tax_rate", "pricing_rules")
class AccountsController(TransactionBase): class AccountsController(TransactionBase):
@ -105,6 +107,8 @@ class AccountsController(TransactionBase):
else: else:
self.validate_deferred_start_and_end_date() self.validate_deferred_start_and_end_date()
self.set_inter_company_account()
validate_regional(self) validate_regional(self)
if self.doctype != 'Material Request': if self.doctype != 'Material Request':
apply_pricing_rule_on_transaction(self) apply_pricing_rule_on_transaction(self)
@ -735,6 +739,21 @@ class AccountsController(TransactionBase):
return self._abbr return self._abbr
def raise_missing_debit_credit_account_error(self, party_type, party):
"""Raise an error if debit to/credit to account does not exist."""
db_or_cr = frappe.bold("Debit To") if self.doctype == "Sales Invoice" else frappe.bold("Credit To")
rec_or_pay = "Receivable" if self.doctype == "Sales Invoice" else "Payable"
link_to_party = frappe.utils.get_link_to_form(party_type, party)
link_to_company = frappe.utils.get_link_to_form("Company", self.company)
message = _("{0} Account not found against Customer {1}.").format(db_or_cr, frappe.bold(party) or '')
message += "<br>" + _("Please set one of the following:") + "<br>"
message += "<br><ul><li>" + _("'Account' in the Accounting section of Customer {0}").format(link_to_party) + "</li>"
message += "<li>" + _("'Default {0} Account' in Company {1}").format(rec_or_pay, link_to_company) + "</li></ul>"
frappe.throw(message, title=_("Account Missing"), exc=AccountMissingError)
def validate_party(self): def validate_party(self):
party_type, party = self.get_party() party_type, party = self.get_party()
validate_party_frozen_disabled(party_type, party) validate_party_frozen_disabled(party_type, party)
@ -915,6 +934,38 @@ class AccountsController(TransactionBase):
else: else:
return frappe.db.get_single_value("Global Defaults", "disable_rounded_total") return frappe.db.get_single_value("Global Defaults", "disable_rounded_total")
def set_inter_company_account(self):
"""
Set intercompany account for inter warehouse transactions
This account will be used in case billing company and internal customer's
representation company is same
"""
if self.is_internal_transfer() and not self.unrealized_profit_loss_account:
unrealized_profit_loss_account = frappe.db.get_value('Company', self.company, 'unrealized_profit_loss_account')
if not unrealized_profit_loss_account:
msg = _("Please select Unrealized Profit / Loss account or add default Unrealized Profit / Loss account account for company {0}").format(
frappe.bold(self.company))
frappe.throw(msg)
self.unrealized_profit_loss_account = unrealized_profit_loss_account
def is_internal_transfer(self):
"""
It will an internal transfer if its an internal customer and representation
company is same as billing company
"""
if self.doctype == 'Sales Invoice':
internal_party_field = 'is_internal_customer'
else:
internal_party_field = 'is_internal_supplier'
if self.get(internal_party_field) and (self.represents_company == self.company):
return True
return False
@frappe.whitelist() @frappe.whitelist()
def get_tax_rate(account_head): def get_tax_rate(account_head):
return frappe.db.get_value("Account", account_head, ["tax_rate", "account_name"], as_dict=True) return frappe.db.get_value("Account", account_head, ["tax_rate", "account_name"], as_dict=True)

View File

@ -42,6 +42,7 @@ class BuyingController(StockController):
self.validate_items() self.validate_items()
self.set_qty_as_per_stock_uom() self.set_qty_as_per_stock_uom()
self.validate_stock_or_nonstock_items() self.validate_stock_or_nonstock_items()
self.update_tax_category_for_internal_transfer()
self.validate_warehouse() self.validate_warehouse()
self.validate_from_warehouse() self.validate_from_warehouse()
self.set_supplier_address() self.set_supplier_address()
@ -94,13 +95,23 @@ class BuyingController(StockController):
def validate_stock_or_nonstock_items(self): def validate_stock_or_nonstock_items(self):
if self.meta.get_field("taxes") and not self.get_stock_items() and not self.get_asset_items(): if self.meta.get_field("taxes") and not self.get_stock_items() and not self.get_asset_items():
tax_for_valuation = [d for d in self.get("taxes") msg = _('Tax Category has been changed to "Total" because all the Items are non-stock items')
self.update_tax_category(msg)
def update_tax_category_for_internal_transfer(self):
if self.doctype == 'Purchase Invoice' and self.is_internal_transfer():
msg = _('Tax Category has been changed to "Total" as its an internal purchase.')
self.update_tax_category(msg)
def update_tax_category(self, msg):
tax_for_valuation = [d for d in self.get("taxes")
if d.category in ["Valuation", "Valuation and Total"]] if d.category in ["Valuation", "Valuation and Total"]]
if tax_for_valuation: if tax_for_valuation:
for d in tax_for_valuation: for d in tax_for_valuation:
d.category = 'Total' d.category = 'Total'
msgprint(_('Tax Category has been changed to "Total" because all the Items are non-stock items'))
msgprint(msg)
def validate_asset_return(self): def validate_asset_return(self):
if self.doctype not in ['Purchase Receipt', 'Purchase Invoice'] or not self.is_return: if self.doctype not in ['Purchase Receipt', 'Purchase Invoice'] or not self.is_return:
@ -497,6 +508,10 @@ class BuyingController(StockController):
frappe.throw(_("Row {0}: Conversion Factor is mandatory").format(d.idx)) frappe.throw(_("Row {0}: Conversion Factor is mandatory").format(d.idx))
d.stock_qty = flt(d.qty) * flt(d.conversion_factor) d.stock_qty = flt(d.qty) * flt(d.conversion_factor)
if self.doctype=="Purchase Receipt" and d.meta.get_field("received_stock_qty"):
# Set Received Qty in Stock UOM
d.received_stock_qty = flt(d.received_qty) * flt(d.conversion_factor, d.precision("conversion_factor"))
def validate_purchase_return(self): def validate_purchase_return(self):
for d in self.get("items"): for d in self.get("items"):
if self.is_return and flt(d.rejected_qty) != 0: if self.is_return and flt(d.rejected_qty) != 0:

View File

@ -203,10 +203,37 @@ def get_already_returned_items(doc):
return items return items
def get_returned_qty_map_for_row(row_name, doctype):
child_doctype = doctype + " Item"
reference_field = frappe.scrub(child_doctype) if doctype == "Purchase Receipt" else "dn_detail"
fields = [
"sum(abs(`tab{0}`.qty)) as qty".format(child_doctype),
"sum(abs(`tab{0}`.stock_qty)) as stock_qty".format(child_doctype)
]
if doctype == "Purchase Receipt":
fields += [
"sum(abs(`tab{0}`.rejected_qty)) as rejected_qty".format(child_doctype),
"sum(abs(`tab{0}`.received_qty)) as received_qty".format(child_doctype),
"sum(abs(`tab{0}`.received_stock_qty)) as received_stock_qty".format(child_doctype)
]
data = frappe.db.get_list(doctype,
fields = fields,
filters = [
[doctype, "docstatus", "=", 1],
[doctype, "is_return", "=", 1],
[child_doctype, reference_field, "=", row_name]
])
return data[0]
def make_return_doc(doctype, source_name, target_doc=None): def make_return_doc(doctype, source_name, target_doc=None):
from frappe.model.mapper import get_mapped_doc from frappe.model.mapper import get_mapped_doc
company = frappe.db.get_value("Delivery Note", source_name, "company") company = frappe.db.get_value("Delivery Note", source_name, "company")
default_warehouse_for_sales_return = frappe.db.get_value("Company", company, "default_warehouse_for_sales_return") default_warehouse_for_sales_return = frappe.db.get_value("Company", company, "default_warehouse_for_sales_return")
def set_missing_values(source, target): def set_missing_values(source, target):
doc = frappe.get_doc(target) doc = frappe.get_doc(target)
doc.is_return = 1 doc.is_return = 1
@ -261,20 +288,25 @@ def make_return_doc(doctype, source_name, target_doc=None):
doc.run_method("calculate_taxes_and_totals") doc.run_method("calculate_taxes_and_totals")
def update_item(source_doc, target_doc, source_parent): def update_item(source_doc, target_doc, source_parent):
target_doc.qty = -1* source_doc.qty target_doc.qty = -1 * source_doc.qty
if doctype == "Purchase Receipt": if doctype == "Purchase Receipt":
target_doc.received_qty = -1* source_doc.received_qty returned_qty_map = get_returned_qty_map_for_row(source_doc.name, doctype)
target_doc.rejected_qty = -1* source_doc.rejected_qty target_doc.received_qty = -1 * flt(source_doc.received_qty - (returned_qty_map.get('received_qty') or 0))
target_doc.qty = -1* source_doc.qty target_doc.rejected_qty = -1 * flt(source_doc.rejected_qty - (returned_qty_map.get('rejected_qty') or 0))
target_doc.stock_qty = -1 * source_doc.stock_qty target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get('qty') or 0))
target_doc.stock_qty = -1 * flt(source_doc.stock_qty - (returned_qty_map.get('stock_qty') or 0))
target_doc.received_stock_qty = -1 * flt(source_doc.received_stock_qty - (returned_qty_map.get('received_stock_qty') or 0))
target_doc.purchase_order = source_doc.purchase_order target_doc.purchase_order = source_doc.purchase_order
target_doc.purchase_order_item = source_doc.purchase_order_item target_doc.purchase_order_item = source_doc.purchase_order_item
target_doc.rejected_warehouse = source_doc.rejected_warehouse target_doc.rejected_warehouse = source_doc.rejected_warehouse
target_doc.purchase_receipt_item = source_doc.name target_doc.purchase_receipt_item = source_doc.name
elif doctype == "Purchase Invoice": elif doctype == "Purchase Invoice":
target_doc.received_qty = -1* source_doc.received_qty target_doc.received_qty = -1 * source_doc.received_qty
target_doc.rejected_qty = -1* source_doc.rejected_qty target_doc.rejected_qty = -1 * source_doc.rejected_qty
target_doc.qty = -1* source_doc.qty target_doc.qty = -1* source_doc.qty
target_doc.stock_qty = -1 * source_doc.stock_qty target_doc.stock_qty = -1 * source_doc.stock_qty
target_doc.purchase_order = source_doc.purchase_order target_doc.purchase_order = source_doc.purchase_order
@ -285,6 +317,10 @@ def make_return_doc(doctype, source_name, target_doc=None):
target_doc.purchase_invoice_item = source_doc.name target_doc.purchase_invoice_item = source_doc.name
elif doctype == "Delivery Note": elif doctype == "Delivery Note":
returned_qty_map = get_returned_qty_map_for_row(source_doc.name, doctype)
target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get('qty') or 0))
target_doc.stock_qty = -1 * flt(source_doc.stock_qty - (returned_qty_map.get('stock_qty') or 0))
target_doc.against_sales_order = source_doc.against_sales_order target_doc.against_sales_order = source_doc.against_sales_order
target_doc.against_sales_invoice = source_doc.against_sales_invoice target_doc.against_sales_invoice = source_doc.against_sales_invoice
target_doc.so_detail = source_doc.so_detail target_doc.so_detail = source_doc.so_detail

View File

@ -42,7 +42,7 @@ class SellingController(StockController):
self.validate_max_discount() self.validate_max_discount()
self.validate_selling_price() self.validate_selling_price()
self.set_qty_as_per_stock_uom() self.set_qty_as_per_stock_uom()
self.set_po_nos() self.set_po_nos(for_validate=True)
self.set_gross_profit() self.set_gross_profit()
set_default_income_account_for_item(self) set_default_income_account_for_item(self)
self.set_customer_address() self.set_customer_address()
@ -370,20 +370,28 @@ class SellingController(StockController):
})) }))
self.make_sl_entries(sl_entries) self.make_sl_entries(sl_entries)
def set_po_nos(self): def set_po_nos(self, for_validate=False):
if self.doctype == 'Sales Invoice' and hasattr(self, "items"): if self.doctype == 'Sales Invoice' and hasattr(self, "items"):
if for_validate and self.po_no:
return
self.set_pos_for_sales_invoice() self.set_pos_for_sales_invoice()
if self.doctype == 'Delivery Note' and hasattr(self, "items"): if self.doctype == 'Delivery Note' and hasattr(self, "items"):
if for_validate and self.po_no:
return
self.set_pos_for_delivery_note() self.set_pos_for_delivery_note()
def set_pos_for_sales_invoice(self): def set_pos_for_sales_invoice(self):
po_nos = [] po_nos = []
if self.po_no:
po_nos.append(self.po_no)
self.get_po_nos('Sales Order', 'sales_order', po_nos) self.get_po_nos('Sales Order', 'sales_order', po_nos)
self.get_po_nos('Delivery Note', 'delivery_note', po_nos) self.get_po_nos('Delivery Note', 'delivery_note', po_nos)
self.po_no = ', '.join(list(set(x.strip() for x in ','.join(po_nos).split(',')))) self.po_no = ', '.join(list(set(x.strip() for x in ','.join(po_nos).split(','))))
def set_pos_for_delivery_note(self): def set_pos_for_delivery_note(self):
po_nos = [] po_nos = []
if self.po_no:
po_nos.append(self.po_no)
self.get_po_nos('Sales Order', 'against_sales_order', po_nos) self.get_po_nos('Sales Order', 'against_sales_order', po_nos)
self.get_po_nos('Sales Invoice', 'against_sales_invoice', po_nos) self.get_po_nos('Sales Invoice', 'against_sales_invoice', po_nos)
self.po_no = ', '.join(list(set(x.strip() for x in ','.join(po_nos).split(',')))) self.po_no = ', '.join(list(set(x.strip() for x in ','.join(po_nos).split(','))))

View File

@ -58,6 +58,7 @@ status_map = {
"Delivery Note": [ "Delivery Note": [
["Draft", None], ["Draft", None],
["To Bill", "eval:self.per_billed < 100 and self.docstatus == 1"], ["To Bill", "eval:self.per_billed < 100 and self.docstatus == 1"],
["Return Issued", "eval:self.per_returned == 100 and self.docstatus == 1"],
["Completed", "eval:self.per_billed == 100 and self.docstatus == 1"], ["Completed", "eval:self.per_billed == 100 and self.docstatus == 1"],
["Cancelled", "eval:self.docstatus==2"], ["Cancelled", "eval:self.docstatus==2"],
["Closed", "eval:self.status=='Closed'"], ["Closed", "eval:self.status=='Closed'"],
@ -65,6 +66,7 @@ status_map = {
"Purchase Receipt": [ "Purchase Receipt": [
["Draft", None], ["Draft", None],
["To Bill", "eval:self.per_billed < 100 and self.docstatus == 1"], ["To Bill", "eval:self.per_billed < 100 and self.docstatus == 1"],
["Return Issued", "eval:self.per_returned == 100 and self.docstatus == 1"],
["Completed", "eval:self.per_billed == 100 and self.docstatus == 1"], ["Completed", "eval:self.per_billed == 100 and self.docstatus == 1"],
["Cancelled", "eval:self.docstatus==2"], ["Cancelled", "eval:self.docstatus==2"],
["Closed", "eval:self.status=='Closed'"], ["Closed", "eval:self.status=='Closed'"],
@ -232,7 +234,7 @@ class StatusUpdater(Document):
self._update_children(args, update_modified) self._update_children(args, update_modified)
if "percent_join_field" in args: if "percent_join_field" in args or "percent_join_field_parent" in args:
self._update_percent_field_in_targets(args, update_modified) self._update_percent_field_in_targets(args, update_modified)
def _update_children(self, args, update_modified): def _update_children(self, args, update_modified):
@ -252,33 +254,43 @@ class StatusUpdater(Document):
if not args.get("second_source_extra_cond"): if not args.get("second_source_extra_cond"):
args["second_source_extra_cond"] = "" args["second_source_extra_cond"] = ""
args['second_source_condition'] = """ + ifnull((select sum(%(second_source_field)s) args['second_source_condition'] = frappe.db.sql(""" select ifnull((select sum(%(second_source_field)s)
from `tab%(second_source_dt)s` from `tab%(second_source_dt)s`
where `%(second_join_field)s`="%(detail_id)s" where `%(second_join_field)s`="%(detail_id)s"
and (`tab%(second_source_dt)s`.docstatus=1) %(second_source_extra_cond)s FOR UPDATE), 0)""" % args and (`tab%(second_source_dt)s`.docstatus=1)
%(second_source_extra_cond)s), 0) """ % args)[0][0]
if args['detail_id']: if args['detail_id']:
if not args.get("extra_cond"): args["extra_cond"] = "" if not args.get("extra_cond"): args["extra_cond"] = ""
frappe.db.sql("""update `tab%(target_dt)s` args["source_dt_value"] = frappe.db.sql("""
set %(target_field)s = (
(select ifnull(sum(%(source_field)s), 0) (select ifnull(sum(%(source_field)s), 0)
from `tab%(source_dt)s` where `%(join_field)s`="%(detail_id)s" from `tab%(source_dt)s` where `%(join_field)s`="%(detail_id)s"
and (docstatus=1 %(cond)s) %(extra_cond)s) and (docstatus=1 %(cond)s) %(extra_cond)s)
%(second_source_condition)s """ % args)[0][0] or 0.0
)
%(update_modified)s if args['second_source_condition']:
args["source_dt_value"] += flt(args['second_source_condition'])
frappe.db.sql("""update `tab%(target_dt)s`
set %(target_field)s = %(source_dt_value)s %(update_modified)s
where name='%(detail_id)s'""" % args) where name='%(detail_id)s'""" % args)
def _update_percent_field_in_targets(self, args, update_modified=True): def _update_percent_field_in_targets(self, args, update_modified=True):
"""Update percent field in parent transaction""" """Update percent field in parent transaction"""
distinct_transactions = set([d.get(args['percent_join_field']) if args.get('percent_join_field_parent'):
for d in self.get_all_children(args['source_dt'])]) # if reference to target doc where % is to be updated, is
# in source doc's parent form, consider percent_join_field_parent
args['name'] = self.get(args['percent_join_field_parent'])
self._update_percent_field(args, update_modified)
else:
distinct_transactions = set([d.get(args['percent_join_field'])
for d in self.get_all_children(args['source_dt'])])
for name in distinct_transactions: for name in distinct_transactions:
if name: if name:
args['name'] = name args['name'] = name
self._update_percent_field(args, update_modified) self._update_percent_field(args, update_modified)
def _update_percent_field(self, args, update_modified=True): def _update_percent_field(self, args, update_modified=True):
"""Update percent field in parent transaction""" """Update percent field in parent transaction"""

View File

@ -77,7 +77,7 @@ class StockController(AccountsController):
if sle_list: if sle_list:
for sle in sle_list: for sle in sle_list:
if warehouse_account.get(sle.warehouse): if warehouse_account.get(sle.warehouse):
# from warehouse account/ target warehouse account # from warehouse account
self.check_expense_account(item_row) self.check_expense_account(item_row)
@ -92,9 +92,16 @@ class StockController(AccountsController):
sle = self.update_stock_ledger_entries(sle) sle = self.update_stock_ledger_entries(sle)
# expense account/ target_warehouse / source_warehouse
if item_row.get('target_warehouse'):
warehouse = item_row.get('target_warehouse')
expense_account = warehouse_account[warehouse]["account"]
else:
expense_account = item_row.expense_account
gl_list.append(self.get_gl_dict({ gl_list.append(self.get_gl_dict({
"account": warehouse_account[sle.warehouse]["account"], "account": warehouse_account[sle.warehouse]["account"],
"against": item_row.expense_account, "against": expense_account,
"cost_center": item_row.cost_center, "cost_center": item_row.cost_center,
"project": item_row.project or self.get('project'), "project": item_row.project or self.get('project'),
"remarks": self.get("remarks") or "Accounting Entry for Stock", "remarks": self.get("remarks") or "Accounting Entry for Stock",
@ -102,9 +109,8 @@ class StockController(AccountsController):
"is_opening": item_row.get("is_opening") or self.get("is_opening") or "No", "is_opening": item_row.get("is_opening") or self.get("is_opening") or "No",
}, warehouse_account[sle.warehouse]["account_currency"], item=item_row)) }, warehouse_account[sle.warehouse]["account_currency"], item=item_row))
# expense account
gl_list.append(self.get_gl_dict({ gl_list.append(self.get_gl_dict({
"account": item_row.expense_account, "account": expense_account,
"against": warehouse_account[sle.warehouse]["account"], "against": warehouse_account[sle.warehouse]["account"],
"cost_center": item_row.cost_center, "cost_center": item_row.cost_center,
"project": item_row.project or self.get('project'), "project": item_row.project or self.get('project'),
@ -340,11 +346,15 @@ class StockController(AccountsController):
validate_warehouse_company(w, self.company) validate_warehouse_company(w, self.company)
def update_billing_percentage(self, update_modified=True): def update_billing_percentage(self, update_modified=True):
target_ref_field = "amount"
if self.doctype == "Delivery Note":
target_ref_field = "amount - (returned_qty * rate)"
self._update_percent_field({ self._update_percent_field({
"target_dt": self.doctype + " Item", "target_dt": self.doctype + " Item",
"target_parent_dt": self.doctype, "target_parent_dt": self.doctype,
"target_parent_field": "per_billed", "target_parent_field": "per_billed",
"target_ref_field": "amount", "target_ref_field": target_ref_field,
"target_field": "billed_amt", "target_field": "billed_amt",
"name": self.name, "name": self.name,
}, update_modified) }, update_modified)

View File

@ -519,6 +519,17 @@ class calculate_taxes_and_totals(object):
if self.doc.docstatus == 0: if self.doc.docstatus == 0:
self.calculate_outstanding_amount() self.calculate_outstanding_amount()
def is_internal_invoice(self):
"""
Checks if its an internal transfer invoice
and decides if to calculate any out standing amount or not
"""
if self.doc.doctype in ('Sales Invoice', 'Purchase Invoice') and self.doc.is_internal_transfer():
return True
return False
def calculate_outstanding_amount(self): def calculate_outstanding_amount(self):
# NOTE: # NOTE:
# write_off_amount is only for POS Invoice # write_off_amount is only for POS Invoice
@ -526,7 +537,8 @@ class calculate_taxes_and_totals(object):
if self.doc.doctype == "Sales Invoice": if self.doc.doctype == "Sales Invoice":
self.calculate_paid_amount() self.calculate_paid_amount()
if self.doc.is_return and self.doc.return_against and not self.doc.get('is_pos'): return if self.doc.is_return and self.doc.return_against and not self.doc.get('is_pos') or \
self.is_internal_invoice(): return
self.doc.round_floats_in(self.doc, ["grand_total", "total_advance", "write_off_amount"]) self.doc.round_floats_in(self.doc, ["grand_total", "total_advance", "write_off_amount"])
self._set_in_company_currency(self.doc, ['write_off_amount']) self._set_in_company_currency(self.doc, ['write_off_amount'])
@ -641,7 +653,8 @@ class calculate_taxes_and_totals(object):
if default_mode_of_payment: if default_mode_of_payment:
self.doc.append('payments', { self.doc.append('payments', {
'mode_of_payment': default_mode_of_payment.mode_of_payment, 'mode_of_payment': default_mode_of_payment.mode_of_payment,
'amount': total_amount_to_pay 'amount': total_amount_to_pay,
'default': 1
}) })
else: else:
self.doc.is_pos = 0 self.doc.is_pos = 0

View File

@ -126,7 +126,7 @@ class Appointment(Document):
add_assignemnt({ add_assignemnt({
'doctype': self.doctype, 'doctype': self.doctype,
'name': self.name, 'name': self.name,
'assign_to': existing_assignee 'assign_to': [existing_assignee]
}) })
return return
if self._assign: if self._assign:
@ -139,7 +139,7 @@ class Appointment(Document):
add_assignemnt({ add_assignemnt({
'doctype': self.doctype, 'doctype': self.doctype,
'name': self.name, 'name': self.name,
'assign_to': agent 'assign_to': [agent]
}) })
break break

View File

@ -1,23 +1,31 @@
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors // Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt // For license information, please see license.txt
cur_frm.add_fetch("contract_template", "contract_terms", "contract_terms");
cur_frm.add_fetch("contract_template", "requires_fulfilment", "requires_fulfilment");
// Add fulfilment terms from contract template into contract
frappe.ui.form.on("Contract", { frappe.ui.form.on("Contract", {
contract_template: function (frm) { contract_template: function (frm) {
// Populate the fulfilment terms table from a contract template, if any
if (frm.doc.contract_template) { if (frm.doc.contract_template) {
frappe.model.with_doc("Contract Template", frm.doc.contract_template, function () { frappe.call({
var tabletransfer = frappe.model.get_doc("Contract Template", frm.doc.contract_template); method: 'erpnext.crm.doctype.contract_template.contract_template.get_contract_template',
args: {
frm.doc.fulfilment_terms = []; template_name: frm.doc.contract_template,
$.each(tabletransfer.fulfilment_terms, function (index, row) { doc: frm.doc
var d = frm.add_child("fulfilment_terms"); },
d.requirement = row.requirement; callback: function(r) {
frm.refresh_field("fulfilment_terms"); if (r && r.message) {
}); let contract_template = r.message.contract_template;
frm.set_value("contract_terms", r.message.contract_terms);
frm.set_value("requires_fulfilment", contract_template.requires_fulfilment);
if (frm.doc.requires_fulfilment) {
// Populate the fulfilment terms table from a contract template, if any
r.message.contract_template.fulfilment_terms.forEach(element => {
let d = frm.add_child("fulfilment_terms");
d.requirement = element.requirement;
});
frm.refresh_field("fulfilment_terms");
}
}
}
}); });
} }
} }

View File

@ -1,4 +1,5 @@
{ {
"actions": [],
"allow_import": 1, "allow_import": 1,
"allow_rename": 1, "allow_rename": 1,
"creation": "2018-04-12 06:32:04.582486", "creation": "2018-04-12 06:32:04.582486",
@ -247,7 +248,7 @@
], ],
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-03-30 06:56:07.257932", "modified": "2020-12-07 11:15:58.385521",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "CRM", "module": "CRM",
"name": "Contract", "name": "Contract",

View File

@ -11,7 +11,9 @@
"contract_terms", "contract_terms",
"sb_fulfilment", "sb_fulfilment",
"requires_fulfilment", "requires_fulfilment",
"fulfilment_terms" "fulfilment_terms",
"section_break_6",
"contract_template_help"
], ],
"fields": [ "fields": [
{ {
@ -41,10 +43,20 @@
"fieldtype": "Table", "fieldtype": "Table",
"label": "Fulfilment Terms and Conditions", "label": "Fulfilment Terms and Conditions",
"options": "Contract Template Fulfilment Terms" "options": "Contract Template Fulfilment Terms"
},
{
"fieldname": "section_break_6",
"fieldtype": "Section Break"
},
{
"fieldname": "contract_template_help",
"fieldtype": "HTML",
"label": "Contract Template Help",
"options": "<h4>Contract Template Example</h4>\n\n<pre>Contract for Customer {{ party_name }}\n\n-Valid From : {{ start_date }} \n-Valid To : {{ end_date }}\n</pre>\n\n<h4>How to get fieldnames</h4>\n\n<p>The field names you can use in your Contract Template are the fields in the Contract for which you are creating the template. You can find out the fields of any documents via Setup &gt; Customize Form View and selecting the document type (e.g. Contract)</p>\n\n<h4>Templating</h4>\n\n<p>Templates are compiled using the Jinja Templating Language. To learn more about Jinja, <a class=\"strong\" href=\"http://jinja.pocoo.org/docs/dev/templates/\">read this documentation.</a></p>"
} }
], ],
"links": [], "links": [],
"modified": "2020-11-11 17:49:44.879363", "modified": "2020-12-07 10:44:22.587047",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "CRM", "module": "CRM",
"name": "Contract Template", "name": "Contract Template",

View File

@ -5,6 +5,27 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe.model.document import Document from frappe.model.document import Document
from frappe.utils.jinja import validate_template
from six import string_types
import json
class ContractTemplate(Document): class ContractTemplate(Document):
pass def validate(self):
if self.contract_terms:
validate_template(self.contract_terms)
@frappe.whitelist()
def get_contract_template(template_name, doc):
if isinstance(doc, string_types):
doc = json.loads(doc)
contract_template = frappe.get_doc("Contract Template", template_name)
contract_terms = None
if contract_template.contract_terms:
contract_terms = frappe.render_template(contract_template.contract_terms, doc)
return {
'contract_template': contract_template,
'contract_terms': contract_terms
}

View File

@ -260,6 +260,15 @@ def update_taxes_with_shipping_lines(taxes, shipping_lines, shopify_settings):
"""Shipping lines represents the shipping details, """Shipping lines represents the shipping details,
each such shipping detail consists of a list of tax_lines""" each such shipping detail consists of a list of tax_lines"""
for shipping_charge in shipping_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"): for tax in shipping_charge.get("tax_lines"):
taxes.append({ taxes.append({
"charge_type": _("Actual"), "charge_type": _("Actual"),

View File

@ -30,6 +30,11 @@
"label": "Laboratory", "label": "Laboratory",
"links": "[\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Lab Test\",\n\t\t\"label\": \"Lab Test\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Sample Collection\",\n\t\t\"label\": \"Sample Collection\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Dosage Form\",\n\t\t\"label\": \"Dosage Form\"\n\t}\n]" "links": "[\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Lab Test\",\n\t\t\"label\": \"Lab Test\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Sample Collection\",\n\t\t\"label\": \"Sample Collection\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Dosage Form\",\n\t\t\"label\": \"Dosage Form\"\n\t}\n]"
}, },
{
"hidden": 0,
"label": "Inpatient",
"links": "[\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Inpatient Record\",\n\t\t\"label\": \"Inpatient Record\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Inpatient Medication Order\",\n\t\t\"label\": \"Inpatient Medication Order\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Inpatient Medication Entry\",\n\t\t\"label\": \"Inpatient Medication Entry\"\n\t}\n]"
},
{ {
"hidden": 0, "hidden": 0,
"label": "Rehabilitation and Physiotherapy", "label": "Rehabilitation and Physiotherapy",
@ -38,7 +43,7 @@
{ {
"hidden": 0, "hidden": 0,
"label": "Records and History", "label": "Records and History",
"links": "[\n\t{\n\t\t\"type\": \"page\",\n\t\t\"name\": \"patient_history\",\n\t\t\"label\": \"Patient History\"\n\t},\n\t{\n\t\t\"type\": \"page\",\n\t\t\"name\": \"patient-progress\",\n\t\t\"label\": \"Patient Progress\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Patient Medical Record\",\n\t\t\"label\": \"Patient Medical Record\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Inpatient Record\",\n\t\t\"label\": \"Inpatient Record\"\n\t}\n]" "links": "[\n\t{\n\t\t\"type\": \"page\",\n\t\t\"name\": \"patient_history\",\n\t\t\"label\": \"Patient History\"\n\t},\n\t{\n\t\t\"type\": \"page\",\n\t\t\"name\": \"patient-progress\",\n\t\t\"label\": \"Patient Progress\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Patient Medical Record\",\n\t\t\"label\": \"Patient Medical Record\"\n\t}\n]"
}, },
{ {
"hidden": 0, "hidden": 0,
@ -64,7 +69,7 @@
"idx": 0, "idx": 0,
"is_standard": 1, "is_standard": 1,
"label": "Healthcare", "label": "Healthcare",
"modified": "2020-11-23 23:00:48.764377", "modified": "2020-11-26 22:09:09.164584",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Healthcare", "module": "Healthcare",
"name": "Healthcare", "name": "Healthcare",

View File

@ -7,6 +7,7 @@ import frappe
import unittest import unittest
from frappe.utils import nowdate, add_days from frappe.utils import nowdate, add_days
from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import create_healthcare_docs, create_appointment, create_healthcare_service_items from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import create_healthcare_docs, create_appointment, create_healthcare_service_items
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
test_dependencies = ["Company"] test_dependencies = ["Company"]
@ -15,6 +16,7 @@ class TestFeeValidity(unittest.TestCase):
frappe.db.sql("""delete from `tabPatient Appointment`""") frappe.db.sql("""delete from `tabPatient Appointment`""")
frappe.db.sql("""delete from `tabFee Validity`""") frappe.db.sql("""delete from `tabFee Validity`""")
frappe.db.sql("""delete from `tabPatient`""") frappe.db.sql("""delete from `tabPatient`""")
make_pos_profile()
def test_fee_validity(self): def test_fee_validity(self):
item = create_healthcare_service_items() item = create_healthcare_service_items()

View File

@ -29,6 +29,29 @@ frappe.ui.form.on('Inpatient Medication Entry', {
} }
}; };
}); });
if (frm.doc.__islocal || frm.doc.docstatus !== 0 || !frm.doc.update_stock)
return;
frm.add_custom_button(__('Make Stock Entry'), function() {
frappe.call({
method: 'erpnext.healthcare.doctype.inpatient_medication_entry.inpatient_medication_entry.make_difference_stock_entry',
args: { docname: frm.doc.name },
freeze: true,
callback: function(r) {
if (r.message) {
var doclist = frappe.model.sync(r.message);
frappe.set_route('Form', doclist[0].doctype, doclist[0].name);
} else {
frappe.msgprint({
title: __('No Drug Shortage'),
message: __('All the drugs are available with sufficient qty to process this Inpatient Medication Entry.'),
indicator: 'green'
});
}
}
});
});
}, },
patient: function(frm) { patient: function(frm) {

View File

@ -142,25 +142,32 @@ class InpatientMedicationEntry(Document):
return orders, order_entry_map return orders, order_entry_map
def check_stock_qty(self): def check_stock_qty(self):
from erpnext.stock.stock_ledger import NegativeStockError drug_shortage = get_drug_shortage_map(self.medication_orders, self.warehouse)
drug_availability = dict() if drug_shortage:
for d in self.medication_orders: message = _('Quantity not available for the following items in warehouse {0}. ').format(frappe.bold(self.warehouse))
if not drug_availability.get(d.drug_code): message += _('Please enable Allow Negative Stock in Stock Settings or create Stock Entry to proceed.')
drug_availability[d.drug_code] = 0
drug_availability[d.drug_code] += flt(d.dosage)
for drug, dosage in drug_availability.items(): formatted_item_rows = ''
available_qty = get_latest_stock_qty(drug, self.warehouse)
# validate qty for drug, shortage_qty in drug_shortage.items():
if flt(available_qty) < flt(dosage): item_link = get_link_to_form('Item', drug)
frappe.throw(_('Quantity not available for {0} in warehouse {1}').format( formatted_item_rows += """
frappe.bold(drug), frappe.bold(self.warehouse)) <td>{0}</td>
+ '<br><br>' + _('Available quantity is {0}, you need {1}').format( <td>{1}</td>
frappe.bold(available_qty), frappe.bold(dosage)) </tr>""".format(item_link, frappe.bold(shortage_qty))
+ '<br><br>' + _('Please enable Allow Negative Stock in Stock Settings or create Stock Entry to proceed.'),
NegativeStockError, title=_('Insufficient Stock')) message += """
<table class='table'>
<thead>
<th>{0}</th>
<th>{1}</th>
</thead>
{2}
</table>
""".format(_('Drug Code'), _('Shortage Qty'), formatted_item_rows)
frappe.throw(message, title=_('Insufficient Stock'), is_minimizable=True, wide=True)
def make_stock_entry(self): def make_stock_entry(self):
stock_entry = frappe.new_doc('Stock Entry') stock_entry = frappe.new_doc('Stock Entry')
@ -223,7 +230,8 @@ def get_pending_medication_orders(entry):
for doc in data: for doc in data:
inpatient_record = doc.inpatient_record inpatient_record = doc.inpatient_record
doc['service_unit'] = get_current_healthcare_service_unit(inpatient_record) if inpatient_record:
doc['service_unit'] = get_current_healthcare_service_unit(inpatient_record)
if entry.service_unit and doc.service_unit != entry.service_unit: if entry.service_unit and doc.service_unit != entry.service_unit:
to_remove.append(doc) to_remove.append(doc)
@ -276,4 +284,55 @@ def get_current_healthcare_service_unit(inpatient_record):
ip_record = frappe.get_doc('Inpatient Record', inpatient_record) ip_record = frappe.get_doc('Inpatient Record', inpatient_record)
if ip_record.inpatient_occupancies: if ip_record.inpatient_occupancies:
return ip_record.inpatient_occupancies[-1].service_unit return ip_record.inpatient_occupancies[-1].service_unit
return return
def get_drug_shortage_map(medication_orders, warehouse):
"""
Returns a dict like { drug_code: shortage_qty }
"""
drug_requirement = dict()
for d in medication_orders:
if not drug_requirement.get(d.drug_code):
drug_requirement[d.drug_code] = 0
drug_requirement[d.drug_code] += flt(d.dosage)
drug_shortage = dict()
for drug, required_qty in drug_requirement.items():
available_qty = get_latest_stock_qty(drug, warehouse)
if flt(required_qty) > flt(available_qty):
drug_shortage[drug] = flt(flt(required_qty) - flt(available_qty))
return drug_shortage
@frappe.whitelist()
def make_difference_stock_entry(docname):
doc = frappe.get_doc('Inpatient Medication Entry', docname)
drug_shortage = get_drug_shortage_map(doc.medication_orders, doc.warehouse)
if not drug_shortage:
return None
stock_entry = frappe.new_doc('Stock Entry')
stock_entry.purpose = 'Material Transfer'
stock_entry.set_stock_entry_type()
stock_entry.to_warehouse = doc.warehouse
stock_entry.company = doc.company
cost_center = frappe.get_cached_value('Company', doc.company, 'cost_center')
expense_account = get_account(None, 'expense_account', 'Healthcare Settings', doc.company)
for drug, shortage_qty in drug_shortage.items():
se_child = stock_entry.append('items')
se_child.item_code = drug
se_child.item_name = frappe.db.get_value('Item', drug, 'stock_uom')
se_child.uom = frappe.db.get_value('Item', drug, 'stock_uom')
se_child.stock_uom = se_child.uom
se_child.qty = flt(shortage_qty)
se_child.t_warehouse = doc.warehouse
# in stock uom
se_child.conversion_factor = 1
se_child.cost_center = cost_center
se_child.expense_account = expense_account
return stock_entry

View File

@ -9,6 +9,7 @@ from frappe.utils import add_days, getdate, now_datetime
from erpnext.healthcare.doctype.inpatient_record.test_inpatient_record import create_patient, create_inpatient, get_healthcare_service_unit, mark_invoiced_inpatient_occupancy from erpnext.healthcare.doctype.inpatient_record.test_inpatient_record import create_patient, create_inpatient, get_healthcare_service_unit, mark_invoiced_inpatient_occupancy
from erpnext.healthcare.doctype.inpatient_record.inpatient_record import admit_patient, discharge_patient, schedule_discharge from erpnext.healthcare.doctype.inpatient_record.inpatient_record import admit_patient, discharge_patient, schedule_discharge
from erpnext.healthcare.doctype.inpatient_medication_order.test_inpatient_medication_order import create_ipmo, create_ipme from erpnext.healthcare.doctype.inpatient_medication_order.test_inpatient_medication_order import create_ipmo, create_ipme
from erpnext.healthcare.doctype.inpatient_medication_entry.inpatient_medication_entry import get_drug_shortage_map, make_difference_stock_entry
from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_account from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_account
class TestInpatientMedicationEntry(unittest.TestCase): class TestInpatientMedicationEntry(unittest.TestCase):
@ -82,6 +83,39 @@ class TestInpatientMedicationEntry(unittest.TestCase):
self.assertEqual(stock_entry.items[0].patient, self.patient) self.assertEqual(stock_entry.items[0].patient, self.patient)
self.assertEqual(stock_entry.items[0].inpatient_medication_entry_child, ipme.medication_orders[0].name) self.assertEqual(stock_entry.items[0].inpatient_medication_entry_child, ipme.medication_orders[0].name)
def test_drug_shortage_stock_entry(self):
ipmo = create_ipmo(self.patient)
ipmo.submit()
ipmo.reload()
date = add_days(getdate(), -1)
filters = frappe._dict(
from_date=date,
to_date=date,
from_time='',
to_time='',
item_code='Dextromethorphan',
patient=self.patient
)
# check drug shortage
ipme = create_ipme(filters, update_stock=1)
ipme.warehouse = 'Finished Goods - _TC'
ipme.save()
drug_shortage = get_drug_shortage_map(ipme.medication_orders, ipme.warehouse)
self.assertEqual(drug_shortage.get('Dextromethorphan'), 3)
# check material transfer for drug shortage
make_stock_entry()
stock_entry = make_difference_stock_entry(ipme.name)
self.assertEqual(stock_entry.items[0].item_code, 'Dextromethorphan')
self.assertEqual(stock_entry.items[0].qty, 3)
stock_entry.from_warehouse = 'Stores - _TC'
stock_entry.submit()
ipme.reload()
ipme.submit()
def tearDown(self): def tearDown(self):
# cleanup - Discharge # cleanup - Discharge
schedule_discharge(frappe.as_json({'patient': self.patient})) schedule_discharge(frappe.as_json({'patient': self.patient}))
@ -94,15 +128,12 @@ class TestInpatientMedicationEntry(unittest.TestCase):
for entry in frappe.get_all('Inpatient Medication Entry'): for entry in frappe.get_all('Inpatient Medication Entry'):
doc = frappe.get_doc('Inpatient Medication Entry', entry.name) doc = frappe.get_doc('Inpatient Medication Entry', entry.name)
doc.cancel() doc.cancel()
frappe.db.delete('Stock Entry', {'inpatient_medication_entry': doc.name})
doc.delete()
for entry in frappe.get_all('Inpatient Medication Order'): for entry in frappe.get_all('Inpatient Medication Order'):
doc = frappe.get_doc('Inpatient Medication Order', entry.name) doc = frappe.get_doc('Inpatient Medication Order', entry.name)
doc.cancel() doc.cancel()
doc.delete()
def make_stock_entry(): def make_stock_entry(warehouse=None):
frappe.db.set_value('Company', '_Test Company', { frappe.db.set_value('Company', '_Test Company', {
'stock_adjustment_account': 'Stock Adjustment - _TC', 'stock_adjustment_account': 'Stock Adjustment - _TC',
'default_inventory_account': 'Stock In Hand - _TC' 'default_inventory_account': 'Stock In Hand - _TC'
@ -110,7 +141,7 @@ def make_stock_entry():
stock_entry = frappe.new_doc('Stock Entry') stock_entry = frappe.new_doc('Stock Entry')
stock_entry.stock_entry_type = 'Material Receipt' stock_entry.stock_entry_type = 'Material Receipt'
stock_entry.company = '_Test Company' stock_entry.company = '_Test Company'
stock_entry.to_warehouse = 'Stores - _TC' stock_entry.to_warehouse = warehouse or 'Stores - _TC'
expense_account = get_account(None, 'expense_account', 'Healthcare Settings', '_Test Company') expense_account = get_account(None, 'expense_account', 'Healthcare Settings', '_Test Company')
se_child = stock_entry.append('items') se_child = stock_entry.append('items')
se_child.item_code = 'Dextromethorphan' se_child.item_code = 'Dextromethorphan'

View File

@ -18,6 +18,10 @@ def get_data():
{ {
'label': _('Billing'), 'label': _('Billing'),
'items': ['Sales Invoice'] 'items': ['Sales Invoice']
},
{
'label': _('Orders'),
'items': ['Inpatient Medication Order']
} }
] ]
} }

View File

@ -7,12 +7,14 @@ import frappe
from erpnext.healthcare.doctype.patient_appointment.patient_appointment import update_status, make_encounter from erpnext.healthcare.doctype.patient_appointment.patient_appointment import update_status, make_encounter
from frappe.utils import nowdate, add_days from frappe.utils import nowdate, add_days
from frappe.utils.make_random import get_random from frappe.utils.make_random import get_random
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
class TestPatientAppointment(unittest.TestCase): class TestPatientAppointment(unittest.TestCase):
def setUp(self): def setUp(self):
frappe.db.sql("""delete from `tabPatient Appointment`""") frappe.db.sql("""delete from `tabPatient Appointment`""")
frappe.db.sql("""delete from `tabFee Validity`""") frappe.db.sql("""delete from `tabFee Validity`""")
frappe.db.sql("""delete from `tabPatient Encounter`""") frappe.db.sql("""delete from `tabPatient Encounter`""")
make_pos_profile()
def test_status(self): def test_status(self):
patient, medical_department, practitioner = create_healthcare_docs() patient, medical_department, practitioner = create_healthcare_docs()

View File

@ -6,11 +6,13 @@ import unittest
import frappe import frappe
from frappe.utils import nowdate from frappe.utils import nowdate
from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import create_encounter, create_healthcare_docs, create_appointment from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import create_encounter, create_healthcare_docs, create_appointment
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
class TestPatientMedicalRecord(unittest.TestCase): class TestPatientMedicalRecord(unittest.TestCase):
def setUp(self): def setUp(self):
frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 0) frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 0)
frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 1) frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 1)
make_pos_profile()
def test_medical_record(self): def test_medical_record(self):
patient, medical_department, practitioner = create_healthcare_docs() patient, medical_department, practitioner = create_healthcare_docs()

View File

@ -441,42 +441,43 @@ global_search_doctypes = {
{"doctype": "Sales Order", "index": 8}, {"doctype": "Sales Order", "index": 8},
{"doctype": "Quotation", "index": 9}, {"doctype": "Quotation", "index": 9},
{"doctype": "Work Order", "index": 10}, {"doctype": "Work Order", "index": 10},
{"doctype": "Purchase Receipt", "index": 11}, {"doctype": "Purchase Order", "index": 11},
{"doctype": "Purchase Invoice", "index": 12}, {"doctype": "Purchase Receipt", "index": 12},
{"doctype": "Delivery Note", "index": 13}, {"doctype": "Purchase Invoice", "index": 13},
{"doctype": "Stock Entry", "index": 14}, {"doctype": "Delivery Note", "index": 14},
{"doctype": "Material Request", "index": 15}, {"doctype": "Stock Entry", "index": 15},
{"doctype": "Delivery Trip", "index": 16}, {"doctype": "Material Request", "index": 16},
{"doctype": "Pick List", "index": 17}, {"doctype": "Delivery Trip", "index": 17},
{"doctype": "Salary Slip", "index": 18}, {"doctype": "Pick List", "index": 18},
{"doctype": "Leave Application", "index": 19}, {"doctype": "Salary Slip", "index": 19},
{"doctype": "Expense Claim", "index": 20}, {"doctype": "Leave Application", "index": 20},
{"doctype": "Payment Entry", "index": 21}, {"doctype": "Expense Claim", "index": 21},
{"doctype": "Lead", "index": 22}, {"doctype": "Payment Entry", "index": 22},
{"doctype": "Opportunity", "index": 23}, {"doctype": "Lead", "index": 23},
{"doctype": "Item Price", "index": 24}, {"doctype": "Opportunity", "index": 24},
{"doctype": "Purchase Taxes and Charges Template", "index": 25}, {"doctype": "Item Price", "index": 25},
{"doctype": "Sales Taxes and Charges", "index": 26}, {"doctype": "Purchase Taxes and Charges Template", "index": 26},
{"doctype": "Asset", "index": 27}, {"doctype": "Sales Taxes and Charges", "index": 27},
{"doctype": "Project", "index": 28}, {"doctype": "Asset", "index": 28},
{"doctype": "Task", "index": 29}, {"doctype": "Project", "index": 29},
{"doctype": "Timesheet", "index": 30}, {"doctype": "Task", "index": 30},
{"doctype": "Issue", "index": 31}, {"doctype": "Timesheet", "index": 31},
{"doctype": "Serial No", "index": 32}, {"doctype": "Issue", "index": 32},
{"doctype": "Batch", "index": 33}, {"doctype": "Serial No", "index": 33},
{"doctype": "Branch", "index": 34}, {"doctype": "Batch", "index": 34},
{"doctype": "Department", "index": 35}, {"doctype": "Branch", "index": 35},
{"doctype": "Employee Grade", "index": 36}, {"doctype": "Department", "index": 36},
{"doctype": "Designation", "index": 37}, {"doctype": "Employee Grade", "index": 37},
{"doctype": "Job Opening", "index": 38}, {"doctype": "Designation", "index": 38},
{"doctype": "Job Applicant", "index": 39}, {"doctype": "Job Opening", "index": 39},
{"doctype": "Job Offer", "index": 40}, {"doctype": "Job Applicant", "index": 40},
{"doctype": "Salary Structure Assignment", "index": 41}, {"doctype": "Job Offer", "index": 41},
{"doctype": "Appraisal", "index": 42}, {"doctype": "Salary Structure Assignment", "index": 42},
{"doctype": "Loan", "index": 43}, {"doctype": "Appraisal", "index": 43},
{"doctype": "Maintenance Schedule", "index": 44}, {"doctype": "Loan", "index": 44},
{"doctype": "Maintenance Visit", "index": 45}, {"doctype": "Maintenance Schedule", "index": 45},
{"doctype": "Warranty Claim", "index": 46}, {"doctype": "Maintenance Visit", "index": 46},
{"doctype": "Warranty Claim", "index": 47},
], ],
"Healthcare": [ "Healthcare": [
{'doctype': 'Patient', 'index': 1}, {'doctype': 'Patient', 'index': 1},

View File

@ -18,13 +18,18 @@ frappe.ui.form.on('Employee Advance', {
if (!frm.doc.employee) { if (!frm.doc.employee) {
frappe.msgprint(__("Please select employee first")); frappe.msgprint(__("Please select employee first"));
} }
var company_currency = erpnext.get_currency(frm.doc.company); let company_currency = erpnext.get_currency(frm.doc.company);
let currencies = [company_currency];
if (frm.doc.currency && (frm.doc.currency != company_currency)) {
currencies.push(frm.doc.currency);
}
return { return {
filters: { filters: {
"root_type": "Asset", "root_type": "Asset",
"is_group": 0, "is_group": 0,
"company": frm.doc.company, "company": frm.doc.company,
"account_currency": ["in", [frm.doc.currency, company_currency]], "account_currency": ["in", currencies],
} }
}; };
}); });
@ -181,21 +186,23 @@ frappe.ui.form.on('Employee Advance', {
}, },
currency: function(frm) { currency: function(frm) {
var from_currency = frm.doc.currency; if (frm.doc.currency) {
var company_currency; var from_currency = frm.doc.currency;
if (!frm.doc.company) { var company_currency;
company_currency = erpnext.get_currency(frappe.defaults.get_default("Company")); if (!frm.doc.company) {
} else { company_currency = erpnext.get_currency(frappe.defaults.get_default("Company"));
company_currency = erpnext.get_currency(frm.doc.company); } else {
company_currency = erpnext.get_currency(frm.doc.company);
}
if (from_currency != company_currency) {
frm.events.set_exchange_rate(frm, from_currency, company_currency);
} else {
frm.set_value("exchange_rate", 1.0);
frm.set_df_property('exchange_rate', 'hidden', 1);
frm.set_df_property("exchange_rate", "description", "" );
}
frm.refresh_fields();
} }
if (from_currency != company_currency) {
frm.events.set_exchange_rate(frm, from_currency, company_currency);
} else {
frm.set_value("exchange_rate", 1.0);
frm.set_df_property('exchange_rate', 'hidden', 1);
frm.set_df_property("exchange_rate", "description", "" );
}
frm.refresh_fields();
}, },
set_exchange_rate: function(frm, from_currency, company_currency) { set_exchange_rate: function(frm, from_currency, company_currency) {

View File

@ -8,7 +8,17 @@ frappe.views.calendar["Job Card"] = {
"allDay": "allDay", "allDay": "allDay",
"progress": "progress" "progress": "progress"
}, },
gantt: true, gantt: {
field_map: {
"start": "started_time",
"end": "started_time",
"id": "name",
"title": "subject",
"color": "color",
"allDay": "allDay",
"progress": "progress"
}
},
filters: [ filters: [
{ {
"fieldtype": "Link", "fieldtype": "Link",

View File

@ -491,6 +491,39 @@ class TestWorkOrder(unittest.TestCase):
work_order1.save() work_order1.save()
self.assertEqual(work_order1.operations[0].time_in_mins, 40.0) self.assertEqual(work_order1.operations[0].time_in_mins, 40.0)
def test_partial_material_consumption(self):
frappe.db.set_value("Manufacturing Settings", None, "material_consumption", 1)
wo_order = make_wo_order_test_record(planned_start_date=now(), qty=4)
ste_cancel_list = []
ste1 = test_stock_entry.make_stock_entry(item_code="_Test Item",
target="_Test Warehouse - _TC", qty=20, basic_rate=5000.0)
ste2 = test_stock_entry.make_stock_entry(item_code="_Test Item Home Desktop 100",
target="_Test Warehouse - _TC", qty=20, basic_rate=1000.0)
ste_cancel_list.extend([ste1, ste2])
s = frappe.get_doc(make_stock_entry(wo_order.name, "Material Transfer for Manufacture", 4))
s.submit()
ste_cancel_list.append(s)
ste1 = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 2))
ste1.submit()
ste_cancel_list.append(ste1)
print(wo_order.name)
ste3 = frappe.get_doc(make_stock_entry(wo_order.name, "Material Consumption for Manufacture", 2))
self.assertEquals(ste3.fg_completed_qty, 2)
expected_qty = {"_Test Item": 2, "_Test Item Home Desktop 100": 4}
for row in ste3.items:
self.assertEquals(row.qty, expected_qty.get(row.item_code))
for ste_doc in ste_cancel_list:
ste_doc.cancel()
frappe.db.set_value("Manufacturing Settings", None, "material_consumption", 0)
def get_scrap_item_details(bom_no): def get_scrap_item_details(bom_no):
scrap_items = {} scrap_items = {}
for item in frappe.db.sql("""select item_code, stock_qty from `tabBOM Scrap Item` for item in frappe.db.sql("""select item_code, stock_qty from `tabBOM Scrap Item`

View File

@ -545,7 +545,8 @@ erpnext.work_order = {
var tbl = frm.doc.required_items || []; var tbl = frm.doc.required_items || [];
var tbl_lenght = tbl.length; var tbl_lenght = tbl.length;
for (var i = 0, len = tbl_lenght; i < len; i++) { for (var i = 0, len = tbl_lenght; i < len; i++) {
if (flt(frm.doc.required_items[i].required_qty) > flt(frm.doc.required_items[i].consumed_qty)) { let wo_item_qty = frm.doc.required_items[i].transferred_qty || frm.doc.required_items[i].required_qty;
if (flt(wo_item_qty) > flt(frm.doc.required_items[i].consumed_qty)) {
counter += 1; counter += 1;
} }
} }

View File

@ -124,7 +124,7 @@ class ProductionPlanReport(object):
if self.filters.include_subassembly_raw_materials else "(bom_item.qty / bom.quantity)") if self.filters.include_subassembly_raw_materials else "(bom_item.qty / bom.quantity)")
raw_materials = frappe.db.sql(""" SELECT bom_item.parent, bom_item.item_code, raw_materials = frappe.db.sql(""" SELECT bom_item.parent, bom_item.item_code,
bom_item.item_name as raw_material_name, {0} as required_qty bom_item.item_name as raw_material_name, {0} as required_qty_per_unit
FROM FROM
`tabBOM` as bom, `tab{1}` as bom_item `tabBOM` as bom, `tab{1}` as bom_item
WHERE WHERE
@ -208,7 +208,7 @@ class ProductionPlanReport(object):
warehouses = self.mrp_warehouses or [] warehouses = self.mrp_warehouses or []
for d in self.raw_materials_dict.get(key): for d in self.raw_materials_dict.get(key):
if self.filters.based_on != "Work Order": if self.filters.based_on != "Work Order":
d.required_qty = d.required_qty * data.qty_to_manufacture d.required_qty = d.required_qty_per_unit * data.qty_to_manufacture
if not warehouses: if not warehouses:
warehouses = [data.warehouse] warehouses = [data.warehouse]

View File

@ -59,7 +59,7 @@ class Member(Document):
frappe.msgprint(_("A customer is already linked to this Member")) frappe.msgprint(_("A customer is already linked to this Member"))
cust = create_customer(frappe._dict({ cust = create_customer(frappe._dict({
'fullname': self.member_name, 'fullname': self.member_name,
'email': self.email_id or self.user, 'email': self.email_id or self.email,
'phone': None 'phone': None
})) }))
@ -177,4 +177,4 @@ def register_member(fullname, email, rzpay_plan_id, subscription_id, pan=None, m
mobile=mobile mobile=mobile
)) ))
return member.name return member.name

View File

@ -450,7 +450,6 @@ erpnext.patches.v8_9.set_member_party_type
erpnext.patches.v9_0.add_user_to_child_table_in_pos_profile erpnext.patches.v9_0.add_user_to_child_table_in_pos_profile
erpnext.patches.v9_0.set_schedule_date_for_material_request_and_purchase_order erpnext.patches.v9_0.set_schedule_date_for_material_request_and_purchase_order
erpnext.patches.v9_0.student_admission_childtable_migrate erpnext.patches.v9_0.student_admission_childtable_migrate
erpnext.patches.v9_0.fix_subscription_next_date #2017-10-23
erpnext.patches.v9_0.add_healthcare_domain erpnext.patches.v9_0.add_healthcare_domain
erpnext.patches.v9_0.set_variant_item_description erpnext.patches.v9_0.set_variant_item_description
erpnext.patches.v9_0.set_uoms_in_variant_field erpnext.patches.v9_0.set_uoms_in_variant_field
@ -691,6 +690,7 @@ erpnext.patches.v13_0.update_old_loans
erpnext.patches.v12_0.set_serial_no_status #2020-05-21 erpnext.patches.v12_0.set_serial_no_status #2020-05-21
erpnext.patches.v12_0.update_price_list_currency_in_bom erpnext.patches.v12_0.update_price_list_currency_in_bom
execute:frappe.reload_doctype('Dashboard') execute:frappe.reload_doctype('Dashboard')
execute:frappe.reload_doc('desk', 'doctype', 'number_card_link')
execute:frappe.delete_doc_if_exists('Dashboard', 'Accounts') execute:frappe.delete_doc_if_exists('Dashboard', 'Accounts')
erpnext.patches.v13_0.update_actual_start_and_end_date_in_wo erpnext.patches.v13_0.update_actual_start_and_end_date_in_wo
erpnext.patches.v13_0.set_company_field_in_healthcare_doctypes #2020-05-25 erpnext.patches.v13_0.set_company_field_in_healthcare_doctypes #2020-05-25
@ -738,3 +738,5 @@ erpnext.patches.v13_0.update_custom_fields_for_shopify
execute:frappe.delete_doc("Report", "Quoted Item Comparison") execute:frappe.delete_doc("Report", "Quoted Item Comparison")
erpnext.patches.v13_0.updates_for_multi_currency_payroll 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.create_leave_policy_assignment_based_on_employee_current_leave_policy
erpnext.patches.v13_0.add_po_to_global_search
erpnext.patches.v13_0.update_returned_qty_in_pr_dn

View File

@ -0,0 +1,17 @@
from __future__ import unicode_literals
import frappe
def execute():
global_search_settings = frappe.get_single("Global Search Settings")
if "Purchase Order" in (
dt.document_type for dt in global_search_settings.allowed_in_global_search
):
return
global_search_settings.append(
"allowed_in_global_search", {"document_type": "Purchase Order"}
)
global_search_settings.save(ignore_permissions=True)

View File

@ -52,6 +52,8 @@ def create_assignment(employee, leave_policy, leave_period=None, allocation_exis
if leave_period: if leave_period:
filters["leave_period"] = leave_period filters["leave_period"] = leave_period
frappe.reload_doc('hr', 'doctype', 'leave_policy_assignment')
if not frappe.db.exists("Leave Policy Assignment" , filters): if not frappe.db.exists("Leave Policy Assignment" , filters):
lpa = frappe.new_doc("Leave Policy Assignment") lpa = frappe.new_doc("Leave Policy Assignment")
lpa.employee = employee lpa.employee = employee

View File

@ -29,7 +29,7 @@ def execute():
'response_by_variance': response_by_variance, 'response_by_variance': response_by_variance,
'resolution_by_variance': resolution_by_variance, 'resolution_by_variance': resolution_by_variance,
'first_response_time': mins_to_first_response 'first_response_time': mins_to_first_response
}) }, update_modified=False)
# commit after every 100 updates # commit after every 100 updates
count += 1 count += 1
if count%100 == 0: if count%100 == 0:
@ -44,7 +44,7 @@ def execute():
count = 0 count = 0
for entry in opportunities: for entry in opportunities:
mins_to_first_response = convert_to_seconds(entry.mins_to_first_response, 'Minutes') mins_to_first_response = convert_to_seconds(entry.mins_to_first_response, 'Minutes')
frappe.db.set_value('Opportunity', entry.name, 'first_response_time', mins_to_first_response) frappe.db.set_value('Opportunity', entry.name, 'first_response_time', mins_to_first_response, update_modified=False)
# commit after every 100 updates # commit after every 100 updates
count += 1 count += 1
if count%100 == 0: if count%100 == 0:

View File

@ -5,6 +5,8 @@ from frappe.utils import nowdate
from erpnext.accounts.doctype.account.test_account import create_account from erpnext.accounts.doctype.account.test_account import create_account
from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import process_loan_interest_accrual_for_term_loans from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import process_loan_interest_accrual_for_term_loans
from erpnext.loan_management.doctype.loan.loan import make_repayment_entry from erpnext.loan_management.doctype.loan.loan import make_repayment_entry
from erpnext.loan_management.doctype.loan_repayment.loan_repayment import get_accrued_interest_entries
from frappe.model.naming import make_autoname
def execute(): def execute():
@ -18,15 +20,29 @@ def execute():
frappe.reload_doc('loan_management', 'doctype', 'loan_repayment_detail') frappe.reload_doc('loan_management', 'doctype', 'loan_repayment_detail')
frappe.reload_doc('loan_management', 'doctype', 'loan_interest_accrual') frappe.reload_doc('loan_management', 'doctype', 'loan_interest_accrual')
frappe.reload_doc('accounts', 'doctype', 'gl_entry') frappe.reload_doc('accounts', 'doctype', 'gl_entry')
frappe.reload_doc('accounts', 'doctype', 'journal_entry_account')
updated_loan_types = [] updated_loan_types = []
loans_to_close = []
# Update old loan status as closed
if frappe.db.has_column('Repayment Schedule', 'paid'):
loans_list = frappe.db.sql("""SELECT distinct parent from `tabRepayment Schedule`
where paid = 0 and docstatus = 1""", as_dict=1)
loans_to_close = [d.parent for d in loans_list]
if loans_to_close:
frappe.db.sql("UPDATE `tabLoan` set status = 'Closed' where name not in (%s)" % (', '.join(['%s'] * len(loans_to_close))), tuple(loans_to_close))
loans = frappe.get_all('Loan', fields=['name', 'loan_type', 'company', 'status', 'mode_of_payment', loans = frappe.get_all('Loan', fields=['name', 'loan_type', 'company', 'status', 'mode_of_payment',
'applicant_type', 'applicant', 'loan_account', 'payment_account', 'interest_income_account']) 'applicant_type', 'applicant', 'loan_account', 'payment_account', 'interest_income_account'],
filters={'docstatus': 1, 'status': ('!=', 'Closed')})
for loan in loans: for loan in loans:
# Update details in Loan Types and Loan # Update details in Loan Types and Loan
loan_type_company = frappe.db.get_value('Loan Type', loan.loan_type, 'company') loan_type_company = frappe.db.get_value('Loan Type', loan.loan_type, 'company')
loan_type = loan.loan_type
group_income_account = frappe.get_value('Account', {'company': loan.company, group_income_account = frappe.get_value('Account', {'company': loan.company,
'is_group': 1, 'root_type': 'Income', 'account_name': _('Indirect Income')}) 'is_group': 1, 'root_type': 'Income', 'account_name': _('Indirect Income')})
@ -38,7 +54,26 @@ def execute():
penalty_account = create_account(company=loan.company, account_type='Income Account', penalty_account = create_account(company=loan.company, account_type='Income Account',
account_name='Penalty Account', parent_account=group_income_account) account_name='Penalty Account', parent_account=group_income_account)
if not loan_type_company: # Same loan type used for multiple companies
if loan_type_company and loan_type_company != loan.company:
# get loan type for appropriate company
loan_type_name = frappe.get_value('Loan Type', {'company': loan.company,
'mode_of_payment': loan.mode_of_payment, 'loan_account': loan.loan_account,
'payment_account': loan.payment_account, 'interest_income_account': loan.interest_income_account,
'penalty_income_account': loan.penalty_income_account}, 'name')
if not loan_type_name:
loan_type_name = create_loan_type(loan, loan_type_name, penalty_account)
# update loan type in loan
frappe.db.sql("UPDATE `tabLoan` set loan_type = %s where name = %s", (loan_type_name,
loan.name))
loan_type = loan_type_name
if loan_type_name not in updated_loan_types:
updated_loan_types.append(loan_type_name)
elif not loan_type_company:
loan_type_doc = frappe.get_doc('Loan Type', loan.loan_type) loan_type_doc = frappe.get_doc('Loan Type', loan.loan_type)
loan_type_doc.is_term_loan = 1 loan_type_doc.is_term_loan = 1
loan_type_doc.company = loan.company loan_type_doc.company = loan.company
@ -49,8 +84,9 @@ def execute():
loan_type_doc.penalty_income_account = penalty_account loan_type_doc.penalty_income_account = penalty_account
loan_type_doc.submit() loan_type_doc.submit()
updated_loan_types.append(loan.loan_type) updated_loan_types.append(loan.loan_type)
loan_type = loan.loan_type
if loan.loan_type in updated_loan_types: if loan_type in updated_loan_types:
if loan.status == 'Fully Disbursed': if loan.status == 'Fully Disbursed':
status = 'Disbursed' status = 'Disbursed'
elif loan.status == 'Repaid/Closed': elif loan.status == 'Repaid/Closed':
@ -64,25 +100,48 @@ def execute():
'status': status 'status': status
}) })
process_loan_interest_accrual_for_term_loans(posting_date=nowdate(), loan_type=loan.loan_type, process_loan_interest_accrual_for_term_loans(posting_date=nowdate(), loan_type=loan_type,
loan=loan.name) loan=loan.name)
payments = frappe.db.sql(''' SELECT j.name, a.debit, a.debit_in_account_currency, j.posting_date
FROM `tabJournal Entry` j, `tabJournal Entry Account` a
WHERE a.parent = j.name and a.reference_type='Loan' and a.reference_name = %s
and account = %s
''', (loan.name, loan.loan_account), as_dict=1)
for payment in payments: if frappe.db.has_column('Repayment Schedule', 'paid'):
repayment_entry = make_repayment_entry(loan.name, loan.loan_applicant_type, loan.applicant, total_principal, total_interest = frappe.db.get_value('Repayment Schedule', {'paid': 1, 'parent': loan.name},
loan.loan_type, loan.company) ['sum(principal_amount) as total_principal', 'sum(interest_amount) as total_interest'])
repayment_entry.amount_paid = payment.debit_in_account_currency accrued_entries = get_accrued_interest_entries(loan.name)
repayment_entry.posting_date = payment.posting_date for entry in accrued_entries:
repayment_entry.save() interest_paid = 0
repayment_entry.submit() principal_paid = 0
jv = frappe.get_doc('Journal Entry', payment.name) if total_interest > entry.interest_amount:
jv.flags.ignore_links = True interest_paid = entry.interest_amount
jv.cancel() else:
interest_paid = total_interest
if total_principal > entry.payable_principal_amount:
principal_paid = entry.payable_principal_amount
else:
principal_paid = total_principal
frappe.db.sql(""" UPDATE `tabLoan Interest Accrual`
SET paid_principal_amount = `paid_principal_amount` + %s,
paid_interest_amount = `paid_interest_amount` + %s
WHERE name = %s""",
(principal_paid, interest_paid, entry.name))
total_principal -= principal_paid
total_interest -= interest_paid
def create_loan_type(loan, loan_type_name, penalty_account):
loan_type_doc = frappe.new_doc('Loan Type')
loan_type_doc.loan_name = make_autoname("Loan Type-.####")
loan_type_doc.is_term_loan = 1
loan_type_doc.company = loan.company
loan_type_doc.mode_of_payment = loan.mode_of_payment
loan_type_doc.payment_account = loan.payment_account
loan_type_doc.loan_account = loan.loan_account
loan_type_doc.interest_income_account = loan.interest_income_account
loan_type_doc.penalty_income_account = penalty_account
loan_type_doc.submit()
return loan_type_doc.name

View File

@ -0,0 +1,27 @@
# Copyright (c) 2019, Frappe and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
def execute():
frappe.reload_doc('stock', 'doctype', 'purchase_receipt')
frappe.reload_doc('stock', 'doctype', 'purchase_receipt_item')
frappe.reload_doc('stock', 'doctype', 'delivery_note')
frappe.reload_doc('stock', 'doctype', 'delivery_note_item')
def update_from_return_docs(doctype):
for return_doc in frappe.get_all(doctype, filters={'is_return' : 1, 'docstatus' : 1}):
# Update original receipt/delivery document from return
return_doc = frappe.get_cached_doc(doctype, return_doc.name)
return_doc.update_prevdoc_status()
return_against = frappe.get_doc(doctype, return_doc.return_against)
return_against.update_billing_status()
# Set received qty in stock uom in PR, as returned qty is checked against it
frappe.db.sql(""" update `tabPurchase Receipt Item`
set received_stock_qty = received_qty * conversion_factor
where docstatus = 1 """)
for doctype in ('Purchase Receipt', 'Delivery Note'):
update_from_return_docs(doctype)

View File

@ -7,19 +7,23 @@ import frappe
def execute(): def execute():
parent_list = [] parent_list = []
count = 0 count = 0
for data in frappe.db.sql("""
select frappe.reload_doc('stock', 'doctype', 'purchase_receipt')
frappe.reload_doc('stock', 'doctype', 'purchase_receipt_item')
for data in frappe.db.sql("""
select
`tabPurchase Receipt Item`.purchase_order, `tabPurchase Receipt Item`.name, `tabPurchase Receipt Item`.purchase_order, `tabPurchase Receipt Item`.name,
`tabPurchase Receipt Item`.item_code, `tabPurchase Receipt Item`.idx, `tabPurchase Receipt Item`.item_code, `tabPurchase Receipt Item`.idx,
`tabPurchase Receipt Item`.parent `tabPurchase Receipt Item`.parent
from from
`tabPurchase Receipt Item`, `tabPurchase Receipt` `tabPurchase Receipt Item`, `tabPurchase Receipt`
where where
`tabPurchase Receipt Item`.parent = `tabPurchase Receipt`.name and `tabPurchase Receipt Item`.parent = `tabPurchase Receipt`.name and
`tabPurchase Receipt Item`.purchase_order_item is null and `tabPurchase Receipt Item`.purchase_order_item is null and
`tabPurchase Receipt Item`.purchase_order is not null and `tabPurchase Receipt Item`.purchase_order is not null and
`tabPurchase Receipt`.is_return = 1""", as_dict=1): `tabPurchase Receipt`.is_return = 1""", as_dict=1):
name = frappe.db.get_value('Purchase Order Item', name = frappe.db.get_value('Purchase Order Item',
{'item_code': data.item_code, 'parent': data.purchase_order, 'idx': data.idx}, 'name') {'item_code': data.item_code, 'parent': data.purchase_order, 'idx': data.idx}, 'name')
if name: if name:

View File

@ -1,48 +0,0 @@
# Copyright (c) 2017, Frappe and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
from frappe.utils import getdate
from frappe.automation.doctype.auto_repeat.auto_repeat import get_next_schedule_date
def execute():
frappe.reload_doc('accounts', 'doctype', 'subscription')
fields = ["name", "reference_doctype", "reference_document",
"start_date", "frequency", "repeat_on_day"]
for d in fields:
if not frappe.db.has_column('Subscription', d):
return
doctypes = ('Purchase Order', 'Sales Order', 'Purchase Invoice', 'Sales Invoice')
for data in frappe.get_all('Subscription',
fields = fields,
filters = {'reference_doctype': ('in', doctypes), 'docstatus': 1}):
recurring_id = frappe.db.get_value(data.reference_doctype, data.reference_document, "recurring_id")
if recurring_id:
frappe.db.sql("update `tab{0}` set subscription=%s where recurring_id=%s"
.format(data.reference_doctype), (data.name, recurring_id))
date_field = 'transaction_date'
if data.reference_doctype in ['Sales Invoice', 'Purchase Invoice']:
date_field = 'posting_date'
start_date = frappe.db.get_value(data.reference_doctype, data.reference_document, date_field)
if start_date and getdate(start_date) != getdate(data.start_date):
last_ref_date = frappe.db.sql("""
select {0}
from `tab{1}`
where subscription=%s and docstatus < 2
order by creation desc
limit 1
""".format(date_field, data.reference_doctype), data.name)[0][0]
next_schedule_date = get_next_schedule_date(last_ref_date, data.frequency, data.repeat_on_day)
frappe.db.set_value("Subscription", data.name, {
"start_date": start_date,
"next_schedule_date": next_schedule_date
}, None)

View File

@ -12,14 +12,6 @@ frappe.ui.form.on('Additional Salary', {
} }
}; };
}); });
if (!frm.doc.currency) return;
frm.set_query("salary_component", function() {
return {
query: "erpnext.payroll.doctype.salary_structure.salary_structure.get_earning_deduction_components",
filters: {currency: frm.doc.currency, company: frm.doc.company}
};
});
}, },
employee: function(frm) { employee: function(frm) {

View File

@ -23,6 +23,7 @@
"employee_benefits", "employee_benefits",
"totals", "totals",
"total_amount", "total_amount",
"column_break",
"pro_rata_dispensed_amount" "pro_rata_dispensed_amount"
], ],
"fields": [ "fields": [
@ -139,11 +140,15 @@
"label": "Company", "label": "Company",
"options": "Company", "options": "Company",
"reqd": 1 "reqd": 1
},
{
"fieldname": "column_break",
"fieldtype": "Column Break"
} }
], ],
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-11-25 11:49:05.095101", "modified": "2020-12-14 15:52:08.566418",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Payroll", "module": "Payroll",
"name": "Employee Benefit Application", "name": "Employee Benefit Application",

View File

@ -11,11 +11,11 @@ frappe.ui.form.on('Employee Incentive', {
}; };
}); });
if (!frm.doc.currency) return; if (!frm.doc.company) return;
frm.set_query("salary_component", function() { frm.set_query("salary_component", function() {
return { return {
query: "erpnext.payroll.doctype.salary_structure.salary_structure.get_earning_deduction_components", query: "erpnext.payroll.doctype.salary_structure.salary_structure.get_earning_deduction_components",
filters: {type: "earning", currency: frm.doc.currency, company: frm.doc.company} filters: {type: "earning", company: frm.doc.company}
}; };
}); });

View File

@ -289,7 +289,9 @@ class PayrollEntry(Document):
jv_name = journal_entry.name jv_name = journal_entry.name
self.update_salary_slip_status(jv_name = jv_name) self.update_salary_slip_status(jv_name = jv_name)
except Exception as e: except Exception as e:
frappe.msgprint(e) if type(e) in (str, list, tuple):
frappe.msgprint(e)
raise
return jv_name return jv_name

View File

@ -4,9 +4,13 @@
frappe.ui.form.on('Retention Bonus', { frappe.ui.form.on('Retention Bonus', {
setup: function(frm) { setup: function(frm) {
frm.set_query("employee", function() { frm.set_query("employee", function() {
if (!frm.doc.company) {
frappe.msgprint(__("Please Select Company First"));
}
return { return {
filters: { filters: {
"status": "Active" "status": "Active",
"company": frm.doc.company
} }
}; };
}); });

View File

@ -55,17 +55,17 @@ frappe.ui.form.on('Salary Structure', {
}, },
set_earning_deduction_component: function(frm) { set_earning_deduction_component: function(frm) {
if(!frm.doc.currency && !frm.doc.company) return; if(!frm.doc.company) return;
frm.set_query("salary_component", "earnings", function() { frm.set_query("salary_component", "earnings", function() {
return { return {
query : "erpnext.payroll.doctype.salary_structure.salary_structure.get_earning_deduction_components", query : "erpnext.payroll.doctype.salary_structure.salary_structure.get_earning_deduction_components",
filters: {type: "earning", currency: frm.doc.currency, company: frm.doc.company} filters: {type: "earning", company: frm.doc.company}
}; };
}); });
frm.set_query("salary_component", "deductions", function() { frm.set_query("salary_component", "deductions", function() {
return { return {
query : "erpnext.payroll.doctype.salary_structure.salary_structure.get_earning_deduction_components", query : "erpnext.payroll.doctype.salary_structure.salary_structure.get_earning_deduction_components",
filters: {type: "deduction", currency: frm.doc.currency, company: frm.doc.company} filters: {type: "deduction", company: frm.doc.company}
}; };
}); });
}, },
@ -74,7 +74,6 @@ frappe.ui.form.on('Salary Structure', {
currency: function(frm) { currency: function(frm) {
calculate_totals(frm.doc); calculate_totals(frm.doc);
frm.trigger("set_dynamic_labels") frm.trigger("set_dynamic_labels")
frm.trigger('set_earning_deduction_component');
frm.refresh() frm.refresh()
}, },

View File

@ -210,7 +210,7 @@ def get_employees(salary_structure):
@frappe.whitelist() @frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs @frappe.validate_and_sanitize_search_inputs
def get_earning_deduction_components(doctype, txt, searchfield, start, page_len, filters): def get_earning_deduction_components(doctype, txt, searchfield, start, page_len, filters):
if len(filters) < 3: if len(filters) < 2:
return {} return {}
return frappe.db.sql(""" return frappe.db.sql("""

View File

@ -189,6 +189,7 @@ erpnext.buying.BuyingController = erpnext.TransactionController.extend({
frappe.model.round_floats_in(item, ["qty", "received_qty"]); frappe.model.round_floats_in(item, ["qty", "received_qty"]);
item.rejected_qty = flt(item.received_qty - item.qty, precision("rejected_qty", item)); item.rejected_qty = flt(item.received_qty - item.qty, precision("rejected_qty", item));
item.received_stock_qty = flt(item.conversion_factor, precision("conversion_factor", item)) * flt(item.received_qty);
} }
this._super(doc, cdt, cdn); this._super(doc, cdt, cdn);
@ -293,69 +294,6 @@ erpnext.buying.BuyingController = erpnext.TransactionController.extend({
this.get_terms(); this.get_terms();
}, },
link_to_mrs: function() {
var my_items = [];
for (var i in cur_frm.doc.items) {
if(!cur_frm.doc.items[i].material_request){
my_items.push(cur_frm.doc.items[i].item_code);
}
}
frappe.call({
method: "erpnext.buying.utils.get_linked_material_requests",
args:{
items: my_items
},
callback: function(r) {
if(!r.message || r.message.length == 0) {
frappe.throw(__("No pending Material Requests found to link for the given items."))
}
else {
var i = 0;
var item_length = cur_frm.doc.items.length;
while (i < item_length) {
var qty = cur_frm.doc.items[i].qty;
(r.message[0] || []).forEach(function(d) {
if (d.qty > 0 && qty > 0 && cur_frm.doc.items[i].item_code == d.item_code && !cur_frm.doc.items[i].material_request_item)
{
cur_frm.doc.items[i].material_request = d.mr_name;
cur_frm.doc.items[i].material_request_item = d.mr_item;
var my_qty = Math.min(qty, d.qty);
qty = qty - my_qty;
d.qty = d.qty - my_qty;
cur_frm.doc.items[i].stock_qty = my_qty*cur_frm.doc.items[i].conversion_factor;
cur_frm.doc.items[i].qty = my_qty;
frappe.msgprint("Assigning " + d.mr_name + " to " + d.item_code + " (row " + cur_frm.doc.items[i].idx + ")");
if (qty > 0)
{
frappe.msgprint("Splitting " + qty + " units of " + d.item_code);
var newrow = frappe.model.add_child(cur_frm.doc, cur_frm.doc.items[i].doctype, "items");
item_length++;
for (var key in cur_frm.doc.items[i])
{
newrow[key] = cur_frm.doc.items[i][key];
}
newrow.idx = item_length;
newrow["stock_qty"] = newrow.conversion_factor*qty;
newrow["qty"] = qty;
newrow["material_request"] = "";
newrow["material_request_item"] = "";
}
}
});
i++;
}
refresh_field("items");
//cur_frm.save();
}
}
});
},
update_auto_repeat_reference: function(doc) { update_auto_repeat_reference: function(doc) {
if (doc.auto_repeat) { if (doc.auto_repeat) {
frappe.call({ frappe.call({
@ -421,6 +359,62 @@ erpnext.buying.BuyingController = erpnext.TransactionController.extend({
cur_frm.add_fetch('project', 'cost_center', 'cost_center'); cur_frm.add_fetch('project', 'cost_center', 'cost_center');
erpnext.buying.link_to_mrs = function(frm) {
frappe.call({
method: "erpnext.buying.utils.get_linked_material_requests",
args:{
items: frm.doc.items.map((item) => item.item_code)
},
callback: function(r) {
if (!r.message || r.message.length == 0) {
frappe.throw({
message: __("No pending Material Requests found to link for the given items."),
title: __("Note")
});
}
var item_length = frm.doc.items.length;
for (let item of frm.doc.items) {
var qty = item.qty;
(r.message[0] || []).forEach(function(d) {
if (d.qty > 0 && qty > 0 && item.item_code == d.item_code && !item.material_request_item)
{
item.material_request = d.mr_name;
item.material_request_item = d.mr_item;
var my_qty = Math.min(qty, d.qty);
qty = qty - my_qty;
d.qty = d.qty - my_qty;
item.stock_qty = my_qty*item.conversion_factor;
item.qty = my_qty;
frappe.msgprint("Assigning " + d.mr_name + " to " + d.item_code + " (row " + item.idx + ")");
if (qty > 0)
{
frappe.msgprint("Splitting " + qty + " units of " + d.item_code);
var newrow = frappe.model.add_child(frm.doc, item.doctype, "items");
item_length++;
for (var key in item)
{
newrow[key] = item[key];
}
newrow.idx = item_length;
newrow["stock_qty"] = newrow.conversion_factor*qty;
newrow["qty"] = qty;
newrow["material_request"] = "";
newrow["material_request_item"] = "";
}
}
});
}
refresh_field("items");
}
});
}
erpnext.buying.get_default_bom = function(frm) { erpnext.buying.get_default_bom = function(frm) {
$.each(frm.doc["items"] || [], function(i, d) { $.each(frm.doc["items"] || [], function(i, d) {
if (d.item_code && d.bom === "") { if (d.item_code && d.bom === "") {

View File

@ -609,6 +609,15 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
this.calculate_outstanding_amount(update_paid_amount); this.calculate_outstanding_amount(update_paid_amount);
}, },
is_internal_invoice: function() {
if (['Sales Invoice', 'Purchase Invoice'].includes(this.frm.doc.doctype)) {
if (this.frm.doc.company === this.frm.doc.represents_company) {
return true;
}
}
return false;
},
calculate_outstanding_amount: function(update_paid_amount) { calculate_outstanding_amount: function(update_paid_amount) {
// NOTE: // NOTE:
// paid_amount and write_off_amount is only for POS/Loyalty Point Redemption Invoice // paid_amount and write_off_amount is only for POS/Loyalty Point Redemption Invoice
@ -617,7 +626,7 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
this.calculate_paid_amount(); this.calculate_paid_amount();
} }
if(this.frm.doc.is_return || this.frm.doc.docstatus > 0) return; if (this.frm.doc.is_return || (this.frm.doc.docstatus > 0) || this.is_internal_invoice()) return;
frappe.model.round_floats_in(this.frm.doc, ["grand_total", "total_advance", "write_off_amount"]); frappe.model.round_floats_in(this.frm.doc, ["grand_total", "total_advance", "write_off_amount"]);

View File

@ -408,7 +408,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
show_description(row_to_modify.idx, row_to_modify.item_code); show_description(row_to_modify.idx, row_to_modify.item_code);
this.frm.from_barcode = true; this.frm.from_barcode = this.frm.from_barcode ? this.frm.from_barcode + 1 : 1;
frappe.model.set_value(row_to_modify.doctype, row_to_modify.name, { frappe.model.set_value(row_to_modify.doctype, row_to_modify.name, {
item_code: data.item_code, item_code: data.item_code,
qty: (row_to_modify.qty || 0) + 1 qty: (row_to_modify.qty || 0) + 1
@ -492,7 +492,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
d.item_code = ""; d.item_code = "";
} }
this.frm.from_barcode = true; this.frm.from_barcode = this.frm.from_barcode ? this.frm.from_barcode + 1 : 1;
this.item_code(doc, cdt, cdn); this.item_code(doc, cdt, cdn);
}, },
@ -509,11 +509,12 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
show_batch_dialog = 1; show_batch_dialog = 1;
} }
// clear barcode if setting item (else barcode will take priority) // clear barcode if setting item (else barcode will take priority)
if(!this.frm.from_barcode) { if (this.frm.from_barcode == 0) {
item.barcode = null; item.barcode = null;
} }
this.frm.from_barcode = this.frm.from_barcode - 1 >= 0 ? this.frm.from_barcode - 1 : 0;
this.frm.from_barcode = false;
if(item.item_code || item.barcode || item.serial_no) { if(item.item_code || item.barcode || item.serial_no) {
if(!this.validate_company_and_party()) { if(!this.validate_company_and_party()) {
this.frm.fields_dict["items"].grid.grid_rows[item.idx - 1].remove(); this.frm.fields_dict["items"].grid.grid_rows[item.idx - 1].remove();

View File

@ -20,4 +20,4 @@ frappe.ui.form.ControlData = frappe.ui.form.ControlData.extend( {
}); });
} }
} }
}); });

View File

@ -192,19 +192,20 @@ class GSTR3BReport(Document):
for d in self.report_dict["itc_elg"]["itc_avl"]: for d in self.report_dict["itc_elg"]["itc_avl"]:
itc_type = itc_type_map.get(d["ty"]) itc_type = itc_type_map.get(d["ty"])
gst_category = ["Registered Regular"]
if d["ty"] == 'ISRC': if d["ty"] == 'ISRC':
reverse_charge = "Y" reverse_charge = ["Y"]
itc_type = 'All Other ITC' itc_type = 'All Other ITC'
gst_category = ['Unregistered', 'Overseas'] gst_category = ['Unregistered', 'Overseas']
else: else:
reverse_charge = "N" gst_category = ['Unregistered', 'Overseas', 'Registered Regular']
reverse_charge = ["N", "Y"]
for account_head in self.account_heads: for account_head in self.account_heads:
for category in gst_category: for category in gst_category:
for key in [['iamt', 'igst_account'], ['camt', 'cgst_account'], ['samt', 'sgst_account'], ['csamt', 'cess_account']]: for charge_type in reverse_charge:
d[key[0]] += flt(itc_details.get((category, itc_type, reverse_charge, account_head.get(key[1])), {}).get("amount"), 2) for key in [['iamt', 'igst_account'], ['camt', 'cgst_account'], ['samt', 'sgst_account'], ['csamt', 'cess_account']]:
d[key[0]] += flt(itc_details.get((category, itc_type, charge_type, account_head.get(key[1])), {}).get("amount"), 2)
for key in ['iamt', 'camt', 'samt', 'csamt']: for key in ['iamt', 'camt', 'samt', 'csamt']:
net_itc[key] += flt(d[key], 2) net_itc[key] += flt(d[key], 2)
@ -264,7 +265,8 @@ class GSTR3BReport(Document):
def get_itc_details(self): def get_itc_details(self):
itc_amount = frappe.db.sql(""" itc_amount = frappe.db.sql("""
select s.gst_category, sum(t.tax_amount_after_discount_amount) as tax_amount, t.account_head, s.eligibility_for_itc, s.reverse_charge select s.gst_category, sum(t.base_tax_amount_after_discount_amount) as tax_amount,
t.account_head, s.eligibility_for_itc, s.reverse_charge
from `tabPurchase Invoice` s , `tabPurchase Taxes and Charges` t from `tabPurchase Invoice` s , `tabPurchase Taxes and Charges` t
where s.docstatus = 1 and t.parent = s.name where s.docstatus = 1 and t.parent = s.name
and month(s.posting_date) = %s and year(s.posting_date) = %s and s.company = %s and month(s.posting_date) = %s and year(s.posting_date) = %s and s.company = %s
@ -387,7 +389,7 @@ class GSTR3BReport(Document):
tax_template = 'Purchase Taxes and Charges' tax_template = 'Purchase Taxes and Charges'
tax_amounts = frappe.db.sql(""" tax_amounts = frappe.db.sql("""
select s.gst_category, sum(t.tax_amount_after_discount_amount) as tax_amount, t.account_head select s.gst_category, sum(t.base_tax_amount_after_discount_amount) as tax_amount, t.account_head
from `tab{doctype}` s , `tab{template}` t from `tab{doctype}` s , `tab{template}` t
where s.docstatus = 1 and t.parent = s.name and s.reverse_charge = %s where s.docstatus = 1 and t.parent = s.name and s.reverse_charge = %s
and month(s.posting_date) = %s and year(s.posting_date) = %s and s.company = %s and month(s.posting_date) = %s and year(s.posting_date) = %s and s.company = %s

View File

@ -12,6 +12,9 @@ erpnext.setup_auto_gst_taxation = (doctype) => {
tax_category: function(frm) { tax_category: function(frm) {
frm.trigger('get_tax_template'); frm.trigger('get_tax_template');
}, },
customer_address: function(frm) {
frm.trigger('get_tax_template');
},
get_tax_template: function(frm) { get_tax_template: function(frm) {
if (!frm.doc.company) return; if (!frm.doc.company) return;
@ -19,6 +22,7 @@ erpnext.setup_auto_gst_taxation = (doctype) => {
'shipping_address': frm.doc.shipping_address || '', 'shipping_address': frm.doc.shipping_address || '',
'shipping_address_name': frm.doc.shipping_address_name || '', 'shipping_address_name': frm.doc.shipping_address_name || '',
'customer_address': frm.doc.customer_address || '', 'customer_address': frm.doc.customer_address || '',
'supplier_address': frm.doc.supplier_address,
'customer': frm.doc.customer, 'customer': frm.doc.customer,
'supplier': frm.doc.supplier, 'supplier': frm.doc.supplier,
'supplier_gstin': frm.doc.supplier_gstin, 'supplier_gstin': frm.doc.supplier_gstin,

View File

@ -12,6 +12,7 @@ from erpnext.regional.india import number_state_mapping
from six import string_types from six import string_types
from erpnext.accounts.general_ledger import make_gl_entries from erpnext.accounts.general_ledger import make_gl_entries
from erpnext.accounts.utils import get_account_currency from erpnext.accounts.utils import get_account_currency
from frappe.model.utils import get_fetch_values
def validate_gstin_for_india(doc, method): def validate_gstin_for_india(doc, method):
if hasattr(doc, 'gst_state') and doc.gst_state: if hasattr(doc, 'gst_state') and doc.gst_state:
@ -161,6 +162,8 @@ def get_regional_address_details(party_details, doctype, company):
party_details = json.loads(party_details) party_details = json.loads(party_details)
party_details = frappe._dict(party_details) party_details = frappe._dict(party_details)
update_party_details(party_details, doctype)
party_details.place_of_supply = get_place_of_supply(party_details, doctype) party_details.place_of_supply = get_place_of_supply(party_details, doctype)
if is_internal_transfer(party_details, doctype): if is_internal_transfer(party_details, doctype):
@ -209,6 +212,11 @@ def get_regional_address_details(party_details, doctype, company):
return party_details return party_details
def update_party_details(party_details, doctype):
for address_field in ['shipping_address', 'company_address', 'supplier_address', 'shipping_address_name', 'customer_address']:
if party_details.get(address_field):
party_details.update(get_fetch_values(doctype, address_field, party_details.get(address_field)))
def is_internal_transfer(party_details, doctype): def is_internal_transfer(party_details, doctype):
if doctype in ("Sales Invoice", "Delivery Note", "Sales Order"): if doctype in ("Sales Invoice", "Delivery Note", "Sales Order"):
destination_gstin = party_details.company_gstin destination_gstin = party_details.company_gstin

View File

@ -151,6 +151,7 @@ class Gstr1Report(object):
{select_columns} {select_columns}
from `tab{doctype}` from `tab{doctype}`
where docstatus = 1 {where_conditions} where docstatus = 1 {where_conditions}
and is_opening = 'No'
order by posting_date desc order by posting_date desc
""".format(select_columns=self.select_columns, doctype=self.doctype, """.format(select_columns=self.select_columns, doctype=self.doctype,
where_conditions=conditions), self.filters, as_dict=1) where_conditions=conditions), self.filters, as_dict=1)

View File

@ -58,6 +58,7 @@ class Customer(TransactionBase):
self.set_loyalty_program() self.set_loyalty_program()
self.check_customer_group_change() self.check_customer_group_change()
self.validate_default_bank_account() self.validate_default_bank_account()
self.validate_internal_customer()
# set loyalty program tier # set loyalty program tier
if frappe.db.exists('Customer', self.name): if frappe.db.exists('Customer', self.name):
@ -82,6 +83,11 @@ class Customer(TransactionBase):
if not is_company_account: if not is_company_account:
frappe.throw(_("{0} is not a company bank account").format(frappe.bold(self.default_bank_account))) frappe.throw(_("{0} is not a company bank account").format(frappe.bold(self.default_bank_account)))
def validate_internal_customer(self):
if self.is_internal_customer and frappe.db.get_value('Customer', {"represents_company": self.represents_company}, "name"):
frappe.throw(_("Internal Customer for company {0} already exists").format(
frappe.bold(self.represents_company)))
def on_update(self): def on_update(self):
self.validate_name_with_customer_group() self.validate_name_with_customer_group()
self.create_primary_contact() self.create_primary_contact()
@ -398,7 +404,7 @@ def check_credit_limit(customer, company, ignore_outstanding_sales_order=False,
# form a list of emails and names to show to the user # form a list of emails and names to show to the user
credit_controller_users_formatted = [get_formatted_email(user).replace("<", "(").replace(">", ")") for user in credit_controller_users] credit_controller_users_formatted = [get_formatted_email(user).replace("<", "(").replace(">", ")") for user in credit_controller_users]
if not credit_controller_users_formatted: if not credit_controller_users_formatted:
frappe.throw(_("Please contact your administrator to extend the credit limits for {0}.".format(customer))) frappe.throw(_("Please contact your administrator to extend the credit limits for {0}.").format(customer))
message = """Please contact any of the following users to extend the credit limits for {0}: message = """Please contact any of the following users to extend the credit limits for {0}:
<br><br><ul><li>{1}</li></ul>""".format(customer, '<li>'.join(credit_controller_users_formatted)) <br><br><ul><li>{1}</li></ul>""".format(customer, '<li>'.join(credit_controller_users_formatted))

View File

@ -8,7 +8,7 @@ frappe.ui.form.on("Sales Order", {
frm.custom_make_buttons = { frm.custom_make_buttons = {
'Delivery Note': 'Delivery Note', 'Delivery Note': 'Delivery Note',
'Pick List': 'Pick List', 'Pick List': 'Pick List',
'Sales Invoice': 'Invoice', 'Sales Invoice': 'Sales Invoice',
'Material Request': 'Material Request', 'Material Request': 'Material Request',
'Purchase Order': 'Purchase Order', 'Purchase Order': 'Purchase Order',
'Project': 'Project', 'Project': 'Project',

View File

@ -14,7 +14,6 @@ from erpnext.stock.stock_balance import update_bin_qty, get_reserved_qty
from frappe.desk.notifications import clear_doctype_notifications from frappe.desk.notifications import clear_doctype_notifications
from frappe.contacts.doctype.address.address import get_company_address from frappe.contacts.doctype.address.address import get_company_address
from erpnext.controllers.selling_controller import SellingController from erpnext.controllers.selling_controller import SellingController
from frappe.automation.doctype.auto_repeat.auto_repeat import get_next_schedule_date
from erpnext.selling.doctype.customer.customer import check_credit_limit from erpnext.selling.doctype.customer.customer import check_credit_limit
from erpnext.stock.doctype.item.item import get_item_defaults from erpnext.stock.doctype.item.item import get_item_defaults
from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
@ -418,8 +417,7 @@ class SalesOrder(SellingController):
def on_recurring(self, reference_doc, auto_repeat_doc): def on_recurring(self, reference_doc, auto_repeat_doc):
def _get_delivery_date(ref_doc_delivery_date, red_doc_transaction_date, transaction_date): def _get_delivery_date(ref_doc_delivery_date, red_doc_transaction_date, transaction_date):
delivery_date = get_next_schedule_date(ref_doc_delivery_date, delivery_date = auto_repeat_doc.get_next_schedule_date(schedule_date=ref_doc_delivery_date)
auto_repeat_doc.frequency, auto_repeat_doc.start_date, cint(auto_repeat_doc.repeat_on_day))
if delivery_date <= transaction_date: if delivery_date <= transaction_date:
delivery_date_diff = frappe.utils.date_diff(ref_doc_delivery_date, red_doc_transaction_date) delivery_date_diff = frappe.utils.date_diff(ref_doc_delivery_date, red_doc_transaction_date)

View File

@ -111,24 +111,24 @@ erpnext.PointOfSale.Controller = class {
dialog.show(); dialog.show();
} }
prepare_app_defaults(data) { async prepare_app_defaults(data) {
this.pos_opening = data.name; this.pos_opening = data.name;
this.company = data.company; this.company = data.company;
this.pos_profile = data.pos_profile; this.pos_profile = data.pos_profile;
this.pos_opening_time = data.period_start_date; this.pos_opening_time = data.period_start_date;
this.item_stock_map = {};
this.settings = {};
frappe.db.get_value('Stock Settings', undefined, 'allow_negative_stock').then(({ message }) => { frappe.db.get_value('Stock Settings', undefined, 'allow_negative_stock').then(({ message }) => {
this.allow_negative_stock = flt(message.allow_negative_stock) || false; this.allow_negative_stock = flt(message.allow_negative_stock) || false;
}); });
frappe.db.get_doc("POS Profile", this.pos_profile).then((profile) => { frappe.db.get_doc("POS Profile", this.pos_profile).then((profile) => {
this.customer_groups = profile.customer_groups.map(group => group.customer_group); this.settings.customer_groups = profile.customer_groups.map(group => group.customer_group);
this.cart.make_customer_selector(); this.settings.hide_images = profile.hide_images;
this.settings.auto_add_item_to_cart = profile.auto_add_item_to_cart;
this.make_app();
}); });
this.item_stock_map = {};
this.make_app();
} }
set_opening_entry_status() { set_opening_entry_status() {
@ -238,12 +238,11 @@ erpnext.PointOfSale.Controller = class {
this.item_selector = new erpnext.PointOfSale.ItemSelector({ this.item_selector = new erpnext.PointOfSale.ItemSelector({
wrapper: this.$components_wrapper, wrapper: this.$components_wrapper,
pos_profile: this.pos_profile, pos_profile: this.pos_profile,
settings: this.settings,
events: { events: {
item_selected: args => this.on_cart_update(args), item_selected: args => this.on_cart_update(args),
get_frm: () => this.frm || {}, get_frm: () => this.frm || {}
get_allowed_item_group: () => this.item_groups
} }
}) })
} }
@ -251,6 +250,7 @@ erpnext.PointOfSale.Controller = class {
init_item_cart() { init_item_cart() {
this.cart = new erpnext.PointOfSale.ItemCart({ this.cart = new erpnext.PointOfSale.ItemCart({
wrapper: this.$components_wrapper, wrapper: this.$components_wrapper,
settings: this.settings,
events: { events: {
get_frm: () => this.frm, get_frm: () => this.frm,
@ -273,9 +273,7 @@ erpnext.PointOfSale.Controller = class {
this.customer_details = details; this.customer_details = details;
// will add/remove LP payment method // will add/remove LP payment method
this.payment.render_loyalty_points_payment_mode(); this.payment.render_loyalty_points_payment_mode();
}, }
get_allowed_customer_group: () => this.customer_groups
} }
}) })
} }

View File

@ -1,8 +1,10 @@
erpnext.PointOfSale.ItemCart = class { erpnext.PointOfSale.ItemCart = class {
constructor({ wrapper, events }) { constructor({ wrapper, events, settings }) {
this.wrapper = wrapper; this.wrapper = wrapper;
this.events = events; this.events = events;
this.customer_info = undefined; this.customer_info = undefined;
this.hide_images = settings.hide_images;
this.allowed_customer_groups = settings.customer_groups;
this.init_component(); this.init_component();
} }
@ -32,6 +34,7 @@ erpnext.PointOfSale.ItemCart = class {
`<div class="customer-section rounded flex flex-col m-8 mb-0"></div>` `<div class="customer-section rounded flex flex-col m-8 mb-0"></div>`
) )
this.$customer_section = this.$component.find('.customer-section'); this.$customer_section = this.$component.find('.customer-section');
this.make_customer_selector();
} }
reset_customer_selector() { reset_customer_selector() {
@ -302,7 +305,7 @@ erpnext.PointOfSale.ItemCart = class {
this.$customer_section.html(`<div class="customer-search-field flex flex-1 items-center"></div>`); this.$customer_section.html(`<div class="customer-search-field flex flex-1 items-center"></div>`);
const me = this; const me = this;
const query = { query: 'erpnext.controllers.queries.customer_query' }; const query = { query: 'erpnext.controllers.queries.customer_query' };
const allowed_customer_group = this.events.get_allowed_customer_group() || []; const allowed_customer_group = this.allowed_customer_groups || [];
if (allowed_customer_group.length) { if (allowed_customer_group.length) {
query.filters = { query.filters = {
customer_group: ['in', allowed_customer_group] customer_group: ['in', allowed_customer_group]
@ -423,6 +426,7 @@ erpnext.PointOfSale.ItemCart = class {
} }
update_customer_section() { update_customer_section() {
const me = this;
const { customer, email_id='', mobile_no='', image } = this.customer_info || {}; const { customer, email_id='', mobile_no='', image } = this.customer_info || {};
if (customer) { if (customer) {
@ -460,7 +464,7 @@ erpnext.PointOfSale.ItemCart = class {
} }
function get_customer_image() { function get_customer_image() {
if (image) { if (!me.hide_images && image) {
return `<div class="icon flex items-center justify-center w-12 h-12 rounded bg-light-grey mr-4 text-grey-200"> return `<div class="icon flex items-center justify-center w-12 h-12 rounded bg-light-grey mr-4 text-grey-200">
<img class="h-full" src="${image}" alt="${image}" style="object-fit: cover;"> <img class="h-full" src="${image}" alt="${image}" style="object-fit: cover;">
</div>` </div>`

View File

@ -1,8 +1,10 @@
erpnext.PointOfSale.ItemSelector = class { erpnext.PointOfSale.ItemSelector = class {
constructor({ frm, wrapper, events, pos_profile }) { constructor({ frm, wrapper, events, pos_profile, settings }) {
this.wrapper = wrapper; this.wrapper = wrapper;
this.events = events; this.events = events;
this.pos_profile = pos_profile; this.pos_profile = pos_profile;
this.hide_images = settings.hide_images;
this.auto_add_item = settings.auto_add_item_to_cart;
this.inti_component(); this.inti_component();
} }
@ -26,13 +28,14 @@ erpnext.PointOfSale.ItemSelector = class {
<div class="flex flex-1 flex-col p-8 pt-2"> <div class="flex flex-1 flex-col p-8 pt-2">
<div class="text-grey mb-6">ALL ITEMS</div> <div class="text-grey mb-6">ALL ITEMS</div>
<div class="items-container grid grid-cols-4 gap-8"> <div class="items-container grid grid-cols-4 gap-8">
</div> </div>
</div> </div>
</div> </div>
</section>` </section>`
); );
this.$component = this.wrapper.find('.items-selector'); this.$component = this.wrapper.find('.items-selector');
this.$items_container = this.$component.find('.items-container');
} }
async load_items_data() { async load_items_data() {
@ -65,7 +68,6 @@ erpnext.PointOfSale.ItemSelector = class {
render_item_list(items) { render_item_list(items) {
this.$items_container = this.$component.find('.items-container');
this.$items_container.html(''); this.$items_container.html('');
items.forEach(item => { items.forEach(item => {
@ -75,11 +77,12 @@ erpnext.PointOfSale.ItemSelector = class {
} }
get_item_html(item) { get_item_html(item) {
const me = this;
const { item_image, serial_no, batch_no, barcode, actual_qty, stock_uom } = item; const { item_image, serial_no, batch_no, barcode, actual_qty, stock_uom } = item;
const indicator_color = actual_qty > 10 ? "green" : actual_qty <= 0 ? "red" : "orange"; const indicator_color = actual_qty > 10 ? "green" : actual_qty <= 0 ? "red" : "orange";
function get_item_image_html() { function get_item_image_html() {
if (item_image) { if (!me.hide_images && item_image) {
return `<div class="flex items-center justify-center h-32 border-b-grey text-6xl text-grey-100"> return `<div class="flex items-center justify-center h-32 border-b-grey text-6xl text-grey-100">
<img class="h-full" src="${item_image}" alt="${frappe.get_abbr(item.item_name)}" style="object-fit: cover;"> <img class="h-full" src="${item_image}" alt="${frappe.get_abbr(item.item_name)}" style="object-fit: cover;">
</div>` </div>`
@ -203,6 +206,7 @@ erpnext.PointOfSale.ItemSelector = class {
ignore_inputs: true, ignore_inputs: true,
page: cur_page.page.page page: cur_page.page.page
}); });
// for selecting the last filtered item on search // for selecting the last filtered item on search
frappe.ui.keys.on("enter", () => { frappe.ui.keys.on("enter", () => {
const selector_is_visible = this.$component.is(':visible'); const selector_is_visible = this.$component.is(':visible');
@ -235,6 +239,7 @@ erpnext.PointOfSale.ItemSelector = class {
const items = this.search_index[search_term]; const items = this.search_index[search_term];
this.items = items; this.items = items;
this.render_item_list(items); this.render_item_list(items);
this.auto_add_item && this.items.length == 1 && this.add_filtered_item_to_cart();
return; return;
} }
} }
@ -247,8 +252,13 @@ erpnext.PointOfSale.ItemSelector = class {
} }
this.items = items; this.items = items;
this.render_item_list(items); this.render_item_list(items);
this.auto_add_item && this.items.length == 1 && this.add_filtered_item_to_cart();
}); });
} }
add_filtered_item_to_cart() {
this.$items_container.find(".item-wrapper").click();
}
resize_selector(minimize) { resize_selector(minimize) {
minimize ? minimize ?

View File

@ -10,8 +10,8 @@ from frappe.utils.nestedset import get_descendants_of
def execute(filters=None): def execute(filters=None):
filters = frappe._dict(filters or {}) filters = frappe._dict(filters or {})
if filters.from_date > filters.to_date: if filters.from_date > filters.to_date:
frappe.throw(_('From Date cannot be greater than To Date')) frappe.throw(_("From Date cannot be greater than To Date"))
columns = get_columns(filters) columns = get_columns(filters)
data = get_data(filters) data = get_data(filters)
@ -148,14 +148,16 @@ def get_data(filters):
company_list.append(filters.get("company")) company_list.append(filters.get("company"))
customer_details = get_customer_details() customer_details = get_customer_details()
item_details = get_item_details()
sales_order_records = get_sales_order_details(company_list, filters) sales_order_records = get_sales_order_details(company_list, filters)
for record in sales_order_records: for record in sales_order_records:
customer_record = customer_details.get(record.customer) customer_record = customer_details.get(record.customer)
item_record = item_details.get(record.item_code)
row = { row = {
"item_code": record.item_code, "item_code": record.item_code,
"item_name": record.item_name, "item_name": item_record.item_name,
"item_group": record.item_group, "item_group": item_record.item_group,
"description": record.description, "description": record.description,
"quantity": record.qty, "quantity": record.qty,
"uom": record.uom, "uom": record.uom,
@ -196,8 +198,8 @@ def get_conditions(filters):
return conditions return conditions
def get_customer_details(): def get_customer_details():
details = frappe.get_all('Customer', details = frappe.get_all("Customer",
fields=['name', 'customer_name', "customer_group"]) fields=["name", "customer_name", "customer_group"])
customer_details = {} customer_details = {}
for d in details: for d in details:
customer_details.setdefault(d.name, frappe._dict({ customer_details.setdefault(d.name, frappe._dict({
@ -206,15 +208,25 @@ def get_customer_details():
})) }))
return customer_details return customer_details
def get_item_details():
details = frappe.db.get_all("Item",
fields=["item_code", "item_name", "item_group"])
item_details = {}
for d in details:
item_details.setdefault(d.item_code, frappe._dict({
"item_name": d.item_name,
"item_group": d.item_group
}))
return item_details
def get_sales_order_details(company_list, filters): def get_sales_order_details(company_list, filters):
conditions = get_conditions(filters) conditions = get_conditions(filters)
return frappe.db.sql(""" return frappe.db.sql("""
SELECT SELECT
so_item.item_code, so_item.item_name, so_item.item_group, so_item.item_code, so_item.description, so_item.qty,
so_item.description, so_item.qty, so_item.uom, so_item.uom, so_item.base_rate, so_item.base_amount,
so_item.base_rate, so_item.base_amount, so.name, so.name, so.transaction_date, so.customer,so.territory,
so.transaction_date, so.customer, so.territory,
so.project, so_item.delivered_qty, so.project, so_item.delivered_qty,
so_item.billed_amt, so.company so_item.billed_amt, so.company
FROM FROM

View File

@ -1,25 +1,5 @@
{ {
"cards": [ "cards": [
{
"hidden": 0,
"label": "Healthcare",
"links": "[\n {\n \"label\": \"Patient\",\n \"name\": \"Patient\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Diagnosis\",\n \"name\": \"Diagnosis\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n }\n]"
},
{
"hidden": 0,
"label": "Agriculture",
"links": "[\n {\n \"label\": \"Crop\",\n \"name\": \"Crop\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Crop Cycle\",\n \"name\": \"Crop Cycle\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Location\",\n \"name\": \"Location\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Fertilizer\",\n \"name\": \"Fertilizer\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n }\n]"
},
{
"hidden": 0,
"label": "Education",
"links": "[\n {\n \"label\": \"Student\",\n \"name\": \"Student\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Course\",\n \"name\": \"Course\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Instructor\",\n \"name\": \"Instructor\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Room\",\n \"name\": \"Room\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n }\n]"
},
{
"hidden": 0,
"label": "Non Profit",
"links": "[\n {\n \"description\": \"Member information.\",\n \"label\": \"Member\",\n \"name\": \"Member\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Volunteer information.\",\n \"label\": \"Volunteer\",\n \"name\": \"Volunteer\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Chapter information.\",\n \"label\": \"Chapter\",\n \"name\": \"Chapter\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Donor information.\",\n \"label\": \"Donor\",\n \"name\": \"Donor\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n }\n]"
},
{ {
"hidden": 0, "hidden": 0,
"label": "Stock", "label": "Stock",
@ -54,10 +34,11 @@
"docstatus": 0, "docstatus": 0,
"doctype": "Desk Page", "doctype": "Desk Page",
"extends_another_page": 0, "extends_another_page": 0,
"hide_custom": 0,
"idx": 0, "idx": 0,
"is_standard": 1, "is_standard": 1,
"label": "Home", "label": "Home",
"modified": "2020-05-11 10:20:37.358701", "modified": "2020-12-07 14:22:38.667767",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Setup", "module": "Setup",
"name": "Home", "name": "Home",
@ -96,4 +77,4 @@
"type": "Page" "type": "Page"
} }
] ]
} }

View File

@ -274,7 +274,8 @@ erpnext.company.setup_queries = function(frm) {
["default_employee_advance_account", {"root_type": "Asset"}], ["default_employee_advance_account", {"root_type": "Asset"}],
["expenses_included_in_asset_valuation", {"account_type": "Expenses Included In Asset Valuation"}], ["expenses_included_in_asset_valuation", {"account_type": "Expenses Included In Asset Valuation"}],
["capital_work_in_progress_account", {"account_type": "Capital Work in Progress"}], ["capital_work_in_progress_account", {"account_type": "Capital Work in Progress"}],
["asset_received_but_not_billed", {"account_type": "Asset Received But Not Billed"}] ["asset_received_but_not_billed", {"account_type": "Asset Received But Not Billed"}],
["unrealized_profit_loss_account", {"root_type": "Liability"}]
], function(i, v) { ], function(i, v) {
erpnext.company.set_custom_query(frm, v); erpnext.company.set_custom_query(frm, v);
}); });

View File

@ -46,10 +46,9 @@
"round_off_account", "round_off_account",
"round_off_cost_center", "round_off_cost_center",
"write_off_account", "write_off_account",
"discount_allowed_account",
"discount_received_account",
"exchange_gain_loss_account", "exchange_gain_loss_account",
"unrealized_exchange_gain_loss_account", "unrealized_exchange_gain_loss_account",
"unrealized_profit_loss_account",
"column_break0", "column_break0",
"allow_account_creation_against_child_company", "allow_account_creation_against_child_company",
"default_payable_account", "default_payable_account",
@ -261,14 +260,14 @@
{ {
"fieldname": "create_chart_of_accounts_based_on", "fieldname": "create_chart_of_accounts_based_on",
"fieldtype": "Select", "fieldtype": "Select",
"label": "Create Chart of Accounts Based on", "label": "Create Chart Of Accounts Based On",
"options": "\nStandard Template\nExisting Company" "options": "\nStandard Template\nExisting Company"
}, },
{ {
"depends_on": "eval:doc.create_chart_of_accounts_based_on===\"Standard Template\"", "depends_on": "eval:doc.create_chart_of_accounts_based_on===\"Standard Template\"",
"fieldname": "chart_of_accounts", "fieldname": "chart_of_accounts",
"fieldtype": "Select", "fieldtype": "Select",
"label": "Chart of Accounts Template", "label": "Chart Of Accounts Template",
"no_copy": 1 "no_copy": 1
}, },
{ {
@ -345,18 +344,6 @@
"label": "Write Off Account", "label": "Write Off Account",
"options": "Account" "options": "Account"
}, },
{
"fieldname": "discount_allowed_account",
"fieldtype": "Link",
"label": "Discount Allowed Account",
"options": "Account"
},
{
"fieldname": "discount_received_account",
"fieldtype": "Link",
"label": "Discount Received Account",
"options": "Account"
},
{ {
"fieldname": "exchange_gain_loss_account", "fieldname": "exchange_gain_loss_account",
"fieldtype": "Link", "fieldtype": "Link",
@ -740,6 +727,12 @@
"fieldtype": "Link", "fieldtype": "Link",
"label": "Default In Transit Warehouse", "label": "Default In Transit Warehouse",
"options": "Warehouse" "options": "Warehouse"
},
{
"fieldname": "unrealized_profit_loss_account",
"fieldtype": "Link",
"label": "Unrealized Profit / Loss Account",
"options": "Account"
} }
], ],
"icon": "fa fa-building", "icon": "fa fa-building",
@ -747,7 +740,7 @@
"image_field": "company_logo", "image_field": "company_logo",
"is_tree": 1, "is_tree": 1,
"links": [], "links": [],
"modified": "2020-08-06 00:38:08.311216", "modified": "2020-12-03 12:27:27.085094",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Setup", "module": "Setup",
"name": "Company", "name": "Company",
@ -808,4 +801,4 @@
"sort_field": "modified", "sort_field": "modified",
"sort_order": "ASC", "sort_order": "ASC",
"track_changes": 1 "track_changes": 1
} }

View File

@ -133,6 +133,7 @@
"per_installed", "per_installed",
"installation_status", "installation_status",
"column_break_89", "column_break_89",
"per_returned",
"excise_page", "excise_page",
"instructions", "instructions",
"subscription_section", "subscription_section",
@ -1099,7 +1100,7 @@
"no_copy": 1, "no_copy": 1,
"oldfieldname": "status", "oldfieldname": "status",
"oldfieldtype": "Select", "oldfieldtype": "Select",
"options": "\nDraft\nTo Bill\nCompleted\nCancelled\nClosed", "options": "\nDraft\nTo Bill\nCompleted\nReturn Issued\nCancelled\nClosed",
"print_hide": 1, "print_hide": 1,
"print_width": "150px", "print_width": "150px",
"read_only": 1, "read_only": 1,
@ -1251,13 +1252,22 @@
"fieldtype": "Link", "fieldtype": "Link",
"label": "Inter Company Reference", "label": "Inter Company Reference",
"options": "Purchase Receipt" "options": "Purchase Receipt"
},
{
"depends_on": "eval:!doc.__islocal",
"fieldname": "per_returned",
"fieldtype": "Percent",
"label": "% Returned",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
} }
], ],
"icon": "fa fa-truck", "icon": "fa fa-truck",
"idx": 146, "idx": 146,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-11-11 14:57:16.388139", "modified": "2020-11-30 12:54:45.407289",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Delivery Note", "name": "Delivery Note",

View File

@ -55,7 +55,7 @@ class DeliveryNote(SellingController):
'no_allowance': 1 'no_allowance': 1
}] }]
if cint(self.is_return): if cint(self.is_return):
self.status_updater.append({ self.status_updater.extend([{
'source_dt': 'Delivery Note Item', 'source_dt': 'Delivery Note Item',
'target_dt': 'Sales Order Item', 'target_dt': 'Sales Order Item',
'join_field': 'so_detail', 'join_field': 'so_detail',
@ -69,7 +69,19 @@ class DeliveryNote(SellingController):
where name=`tabDelivery Note Item`.parent and is_return=1)""", where name=`tabDelivery Note Item`.parent and is_return=1)""",
'second_source_extra_cond': """ and exists (select name from `tabSales Invoice` 'second_source_extra_cond': """ and exists (select name from `tabSales Invoice`
where name=`tabSales Invoice Item`.parent and is_return=1 and update_stock=1)""" where name=`tabSales Invoice Item`.parent and is_return=1 and update_stock=1)"""
}) },
{
'source_dt': 'Delivery Note Item',
'target_dt': 'Delivery Note Item',
'join_field': 'dn_detail',
'target_field': 'returned_qty',
'target_parent_dt': 'Delivery Note',
'target_parent_field': 'per_returned',
'target_ref_field': 'stock_qty',
'source_field': '-1 * stock_qty',
'percent_join_field_parent': 'return_against'
}
])
def before_print(self): def before_print(self):
def toggle_print_hide(meta, fieldname): def toggle_print_hide(meta, fieldname):

View File

@ -6,9 +6,11 @@ frappe.listview_settings['Delivery Note'] = {
return [__("Return"), "darkgrey", "is_return,=,Yes"]; return [__("Return"), "darkgrey", "is_return,=,Yes"];
} else if (doc.status === "Closed") { } else if (doc.status === "Closed") {
return [__("Closed"), "green", "status,=,Closed"]; return [__("Closed"), "green", "status,=,Closed"];
} else if (flt(doc.per_returned, 2) === 100) {
return [__("Return Issued"), "grey", "per_returned,=,100"];
} else if (flt(doc.per_billed, 2) < 100) { } else if (flt(doc.per_billed, 2) < 100) {
return [__("To Bill"), "orange", "per_billed,<,100"]; return [__("To Bill"), "orange", "per_billed,<,100"];
} else if (flt(doc.per_billed, 2) == 100) { } else if (flt(doc.per_billed, 2) === 100) {
return [__("Completed"), "green", "per_billed,=,100"]; return [__("Completed"), "green", "per_billed,=,100"];
} }
}, },

Some files were not shown because too many files have changed in this diff Show More