Merge branch 'cont-temp' of https://github.com/kennethsequeira/erpnext into cont-temp
pull latest
This commit is contained in:
commit
00510e7f12
@ -155,7 +155,8 @@ class OpeningInvoiceCreationTool(Document):
|
||||
"posting_date": row.posting_date,
|
||||
frappe.scrub(row.party_type): row.party,
|
||||
"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()
|
||||
|
@ -7,17 +7,24 @@ import frappe
|
||||
import unittest
|
||||
|
||||
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
|
||||
|
||||
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")
|
||||
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)
|
||||
return doc.make_invoices()
|
||||
|
||||
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)
|
||||
expected_value = {
|
||||
@ -27,6 +34,13 @@ class TestOpeningInvoiceCreationTool(unittest.TestCase):
|
||||
}
|
||||
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"):
|
||||
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])
|
||||
|
||||
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)
|
||||
expected_value = {
|
||||
@ -46,6 +60,32 @@ class TestOpeningInvoiceCreationTool(unittest.TestCase):
|
||||
}
|
||||
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):
|
||||
party = "Customer" if args.get("invoice_type", "Sales") == "Sales" else "Supplier"
|
||||
company = args.get("company", "_Test Company")
|
||||
@ -57,7 +97,7 @@ def get_opening_invoice_creation_dict(**args):
|
||||
{
|
||||
"qty": 1.0,
|
||||
"outstanding_amount": 300,
|
||||
"party": "_Test {0}".format(party),
|
||||
"party": args.get("party_1") or "_Test {0}".format(party),
|
||||
"item_name": "Opening Item",
|
||||
"due_date": "2016-09-10",
|
||||
"posting_date": "2016-09-05",
|
||||
@ -66,7 +106,7 @@ def get_opening_invoice_creation_dict(**args):
|
||||
{
|
||||
"qty": 2.0,
|
||||
"outstanding_amount": 250,
|
||||
"party": "_Test {0} 1".format(party),
|
||||
"party": args.get("party_2") or "_Test {0} 1".format(party),
|
||||
"item_name": "Opening Item",
|
||||
"due_date": "2016-09-10",
|
||||
"posting_date": "2016-09-05",
|
||||
@ -77,3 +117,30 @@ def get_opening_invoice_creation_dict(**args):
|
||||
|
||||
invoice_dict.update(args)
|
||||
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)
|
@ -406,6 +406,7 @@
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.rate_or_discount==\"Rate\"",
|
||||
"fieldname": "rate",
|
||||
"fieldtype": "Currency",
|
||||
@ -469,6 +470,7 @@
|
||||
"options": "UOM"
|
||||
},
|
||||
{
|
||||
"description": "If rate is zero them item will be treated as \"Free Item\"",
|
||||
"fieldname": "free_item_rate",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Rate"
|
||||
@ -563,7 +565,7 @@
|
||||
"icon": "fa fa-gift",
|
||||
"idx": 1,
|
||||
"links": [],
|
||||
"modified": "2020-10-28 16:53:14.416172",
|
||||
"modified": "2020-12-04 00:36:24.698219",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Pricing Rule",
|
||||
|
@ -521,6 +521,22 @@ class TestPricingRule(unittest.TestCase):
|
||||
frappe.get_doc("Item Price", {"item_code": "Water Flask"}).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):
|
||||
args = frappe._dict(args)
|
||||
|
||||
@ -539,17 +555,20 @@ def make_pricing_rule(**args):
|
||||
"rate_or_discount": args.rate_or_discount or "Discount Percentage",
|
||||
"discount_percentage": args.discount_percentage 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,
|
||||
"condition": args.condition or '',
|
||||
"apply_multiple_pricing_rules": args.apply_multiple_pricing_rules or 0
|
||||
})
|
||||
|
||||
if args.get("priority"):
|
||||
doc.priority = args.get("priority")
|
||||
for field in ["free_item", "free_qty", "free_item_rate", "priority",
|
||||
"margin_type", "price_or_product_discount"]:
|
||||
if args.get(field):
|
||||
doc.set(field, args.get(field))
|
||||
|
||||
apply_on = doc.apply_on.replace(' ', '_').lower()
|
||||
child_table = {'Item Code': 'items', 'Item Group': 'item_groups', 'Brand': 'brands'}
|
||||
|
||||
if doc.apply_on != "Transaction":
|
||||
doc.append(child_table.get(doc.apply_on), {
|
||||
apply_on: args.get(apply_on) or "_Test Item"
|
||||
})
|
||||
|
@ -457,6 +457,9 @@ def apply_pricing_rule_on_transaction(doc):
|
||||
pricing_rules = filter_pricing_rules_for_qty_amount(doc.total_qty,
|
||||
doc.total, pricing_rules)
|
||||
|
||||
if not pricing_rules:
|
||||
remove_free_item(doc)
|
||||
|
||||
for d in pricing_rules:
|
||||
if d.price_or_product_discount == 'Price':
|
||||
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)
|
||||
apply_pricing_rule_for_free_items(doc, item_details.free_item_data)
|
||||
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):
|
||||
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):
|
||||
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
|
||||
|
||||
if not free_item:
|
||||
|
@ -21,7 +21,7 @@ class TestProcessDeferredAccounting(unittest.TestCase):
|
||||
item.no_of_months = 12
|
||||
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].service_start_date = "2019-01-10"
|
||||
si.items[0].service_end_date = "2019-03-15"
|
||||
|
@ -147,6 +147,11 @@ class PurchaseInvoice(BuyingController):
|
||||
throw(_("Conversion rate cannot be 0 or 1"))
|
||||
|
||||
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_type", "report_type", "account_currency"], as_dict=True)
|
||||
|
||||
@ -1032,7 +1037,9 @@ class PurchaseInvoice(BuyingController):
|
||||
updated_pr += update_billed_amount_based_on_po(d.po_detail, update_modified)
|
||||
|
||||
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):
|
||||
self.due_date = None
|
||||
|
@ -472,6 +472,11 @@ class SalesInvoice(SellingController):
|
||||
return frappe.db.sql("select abbr from tabCompany where name=%s", self.company)[0][0]
|
||||
|
||||
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_type", "report_type", "account_currency"], as_dict=True)
|
||||
|
||||
|
@ -14,11 +14,93 @@ def execute(filters=None):
|
||||
|
||||
def get_column():
|
||||
return [
|
||||
_("Delivery Note") + ":Link/Delivery Note:120", _("Status") + "::120", _("Date") + ":Date:100",
|
||||
_("Suplier") + ":Link/Customer:120", _("Customer Name") + "::120",
|
||||
_("Project") + ":Link/Project:120", _("Item Code") + ":Link/Item:120",
|
||||
_("Amount") + ":Currency:100", _("Billed Amount") + ":Currency:100", _("Pending Amount") + ":Currency:100",
|
||||
_("Item Name") + "::120", _("Description") + "::120", _("Company") + ":Link/Company:120",
|
||||
{
|
||||
"label": _("Delivery Note"),
|
||||
"fieldname": "name",
|
||||
"fieldtype": "Link",
|
||||
"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():
|
||||
|
@ -17,18 +17,26 @@ def get_ordered_to_be_billed_data(args):
|
||||
|
||||
return frappe.db.sql("""
|
||||
Select
|
||||
`{parent_tab}`.name, `{parent_tab}`.status, `{parent_tab}`.{date_field}, `{parent_tab}`.{party}, `{parent_tab}`.{party}_name,
|
||||
{project_field}, `{child_tab}`.item_code, `{child_tab}`.base_amount,
|
||||
`{parent_tab}`.name, `{parent_tab}`.{date_field},
|
||||
`{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}`.base_amount - (`{child_tab}`.billed_amt * ifnull(`{parent_tab}`.conversion_rate, 1))),
|
||||
`{child_tab}`.item_name, `{child_tab}`.description, `{parent_tab}`.company
|
||||
(`{child_tab}`.base_rate * ifnull(`{child_tab}`.returned_qty, 0)),
|
||||
(`{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
|
||||
`{parent_tab}`, `{child_tab}`
|
||||
where
|
||||
`{parent_tab}`.name = `{child_tab}`.parent and `{parent_tab}`.docstatus = 1
|
||||
and `{parent_tab}`.status not in ('Closed', 'Completed')
|
||||
and `{child_tab}`.amount > 0 and round(`{child_tab}`.billed_amt *
|
||||
ifnull(`{parent_tab}`.conversion_rate, 1), {precision}) < `{child_tab}`.base_amount
|
||||
and `{child_tab}`.amount > 0
|
||||
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
|
||||
`{parent_tab}`.{order} {order_by}
|
||||
""".format(parent_tab = 'tab' + doctype, child_tab = 'tab' + child_tab, precision= precision, party = party,
|
||||
|
@ -14,11 +14,93 @@ def execute(filters=None):
|
||||
|
||||
def get_column():
|
||||
return [
|
||||
_("Purchase Receipt") + ":Link/Purchase Receipt:120", _("Status") + "::120", _("Date") + ":Date:100",
|
||||
_("Supplier") + ":Link/Supplier:120", _("Supplier Name") + "::120",
|
||||
_("Project") + ":Link/Project:120", _("Item Code") + ":Link/Item:120",
|
||||
_("Amount") + ":Currency:100", _("Billed Amount") + ":Currency:100", _("Amount to Bill") + ":Currency:100",
|
||||
_("Item Name") + "::120", _("Description") + "::120", _("Company") + ":Link/Company:120",
|
||||
{
|
||||
"label": _("Purchase Receipt"),
|
||||
"fieldname": "name",
|
||||
"fieldtype": "Link",
|
||||
"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():
|
||||
|
@ -290,11 +290,17 @@ erpnext.buying.RequestforQuotationController = erpnext.buying.BuyingController.e
|
||||
dialog.show();
|
||||
}, __("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
|
||||
this.frm.add_custom_button(__('Get Suppliers'),
|
||||
function() {
|
||||
me.get_suppliers_button(me.frm);
|
||||
});
|
||||
}, __("Tools"));
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -18,7 +18,6 @@
|
||||
"suppliers",
|
||||
"items_section",
|
||||
"items",
|
||||
"link_to_mrs",
|
||||
"supplier_response_section",
|
||||
"salutation",
|
||||
"subject",
|
||||
@ -118,13 +117,6 @@
|
||||
"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",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Email Details"
|
||||
@ -260,7 +252,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-11-04 22:04:29.017134",
|
||||
"modified": "2020-11-05 22:04:29.017134",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Request for Quotation",
|
||||
|
@ -50,6 +50,12 @@ erpnext.buying.SupplierQuotationController = erpnext.buying.BuyingController.ext
|
||||
})
|
||||
}, __("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"),
|
||||
function() {
|
||||
if (!me.frm.doc.supplier) {
|
||||
|
@ -35,7 +35,6 @@
|
||||
"ignore_pricing_rule",
|
||||
"items_section",
|
||||
"items",
|
||||
"link_to_mrs",
|
||||
"pricing_rule_details",
|
||||
"pricing_rules",
|
||||
"section_break_22",
|
||||
@ -322,12 +321,6 @@
|
||||
"options": "Supplier Quotation Item",
|
||||
"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",
|
||||
"fieldtype": "Section Break",
|
||||
@ -806,9 +799,10 @@
|
||||
],
|
||||
"icon": "fa fa-shopping-cart",
|
||||
"idx": 29,
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-10-30 13:58:33.043971",
|
||||
"modified": "2020-12-03 15:18:29.073368",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Supplier Quotation",
|
||||
|
@ -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.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")
|
||||
|
||||
class AccountsController(TransactionBase):
|
||||
@ -735,6 +737,21 @@ class AccountsController(TransactionBase):
|
||||
|
||||
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):
|
||||
party_type, party = self.get_party()
|
||||
validate_party_frozen_disabled(party_type, party)
|
||||
|
@ -497,6 +497,10 @@ class BuyingController(StockController):
|
||||
frappe.throw(_("Row {0}: Conversion Factor is mandatory").format(d.idx))
|
||||
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):
|
||||
for d in self.get("items"):
|
||||
if self.is_return and flt(d.rejected_qty) != 0:
|
||||
|
@ -203,10 +203,37 @@ def get_already_returned_items(doc):
|
||||
|
||||
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):
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
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")
|
||||
|
||||
def set_missing_values(source, target):
|
||||
doc = frappe.get_doc(target)
|
||||
doc.is_return = 1
|
||||
@ -262,11 +289,16 @@ def make_return_doc(doctype, source_name, target_doc=None):
|
||||
|
||||
def update_item(source_doc, target_doc, source_parent):
|
||||
target_doc.qty = -1 * source_doc.qty
|
||||
|
||||
if doctype == "Purchase Receipt":
|
||||
target_doc.received_qty = -1* source_doc.received_qty
|
||||
target_doc.rejected_qty = -1* source_doc.rejected_qty
|
||||
target_doc.qty = -1* source_doc.qty
|
||||
target_doc.stock_qty = -1 * source_doc.stock_qty
|
||||
returned_qty_map = get_returned_qty_map_for_row(source_doc.name, doctype)
|
||||
target_doc.received_qty = -1 * flt(source_doc.received_qty - (returned_qty_map.get('received_qty') or 0))
|
||||
target_doc.rejected_qty = -1 * flt(source_doc.rejected_qty - (returned_qty_map.get('rejected_qty') or 0))
|
||||
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_item = source_doc.purchase_order_item
|
||||
target_doc.rejected_warehouse = source_doc.rejected_warehouse
|
||||
@ -285,6 +317,10 @@ def make_return_doc(doctype, source_name, target_doc=None):
|
||||
target_doc.purchase_invoice_item = source_doc.name
|
||||
|
||||
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_invoice = source_doc.against_sales_invoice
|
||||
target_doc.so_detail = source_doc.so_detail
|
||||
|
@ -58,6 +58,7 @@ status_map = {
|
||||
"Delivery Note": [
|
||||
["Draft", None],
|
||||
["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"],
|
||||
["Cancelled", "eval:self.docstatus==2"],
|
||||
["Closed", "eval:self.status=='Closed'"],
|
||||
@ -65,6 +66,7 @@ status_map = {
|
||||
"Purchase Receipt": [
|
||||
["Draft", None],
|
||||
["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"],
|
||||
["Cancelled", "eval:self.docstatus==2"],
|
||||
["Closed", "eval:self.status=='Closed'"],
|
||||
@ -232,7 +234,7 @@ class StatusUpdater(Document):
|
||||
|
||||
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)
|
||||
|
||||
def _update_children(self, args, update_modified):
|
||||
@ -272,6 +274,12 @@ class StatusUpdater(Document):
|
||||
|
||||
def _update_percent_field_in_targets(self, args, update_modified=True):
|
||||
"""Update percent field in parent transaction"""
|
||||
if args.get('percent_join_field_parent'):
|
||||
# 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'])])
|
||||
|
||||
|
@ -340,11 +340,15 @@ class StockController(AccountsController):
|
||||
validate_warehouse_company(w, self.company)
|
||||
|
||||
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({
|
||||
"target_dt": self.doctype + " Item",
|
||||
"target_parent_dt": self.doctype,
|
||||
"target_parent_field": "per_billed",
|
||||
"target_ref_field": "amount",
|
||||
"target_ref_field": target_ref_field,
|
||||
"target_field": "billed_amt",
|
||||
"name": self.name,
|
||||
}, update_modified)
|
||||
|
@ -691,6 +691,7 @@ erpnext.patches.v13_0.update_old_loans
|
||||
erpnext.patches.v12_0.set_serial_no_status #2020-05-21
|
||||
erpnext.patches.v12_0.update_price_list_currency_in_bom
|
||||
execute:frappe.reload_doctype('Dashboard')
|
||||
execute:frappe.reload_doc('desk', 'doctype', 'number_card_link')
|
||||
execute:frappe.delete_doc_if_exists('Dashboard', 'Accounts')
|
||||
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
|
||||
@ -738,3 +739,4 @@ erpnext.patches.v13_0.update_custom_fields_for_shopify
|
||||
execute:frappe.delete_doc("Report", "Quoted Item Comparison")
|
||||
erpnext.patches.v13_0.updates_for_multi_currency_payroll
|
||||
erpnext.patches.v13_0.create_leave_policy_assignment_based_on_employee_current_leave_policy
|
||||
erpnext.patches.v13_0.update_returned_qty_in_pr_dn
|
||||
|
@ -52,6 +52,8 @@ def create_assignment(employee, leave_policy, leave_period=None, allocation_exis
|
||||
if leave_period:
|
||||
filters["leave_period"] = leave_period
|
||||
|
||||
frappe.reload_doc('hr', 'doctype', 'leave_policy_assignment')
|
||||
|
||||
if not frappe.db.exists("Leave Policy Assignment" , filters):
|
||||
lpa = frappe.new_doc("Leave Policy Assignment")
|
||||
lpa.employee = employee
|
||||
|
27
erpnext/patches/v13_0/update_returned_qty_in_pr_dn.py
Normal file
27
erpnext/patches/v13_0/update_returned_qty_in_pr_dn.py
Normal 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)
|
@ -7,6 +7,10 @@ import frappe
|
||||
def execute():
|
||||
parent_list = []
|
||||
count = 0
|
||||
|
||||
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,
|
||||
|
@ -189,6 +189,7 @@ erpnext.buying.BuyingController = erpnext.TransactionController.extend({
|
||||
|
||||
frappe.model.round_floats_in(item, ["qty", "received_qty"]);
|
||||
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);
|
||||
@ -293,69 +294,6 @@ erpnext.buying.BuyingController = erpnext.TransactionController.extend({
|
||||
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) {
|
||||
if (doc.auto_repeat) {
|
||||
frappe.call({
|
||||
@ -421,6 +359,62 @@ erpnext.buying.BuyingController = erpnext.TransactionController.extend({
|
||||
|
||||
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) {
|
||||
$.each(frm.doc["items"] || [], function(i, d) {
|
||||
if (d.item_code && d.bom === "") {
|
||||
|
@ -192,19 +192,20 @@ class GSTR3BReport(Document):
|
||||
for d in self.report_dict["itc_elg"]["itc_avl"]:
|
||||
|
||||
itc_type = itc_type_map.get(d["ty"])
|
||||
gst_category = ["Registered Regular"]
|
||||
|
||||
if d["ty"] == 'ISRC':
|
||||
reverse_charge = "Y"
|
||||
reverse_charge = ["Y"]
|
||||
itc_type = 'All Other ITC'
|
||||
gst_category = ['Unregistered', 'Overseas']
|
||||
else:
|
||||
reverse_charge = "N"
|
||||
gst_category = ['Unregistered', 'Overseas', 'Registered Regular']
|
||||
reverse_charge = ["N", "Y"]
|
||||
|
||||
for account_head in self.account_heads:
|
||||
for category in gst_category:
|
||||
for charge_type in reverse_charge:
|
||||
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, reverse_charge, account_head.get(key[1])), {}).get("amount"), 2)
|
||||
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']:
|
||||
net_itc[key] += flt(d[key], 2)
|
||||
@ -264,7 +265,8 @@ class GSTR3BReport(Document):
|
||||
|
||||
def get_itc_details(self):
|
||||
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
|
||||
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
|
||||
@ -387,7 +389,7 @@ class GSTR3BReport(Document):
|
||||
tax_template = 'Purchase Taxes and Charges'
|
||||
|
||||
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
|
||||
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
|
||||
|
@ -19,6 +19,7 @@ erpnext.setup_auto_gst_taxation = (doctype) => {
|
||||
'shipping_address': frm.doc.shipping_address || '',
|
||||
'shipping_address_name': frm.doc.shipping_address_name || '',
|
||||
'customer_address': frm.doc.customer_address || '',
|
||||
'supplier_address': frm.doc.supplier_address,
|
||||
'customer': frm.doc.customer,
|
||||
'supplier': frm.doc.supplier,
|
||||
'supplier_gstin': frm.doc.supplier_gstin,
|
||||
|
@ -12,6 +12,7 @@ from erpnext.regional.india import number_state_mapping
|
||||
from six import string_types
|
||||
from erpnext.accounts.general_ledger import make_gl_entries
|
||||
from erpnext.accounts.utils import get_account_currency
|
||||
from frappe.model.utils import get_fetch_values
|
||||
|
||||
def validate_gstin_for_india(doc, method):
|
||||
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 = frappe._dict(party_details)
|
||||
|
||||
update_party_details(party_details, doctype)
|
||||
|
||||
party_details.place_of_supply = get_place_of_supply(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
|
||||
|
||||
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):
|
||||
if doctype in ("Sales Invoice", "Delivery Note", "Sales Order"):
|
||||
destination_gstin = party_details.company_gstin
|
||||
|
@ -133,6 +133,7 @@
|
||||
"per_installed",
|
||||
"installation_status",
|
||||
"column_break_89",
|
||||
"per_returned",
|
||||
"excise_page",
|
||||
"instructions",
|
||||
"subscription_section",
|
||||
@ -1099,7 +1100,7 @@
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "status",
|
||||
"oldfieldtype": "Select",
|
||||
"options": "\nDraft\nTo Bill\nCompleted\nCancelled\nClosed",
|
||||
"options": "\nDraft\nTo Bill\nCompleted\nReturn Issued\nCancelled\nClosed",
|
||||
"print_hide": 1,
|
||||
"print_width": "150px",
|
||||
"read_only": 1,
|
||||
@ -1251,13 +1252,22 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "Inter Company Reference",
|
||||
"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",
|
||||
"idx": 146,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-11-11 14:57:16.388139",
|
||||
"modified": "2020-11-30 12:54:45.407289",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Delivery Note",
|
||||
|
@ -55,7 +55,7 @@ class DeliveryNote(SellingController):
|
||||
'no_allowance': 1
|
||||
}]
|
||||
if cint(self.is_return):
|
||||
self.status_updater.append({
|
||||
self.status_updater.extend([{
|
||||
'source_dt': 'Delivery Note Item',
|
||||
'target_dt': 'Sales Order Item',
|
||||
'join_field': 'so_detail',
|
||||
@ -69,7 +69,19 @@ class DeliveryNote(SellingController):
|
||||
where name=`tabDelivery Note Item`.parent and is_return=1)""",
|
||||
'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)"""
|
||||
})
|
||||
},
|
||||
{
|
||||
'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 toggle_print_hide(meta, fieldname):
|
||||
|
@ -6,9 +6,11 @@ frappe.listview_settings['Delivery Note'] = {
|
||||
return [__("Return"), "darkgrey", "is_return,=,Yes"];
|
||||
} else if (doc.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) {
|
||||
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"];
|
||||
}
|
||||
},
|
||||
|
@ -206,7 +206,7 @@ class TestDeliveryNote(unittest.TestCase):
|
||||
for field, value in field_values.items():
|
||||
self.assertEqual(cstr(serial_no.get(field)), value)
|
||||
|
||||
def test_sales_return_for_non_bundled_items(self):
|
||||
def test_sales_return_for_non_bundled_items_partial(self):
|
||||
company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company')
|
||||
|
||||
make_stock_entry(item_code="_Test Item", target="Stores - TCP1", qty=50, basic_rate=100)
|
||||
@ -225,7 +225,10 @@ class TestDeliveryNote(unittest.TestCase):
|
||||
|
||||
# return entry
|
||||
dn1 = create_delivery_note(is_return=1, return_against=dn.name, qty=-2, rate=500,
|
||||
company=company, warehouse="Stores - TCP1", expense_account="Cost of Goods Sold - TCP1", cost_center="Main - TCP1")
|
||||
company=company, warehouse="Stores - TCP1", expense_account="Cost of Goods Sold - TCP1",
|
||||
cost_center="Main - TCP1", do_not_submit=1)
|
||||
dn1.items[0].dn_detail = dn.items[0].name
|
||||
dn1.submit()
|
||||
|
||||
actual_qty_2 = get_qty_after_transaction(warehouse="Stores - TCP1")
|
||||
|
||||
@ -243,6 +246,70 @@ class TestDeliveryNote(unittest.TestCase):
|
||||
|
||||
self.assertEqual(gle_warehouse_amount, stock_value_difference)
|
||||
|
||||
# hack because new_doc isn't considering is_return portion of status_updater
|
||||
returned = frappe.get_doc("Delivery Note", dn1.name)
|
||||
returned.update_prevdoc_status()
|
||||
dn.load_from_db()
|
||||
|
||||
# Check if Original DN updated
|
||||
self.assertEqual(dn.items[0].returned_qty, 2)
|
||||
self.assertEqual(dn.per_returned, 40)
|
||||
|
||||
from erpnext.controllers.sales_and_purchase_return import make_return_doc
|
||||
return_dn_2 = make_return_doc("Delivery Note", dn.name)
|
||||
|
||||
# Check if unreturned amount is mapped in 2nd return
|
||||
self.assertEqual(return_dn_2.items[0].qty, -3)
|
||||
|
||||
si = make_sales_invoice(dn.name)
|
||||
si.submit()
|
||||
|
||||
self.assertEqual(si.items[0].qty, 3)
|
||||
|
||||
dn.load_from_db()
|
||||
# DN should be completed on billing all unreturned amount
|
||||
self.assertEqual(dn.items[0].billed_amt, 1500)
|
||||
self.assertEqual(dn.per_billed, 100)
|
||||
self.assertEqual(dn.status, 'Completed')
|
||||
|
||||
si.load_from_db()
|
||||
si.cancel()
|
||||
|
||||
dn.load_from_db()
|
||||
self.assertEqual(dn.per_billed, 0)
|
||||
|
||||
dn1.cancel()
|
||||
dn.cancel()
|
||||
|
||||
def test_sales_return_for_non_bundled_items_full(self):
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
|
||||
company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company')
|
||||
|
||||
make_item("Box", {'is_stock_item': 1})
|
||||
|
||||
make_stock_entry(item_code="Box", target="Stores - TCP1", qty=10, basic_rate=100)
|
||||
|
||||
dn = create_delivery_note(item_code="Box", qty=5, rate=500, warehouse="Stores - TCP1", company=company,
|
||||
expense_account="Cost of Goods Sold - TCP1", cost_center="Main - TCP1")
|
||||
|
||||
#return entry
|
||||
dn1 = create_delivery_note(item_code="Box", is_return=1, return_against=dn.name, qty=-5, rate=500,
|
||||
company=company, warehouse="Stores - TCP1", expense_account="Cost of Goods Sold - TCP1",
|
||||
cost_center="Main - TCP1", do_not_submit=1)
|
||||
dn1.items[0].dn_detail = dn.items[0].name
|
||||
dn1.submit()
|
||||
|
||||
# hack because new_doc isn't considering is_return portion of status_updater
|
||||
returned = frappe.get_doc("Delivery Note", dn1.name)
|
||||
returned.update_prevdoc_status()
|
||||
dn.load_from_db()
|
||||
|
||||
# Check if Original DN updated
|
||||
self.assertEqual(dn.items[0].returned_qty, 5)
|
||||
self.assertEqual(dn.per_returned, 100)
|
||||
self.assertEqual(dn.status, 'Return Issued')
|
||||
|
||||
def test_return_single_item_from_bundled_items(self):
|
||||
company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company')
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
{
|
||||
"actions": [],
|
||||
"autoname": "hash",
|
||||
"creation": "2013-04-22 13:15:44",
|
||||
"doctype": "DocType",
|
||||
@ -24,7 +25,10 @@
|
||||
"col_break2",
|
||||
"uom",
|
||||
"conversion_factor",
|
||||
"stock_qty_sec_break",
|
||||
"stock_qty",
|
||||
"stock_qty_col_break",
|
||||
"returned_qty",
|
||||
"section_break_17",
|
||||
"price_list_rate",
|
||||
"base_price_list_rate",
|
||||
@ -211,7 +215,7 @@
|
||||
{
|
||||
"fieldname": "stock_qty",
|
||||
"fieldtype": "Float",
|
||||
"label": "Qty as per Stock UOM",
|
||||
"label": "Qty in Stock UOM",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
@ -715,12 +719,29 @@
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "stock_qty_sec_break",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "stock_qty_col_break",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "returned_qty",
|
||||
"fieldname": "returned_qty",
|
||||
"fieldtype": "Float",
|
||||
"label": "Returned Qty in Stock UOM",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-07-20 12:25:06.177894",
|
||||
"modified": "2020-07-31 20:12:43.054342",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Delivery Note Item",
|
||||
|
@ -111,6 +111,7 @@
|
||||
"range",
|
||||
"column_break4",
|
||||
"per_billed",
|
||||
"per_returned",
|
||||
"is_internal_supplier",
|
||||
"inter_company_reference",
|
||||
"subscription_detail",
|
||||
@ -895,7 +896,7 @@
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "status",
|
||||
"oldfieldtype": "Select",
|
||||
"options": "\nDraft\nTo Bill\nCompleted\nCancelled\nClosed",
|
||||
"options": "\nDraft\nTo Bill\nCompleted\nReturn Issued\nCancelled\nClosed",
|
||||
"print_hide": 1,
|
||||
"print_width": "150px",
|
||||
"read_only": 1,
|
||||
@ -1104,13 +1105,22 @@
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Billing Address",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"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",
|
||||
"idx": 261,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-10-30 14:00:08.347534",
|
||||
"modified": "2020-11-30 12:54:23.278500",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Purchase Receipt",
|
||||
|
@ -55,7 +55,8 @@ class PurchaseReceipt(BuyingController):
|
||||
'percent_join_field': 'material_request'
|
||||
}]
|
||||
if cint(self.is_return):
|
||||
self.status_updater.append({
|
||||
self.status_updater.extend([
|
||||
{
|
||||
'source_dt': 'Purchase Receipt Item',
|
||||
'target_dt': 'Purchase Order Item',
|
||||
'join_field': 'purchase_order_item',
|
||||
@ -68,7 +69,19 @@ class PurchaseReceipt(BuyingController):
|
||||
where name=`tabPurchase Receipt Item`.parent and is_return=1)""",
|
||||
'second_source_extra_cond': """ and exists (select name from `tabPurchase Invoice`
|
||||
where name=`tabPurchase Invoice Item`.parent and is_return=1 and update_stock=1)"""
|
||||
})
|
||||
},
|
||||
{
|
||||
'source_dt': 'Purchase Receipt Item',
|
||||
'target_dt': 'Purchase Receipt Item',
|
||||
'join_field': 'purchase_receipt_item',
|
||||
'target_field': 'returned_qty',
|
||||
'target_parent_dt': 'Purchase Receipt',
|
||||
'target_parent_field': 'per_returned',
|
||||
'target_ref_field': 'received_stock_qty',
|
||||
'source_field': '-1 * received_stock_qty',
|
||||
'percent_join_field_parent': 'return_against'
|
||||
}
|
||||
])
|
||||
|
||||
def validate(self):
|
||||
self.validate_posting_time()
|
||||
@ -490,7 +503,7 @@ class PurchaseReceipt(BuyingController):
|
||||
|
||||
for pr in set(updated_pr):
|
||||
pr_doc = self if (pr == self.name) else frappe.get_doc("Purchase Receipt", pr)
|
||||
pr_doc.update_billing_percentage(update_modified=update_modified)
|
||||
update_billing_percentage(pr_doc, update_modified=update_modified)
|
||||
|
||||
self.load_from_db()
|
||||
|
||||
@ -500,7 +513,7 @@ def update_billed_amount_based_on_po(po_detail, update_modified=True):
|
||||
where po_detail=%s and (pr_detail is null or pr_detail = '') and docstatus=1""", po_detail)
|
||||
billed_against_po = billed_against_po and billed_against_po[0][0] or 0
|
||||
|
||||
# Get all Delivery Note Item rows against the Sales Order Item row
|
||||
# Get all Purchase Receipt Item rows against the Purchase Order Item row
|
||||
pr_details = frappe.db.sql("""select pr_item.name, pr_item.amount, pr_item.parent
|
||||
from `tabPurchase Receipt Item` pr_item, `tabPurchase Receipt` pr
|
||||
where pr.name=pr_item.parent and pr_item.purchase_order_item=%s
|
||||
@ -530,6 +543,39 @@ def update_billed_amount_based_on_po(po_detail, update_modified=True):
|
||||
|
||||
return updated_pr
|
||||
|
||||
def update_billing_percentage(pr_doc, update_modified=True):
|
||||
# Reload as billed amount was set in db directly
|
||||
pr_doc.load_from_db()
|
||||
|
||||
# Update Billing % based on pending accepted qty
|
||||
total_amount, total_billed_amount = 0, 0
|
||||
for item in pr_doc.items:
|
||||
return_data = frappe.db.get_list("Purchase Receipt",
|
||||
fields = [
|
||||
"sum(abs(`tabPurchase Receipt Item`.qty)) as qty"
|
||||
],
|
||||
filters = [
|
||||
["Purchase Receipt", "docstatus", "=", 1],
|
||||
["Purchase Receipt", "is_return", "=", 1],
|
||||
["Purchase Receipt Item", "purchase_receipt_item", "=", item.name]
|
||||
])
|
||||
|
||||
returned_qty = return_data[0].qty if return_data else 0
|
||||
returned_amount = flt(returned_qty) * flt(item.rate)
|
||||
pending_amount = flt(item.amount) - returned_amount
|
||||
total_billable_amount = pending_amount if item.billed_amt <= pending_amount else item.billed_amt
|
||||
|
||||
total_amount += total_billable_amount
|
||||
total_billed_amount += flt(item.billed_amt)
|
||||
|
||||
percent_billed = round(100 * (total_billed_amount / (total_amount or 1)), 6)
|
||||
pr_doc.db_set("per_billed", percent_billed)
|
||||
pr_doc.load_from_db()
|
||||
|
||||
if update_modified:
|
||||
pr_doc.set_status(update=True)
|
||||
pr_doc.notify_update()
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_purchase_invoice(source_name, target_doc=None):
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
@ -552,6 +598,7 @@ def make_purchase_invoice(source_name, target_doc=None):
|
||||
|
||||
def update_item(source_doc, target_doc, source_parent):
|
||||
target_doc.qty, returned_qty = get_pending_qty(source_doc)
|
||||
target_doc.stock_qty = flt(target_doc.qty) * flt(target_doc.conversion_factor, target_doc.precision("conversion_factor"))
|
||||
returned_qty_map[source_doc.name] = returned_qty
|
||||
|
||||
def get_pending_qty(item_row):
|
||||
|
@ -6,9 +6,11 @@ frappe.listview_settings['Purchase Receipt'] = {
|
||||
return [__("Return"), "darkgrey", "is_return,=,Yes"];
|
||||
} else if (doc.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.grand_total) !== 0 && flt(doc.per_billed, 2) < 100) {
|
||||
return [__("To Bill"), "orange", "per_billed,<,100"];
|
||||
} else if (flt(doc.grand_total) === 0 || flt(doc.per_billed, 2) == 100) {
|
||||
} else if (flt(doc.grand_total) === 0 || flt(doc.per_billed, 2) === 100) {
|
||||
return [__("Completed"), "green", "per_billed,=,100"];
|
||||
}
|
||||
}
|
||||
|
@ -137,7 +137,10 @@ class TestPurchaseReceipt(unittest.TestCase):
|
||||
self.assertFalse(frappe.db.get_all('Serial No', {'batch_no': batch_no}))
|
||||
|
||||
def test_purchase_receipt_gl_entry(self):
|
||||
pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1", get_multiple_items = True, get_taxes_and_charges = True)
|
||||
pr = make_purchase_receipt(company="_Test Company with perpetual inventory",
|
||||
warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1",
|
||||
get_multiple_items = True, get_taxes_and_charges = True)
|
||||
|
||||
self.assertEqual(cint(erpnext.is_perpetual_inventory_enabled(pr.company)), 1)
|
||||
|
||||
gl_entries = get_gl_entries("Purchase Receipt", pr.name)
|
||||
@ -281,11 +284,15 @@ class TestPurchaseReceipt(unittest.TestCase):
|
||||
self.assertEqual(frappe.db.get_value("Serial No", serial_no, "warehouse"),
|
||||
pr.get("items")[0].rejected_warehouse)
|
||||
|
||||
def test_purchase_return(self):
|
||||
def test_purchase_return_partial(self):
|
||||
pr = make_purchase_receipt(company="_Test Company with perpetual inventory",
|
||||
warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1")
|
||||
|
||||
pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1")
|
||||
|
||||
return_pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1", is_return=1, return_against=pr.name, qty=-2)
|
||||
return_pr = make_purchase_receipt(company="_Test Company with perpetual inventory",
|
||||
warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1",
|
||||
is_return=1, return_against=pr.name, qty=-2, do_not_submit=1)
|
||||
return_pr.items[0].purchase_receipt_item = pr.items[0].name
|
||||
return_pr.submit()
|
||||
|
||||
# check sle
|
||||
outgoing_rate = frappe.db.get_value("Stock Ledger Entry", {"voucher_type": "Purchase Receipt",
|
||||
@ -309,6 +316,60 @@ class TestPurchaseReceipt(unittest.TestCase):
|
||||
self.assertEqual(expected_values[gle.account][0], gle.debit)
|
||||
self.assertEqual(expected_values[gle.account][1], gle.credit)
|
||||
|
||||
# hack because new_doc isn't considering is_return portion of status_updater
|
||||
returned = frappe.get_doc("Purchase Receipt", return_pr.name)
|
||||
returned.update_prevdoc_status()
|
||||
pr.load_from_db()
|
||||
|
||||
# Check if Original PR updated
|
||||
self.assertEqual(pr.items[0].returned_qty, 2)
|
||||
self.assertEqual(pr.per_returned, 40)
|
||||
|
||||
from erpnext.controllers.sales_and_purchase_return import make_return_doc
|
||||
return_pr_2 = make_return_doc("Purchase Receipt", pr.name)
|
||||
|
||||
# Check if unreturned amount is mapped in 2nd return
|
||||
self.assertEqual(return_pr_2.items[0].qty, -3)
|
||||
|
||||
# Make PI against unreturned amount
|
||||
pi = make_purchase_invoice(pr.name)
|
||||
pi.submit()
|
||||
|
||||
self.assertEqual(pi.items[0].qty, 3)
|
||||
|
||||
pr.load_from_db()
|
||||
# PR should be completed on billing all unreturned amount
|
||||
self.assertEqual(pr.items[0].billed_amt, 150)
|
||||
self.assertEqual(pr.per_billed, 100)
|
||||
self.assertEqual(pr.status, 'Completed')
|
||||
|
||||
pi.load_from_db()
|
||||
pi.cancel()
|
||||
|
||||
pr.load_from_db()
|
||||
self.assertEqual(pr.per_billed, 0)
|
||||
|
||||
return_pr.cancel()
|
||||
pr.cancel()
|
||||
|
||||
def test_purchase_return_full(self):
|
||||
pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1",
|
||||
supplier_warehouse = "Work in Progress - TCP1")
|
||||
|
||||
return_pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1",
|
||||
supplier_warehouse = "Work in Progress - TCP1", is_return=1, return_against=pr.name, qty=-5, do_not_submit=1)
|
||||
return_pr.items[0].purchase_receipt_item = pr.items[0].name
|
||||
return_pr.submit()
|
||||
|
||||
# hack because new_doc isn't considering is_return portion of status_updater
|
||||
returned = frappe.get_doc("Purchase Receipt", return_pr.name)
|
||||
returned.update_prevdoc_status()
|
||||
pr.load_from_db()
|
||||
|
||||
# Check if Original PR updated
|
||||
self.assertEqual(pr.items[0].returned_qty, 5)
|
||||
self.assertEqual(pr.per_returned, 100)
|
||||
self.assertEqual(pr.status, 'Return Issued')
|
||||
|
||||
def test_purchase_return_for_rejected_qty(self):
|
||||
from erpnext.stock.doctype.warehouse.test_warehouse import get_warehouse
|
||||
@ -416,6 +477,7 @@ class TestPurchaseReceipt(unittest.TestCase):
|
||||
self.assertEqual(pr1.per_billed, 100)
|
||||
self.assertEqual(pr1.status, "Completed")
|
||||
|
||||
pr2.load_from_db()
|
||||
self.assertEqual(pr2.get("items")[0].billed_amt, 2000)
|
||||
self.assertEqual(pr2.per_billed, 80)
|
||||
self.assertEqual(pr2.status, "To Bill")
|
||||
|
@ -28,9 +28,13 @@
|
||||
"uom",
|
||||
"stock_uom",
|
||||
"conversion_factor",
|
||||
"stock_qty",
|
||||
"retain_sample",
|
||||
"sample_quantity",
|
||||
"tracking_section",
|
||||
"received_stock_qty",
|
||||
"stock_qty",
|
||||
"col_break_tracking_section",
|
||||
"returned_qty",
|
||||
"rate_and_amount",
|
||||
"price_list_rate",
|
||||
"discount_percentage",
|
||||
@ -526,7 +530,7 @@
|
||||
{
|
||||
"fieldname": "stock_qty",
|
||||
"fieldtype": "Float",
|
||||
"label": "Accepted Qty as per Stock UOM",
|
||||
"label": "Accepted Qty in Stock UOM",
|
||||
"oldfieldname": "stock_qty",
|
||||
"oldfieldtype": "Currency",
|
||||
"print_hide": 1,
|
||||
@ -834,12 +838,35 @@
|
||||
"collapsible": 1,
|
||||
"fieldname": "image_column",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "tracking_section",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "col_break_tracking_section",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "returned_qty",
|
||||
"fieldname": "returned_qty",
|
||||
"fieldtype": "Float",
|
||||
"label": "Returned Qty in Stock UOM",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "received_stock_qty",
|
||||
"fieldtype": "Float",
|
||||
"label": "Received Qty in Stock UOM",
|
||||
"print_hide": 1
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-04-28 19:01:21.154963",
|
||||
"modified": "2020-11-02 10:00:38.204294",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Purchase Receipt Item",
|
||||
|
@ -55,7 +55,7 @@ class StockSettings(Document):
|
||||
""")
|
||||
|
||||
if sle:
|
||||
frappe.throw(_("Can't change valuation method, as there are transactions against some items which does not have it's own valuation method"))
|
||||
frappe.throw(_("Can't change the valuation method, as there are transactions against some items which do not have its own valuation method"))
|
||||
|
||||
def validate_clean_description_html(self):
|
||||
if int(self.clean_description_html or 0) \
|
||||
|
@ -164,7 +164,7 @@ def get_stock_ledger_entries(filters, items):
|
||||
select
|
||||
sle.item_code, warehouse, sle.posting_date, sle.actual_qty, sle.valuation_rate,
|
||||
sle.company, sle.voucher_type, sle.qty_after_transaction, sle.stock_value_difference,
|
||||
sle.item_code as name, sle.voucher_no
|
||||
sle.item_code as name, sle.voucher_no, sle.stock_value
|
||||
from
|
||||
`tabStock Ledger Entry` sle force index (posting_sort_index)
|
||||
where sle.docstatus < 2 %s %s
|
||||
@ -197,7 +197,7 @@ def get_item_warehouse_map(filters, sle):
|
||||
else:
|
||||
qty_diff = flt(d.actual_qty)
|
||||
|
||||
value_diff = flt(d.stock_value_difference)
|
||||
value_diff = flt(d.stock_value) - flt(qty_dict.bal_val)
|
||||
|
||||
if d.posting_date < from_date:
|
||||
qty_dict.opening_qty += qty_diff
|
||||
|
@ -1,6 +1,13 @@
|
||||
frappe.ui.form.on("Issue", {
|
||||
onload: function(frm) {
|
||||
frm.email_field = "raised_by";
|
||||
frm.set_query("customer", function () {
|
||||
return {
|
||||
filters: {
|
||||
"disabled": 0
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
frappe.db.get_value("Support Settings", {name: "Support Settings"},
|
||||
["allow_resetting_service_level_agreement", "track_service_level_agreement"], (r) => {
|
||||
@ -21,14 +28,14 @@ frappe.ui.form.on("Issue", {
|
||||
},
|
||||
callback: function (r) {
|
||||
if (r && r.message) {
|
||||
frm.set_query('priority', function() {
|
||||
frm.set_query("priority", function() {
|
||||
return {
|
||||
filters: {
|
||||
"name": ["in", r.message.priority],
|
||||
}
|
||||
};
|
||||
});
|
||||
frm.set_query('service_level_agreement', function() {
|
||||
frm.set_query("service_level_agreement", function() {
|
||||
return {
|
||||
filters: {
|
||||
"name": ["in", r.message.service_level_agreements],
|
||||
@ -45,9 +52,9 @@ frappe.ui.form.on("Issue", {
|
||||
if (frm.doc.status !== "Closed" && frm.doc.agreement_status === "Ongoing") {
|
||||
if (frm.doc.service_level_agreement) {
|
||||
frappe.call({
|
||||
'method': 'frappe.client.get',
|
||||
"method": "frappe.client.get",
|
||||
args: {
|
||||
doctype: 'Service Level Agreement',
|
||||
doctype: "Service Level Agreement",
|
||||
name: frm.doc.service_level_agreement
|
||||
},
|
||||
callback: function(data) {
|
||||
@ -127,8 +134,8 @@ frappe.ui.form.on("Issue", {
|
||||
reset_sla.clear();
|
||||
|
||||
frappe.show_alert({
|
||||
indicator: 'green',
|
||||
message: __('Resetting Service Level Agreement.')
|
||||
indicator: "green",
|
||||
message: __("Resetting Service Level Agreement.")
|
||||
});
|
||||
|
||||
frm.call("reset_service_level_agreement", {
|
||||
@ -145,35 +152,36 @@ frappe.ui.form.on("Issue", {
|
||||
reset_sla.show();
|
||||
},
|
||||
|
||||
|
||||
timeline_refresh: function(frm) {
|
||||
// create button for "Help Article"
|
||||
if(frappe.model.can_create('Help Article')) {
|
||||
if (frappe.model.can_create("Help Article")) {
|
||||
// Removing Help Article button if exists to avoid multiple occurance
|
||||
frm.timeline.wrapper.find('.comment-header .asset-details .btn-add-to-kb').remove();
|
||||
$('<button class="btn btn-xs btn-link btn-add-to-kb text-muted hidden-xs pull-right">'+
|
||||
__('Help Article') + '</button>')
|
||||
.appendTo(frm.timeline.wrapper.find('.comment-header .asset-details:not([data-communication-type="Comment"])'))
|
||||
.on('click', function() {
|
||||
var content = $(this).parents('.timeline-item:first').find('.timeline-item-content').html();
|
||||
var doc = frappe.model.get_new_doc('Help Article');
|
||||
.on("click", function() {
|
||||
var content = $(this).parents(".timeline-item:first").find(".timeline-item-content").html();
|
||||
var doc = frappe.model.get_new_doc("Help Article");
|
||||
doc.title = frm.doc.subject;
|
||||
doc.content = content;
|
||||
frappe.set_route('Form', 'Help Article', doc.name);
|
||||
frappe.set_route("Form", "Help Article", doc.name);
|
||||
});
|
||||
}
|
||||
|
||||
if (!frm.timeline.wrapper.find('.btn-split-issue').length) {
|
||||
if (!frm.timeline.wrapper.find(".btn-split-issue").length) {
|
||||
let split_issue = __("Split Issue")
|
||||
$(`<button class="btn btn-xs btn-link btn-add-to-kb text-muted hidden-xs btn-split-issue pull-right" style="display:inline-block; margin-right: 15px">
|
||||
${split_issue}
|
||||
</button>`)
|
||||
.appendTo(frm.timeline.wrapper.find('.comment-header .asset-details:not([data-communication-type="Comment"])'))
|
||||
if (!frm.timeline.wrapper.data("split-issue-event-attached")){
|
||||
frm.timeline.wrapper.on('click', '.btn-split-issue', (e) => {
|
||||
frm.timeline.wrapper.on("click", ".btn-split-issue", (e) => {
|
||||
var dialog = new frappe.ui.Dialog({
|
||||
title: __("Split Issue"),
|
||||
fields: [
|
||||
{fieldname: 'subject', fieldtype: 'Data', reqd:1, label: __('Subject'), description: __('All communications including and above this shall be moved into the new Issue')}
|
||||
{fieldname: "subject", fieldtype: "Data", reqd: 1, label: __("Subject"), description: __("All communications including and above this shall be moved into the new Issue")}
|
||||
],
|
||||
primary_action_label: __("Split"),
|
||||
primary_action: function() {
|
||||
@ -226,7 +234,7 @@ function set_time_to_resolve_and_response(frm) {
|
||||
function get_time_left(timestamp, agreement_status) {
|
||||
const diff = moment(timestamp).diff(moment());
|
||||
const diff_display = diff >= 44500 ? moment.duration(diff).humanize() : "Failed";
|
||||
let indicator = (diff_display == 'Failed' && agreement_status != "Fulfilled") ? "red" : "green";
|
||||
let indicator = (diff_display == "Failed" && agreement_status != "Fulfilled") ? "red" : "green";
|
||||
return {"diff_display": diff_display, "indicator": indicator};
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user