Merge branch 'develop' into cont-temp

This commit is contained in:
Marica 2020-12-07 09:53:23 +05:30 committed by GitHub
commit 13dcd18ede
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 853 additions and 196 deletions

View File

@ -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()

View File

@ -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)

View File

@ -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",

View File

@ -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"
})

View File

@ -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:

View File

@ -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"

View File

@ -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

View File

@ -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)

View File

@ -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():

View File

@ -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,

View File

@ -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():

View File

@ -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"));
}
},

View File

@ -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",

View File

@ -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) {

View File

@ -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",

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.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)

View File

@ -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:

View File

@ -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

View File

@ -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'])])

View File

@ -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)

View File

@ -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

View File

@ -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

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,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,

View File

@ -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 === "") {

View File

@ -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

View File

@ -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,

View File

@ -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

View File

@ -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",

View File

@ -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):

View File

@ -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"];
}
},

View File

@ -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')

View File

@ -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",

View File

@ -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",

View File

@ -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):

View File

@ -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"];
}
}

View File

@ -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")

View File

@ -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",

View File

@ -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) \

View File

@ -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

View File

@ -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};
}