Merge branch 'develop' into update-barcode-if-scan-barcode-used
This commit is contained in:
commit
3bc69e59ec
@ -245,6 +245,9 @@ def get():
|
||||
"account_number": "2200"
|
||||
},
|
||||
_("Duties and Taxes"): {
|
||||
_("TDS Payable"): {
|
||||
"account_number": "2310"
|
||||
},
|
||||
"account_type": "Tax",
|
||||
"is_group": 1,
|
||||
"account_number": "2300"
|
||||
|
@ -9,11 +9,13 @@ from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sal
|
||||
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
||||
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
|
||||
from erpnext.accounts.page.bank_reconciliation.bank_reconciliation import reconcile, get_linked_payments
|
||||
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
|
||||
|
||||
test_dependencies = ["Item", "Cost Center"]
|
||||
|
||||
class TestBankTransaction(unittest.TestCase):
|
||||
def setUp(self):
|
||||
make_pos_profile()
|
||||
add_transactions()
|
||||
add_payments()
|
||||
|
||||
@ -27,6 +29,9 @@ class TestBankTransaction(unittest.TestCase):
|
||||
frappe.db.sql("""delete from `tabPayment Entry Reference`""")
|
||||
frappe.db.sql("""delete from `tabPayment Entry`""")
|
||||
|
||||
# Delete POS Profile
|
||||
frappe.db.sql("delete from `tabPOS Profile`")
|
||||
|
||||
frappe.flags.test_bank_transactions_created = False
|
||||
frappe.flags.test_payments_created = False
|
||||
|
||||
|
@ -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",
|
||||
@ -76,4 +116,31 @@ def get_opening_invoice_creation_dict(**args):
|
||||
})
|
||||
|
||||
invoice_dict.update(args)
|
||||
return invoice_dict
|
||||
return invoice_dict
|
||||
|
||||
def make_company():
|
||||
if frappe.db.exists("Company", "_Test Opening Invoice Company"):
|
||||
return frappe.get_doc("Company", "_Test Opening Invoice Company")
|
||||
|
||||
company = frappe.new_doc("Company")
|
||||
company.company_name = "_Test Opening Invoice Company"
|
||||
company.abbr = "_TOIC"
|
||||
company.default_currency = "INR"
|
||||
company.country = "India"
|
||||
company.insert()
|
||||
return company
|
||||
|
||||
def make_customer(customer=None):
|
||||
customer_name = customer or "Opening Customer"
|
||||
customer = frappe.get_doc({
|
||||
"doctype": "Customer",
|
||||
"customer_name": customer_name,
|
||||
"customer_group": "All Customer Groups",
|
||||
"customer_type": "Company",
|
||||
"territory": "All Territories"
|
||||
})
|
||||
if not frappe.db.exists("Customer", customer_name):
|
||||
customer.insert(ignore_permissions=True)
|
||||
return customer.name
|
||||
else:
|
||||
return frappe.db.exists("Customer", customer_name)
|
@ -14,7 +14,6 @@
|
||||
"column_break_9",
|
||||
"update_stock",
|
||||
"ignore_pricing_rule",
|
||||
"hide_unavailable_items",
|
||||
"warehouse",
|
||||
"campaign",
|
||||
"company_address",
|
||||
@ -23,6 +22,9 @@
|
||||
"section_break_11",
|
||||
"payments",
|
||||
"section_break_14",
|
||||
"hide_images",
|
||||
"hide_unavailable_items",
|
||||
"auto_add_item_to_cart",
|
||||
"item_groups",
|
||||
"column_break_16",
|
||||
"customer_groups",
|
||||
@ -124,7 +126,8 @@
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_14",
|
||||
"fieldtype": "Section Break"
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Configuration"
|
||||
},
|
||||
{
|
||||
"description": "Only show Items from these Item Groups",
|
||||
@ -314,13 +317,25 @@
|
||||
"fieldname": "hide_unavailable_items",
|
||||
"fieldtype": "Check",
|
||||
"label": "Hide Unavailable Items"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "hide_images",
|
||||
"fieldtype": "Check",
|
||||
"label": "Hide Images"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "auto_add_item_to_cart",
|
||||
"fieldtype": "Check",
|
||||
"label": "Automatically Add Filtered Item To Cart"
|
||||
}
|
||||
],
|
||||
"icon": "icon-cog",
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2020-10-29 13:18:38.795925",
|
||||
"modified": "2020-12-10 13:59:28.877572",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Profile",
|
||||
|
@ -70,6 +70,7 @@ def get_items_list(pos_profile, company):
|
||||
""".format(cond=cond), tuple([company] + args_list), as_dict=1)
|
||||
|
||||
def make_pos_profile(**args):
|
||||
frappe.db.sql("delete from `tabPOS Payment Method`")
|
||||
frappe.db.sql("delete from `tabPOS Profile`")
|
||||
|
||||
args = frappe._dict(args)
|
||||
|
@ -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,20 +555,23 @@ 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'}
|
||||
doc.append(child_table.get(doc.apply_on), {
|
||||
apply_on: args.get(apply_on) or "_Test Item"
|
||||
})
|
||||
|
||||
if doc.apply_on != "Transaction":
|
||||
doc.append(child_table.get(doc.apply_on), {
|
||||
apply_on: args.get(apply_on) or "_Test Item"
|
||||
})
|
||||
|
||||
doc.insert(ignore_permissions=True)
|
||||
if args.get(apply_on) and apply_on != "item_code":
|
||||
|
@ -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
|
||||
|
@ -405,6 +405,8 @@ class SalesInvoice(SellingController):
|
||||
from erpnext.stock.get_item_details import get_pos_profile_item_details, get_pos_profile
|
||||
if not self.pos_profile:
|
||||
pos_profile = get_pos_profile(self.company) or {}
|
||||
if not pos_profile:
|
||||
frappe.throw(_("No POS Profile found. Please create a New POS Profile first"))
|
||||
self.pos_profile = pos_profile.get('name')
|
||||
|
||||
pos = {}
|
||||
@ -472,6 +474,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)
|
||||
|
||||
|
@ -690,7 +690,8 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
self.assertTrue(gle)
|
||||
|
||||
def test_pos_gl_entry_with_perpetual_inventory(self):
|
||||
make_pos_profile()
|
||||
make_pos_profile(company="_Test Company with perpetual inventory", income_account = "Sales - TCP1",
|
||||
expense_account = "Cost of Goods Sold - TCP1", warehouse="Stores - TCP1", cost_center = "Main - TCP1", write_off_account="_Test Write Off - TCP1")
|
||||
|
||||
pr = make_purchase_receipt(company= "_Test Company with perpetual inventory", item_code= "_Test FG Item",warehouse= "Stores - TCP1",cost_center= "Main - TCP1")
|
||||
|
||||
@ -746,7 +747,8 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
self.assertEqual(pos_return.get('payments')[0].amount, -1000)
|
||||
|
||||
def test_pos_change_amount(self):
|
||||
make_pos_profile()
|
||||
make_pos_profile(company="_Test Company with perpetual inventory", income_account = "Sales - TCP1",
|
||||
expense_account = "Cost of Goods Sold - TCP1", warehouse="Stores - TCP1", cost_center = "Main - TCP1", write_off_account="_Test Write Off - TCP1")
|
||||
|
||||
pr = make_purchase_receipt(company= "_Test Company with perpetual inventory",
|
||||
item_code= "_Test FG Item",warehouse= "Stores - TCP1", cost_center= "Main - TCP1")
|
||||
|
@ -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():
|
||||
|
@ -8,6 +8,7 @@ from frappe.utils import flt
|
||||
from erpnext.accounts.report.item_wise_sales_register.item_wise_sales_register import (get_tax_accounts,
|
||||
get_grand_total, add_total_row, get_display_value, get_group_by_and_display_fields, add_sub_total_row,
|
||||
get_group_by_conditions)
|
||||
from erpnext.selling.report.item_wise_sales_history.item_wise_sales_history import get_item_details
|
||||
|
||||
def execute(filters=None):
|
||||
return _execute(filters)
|
||||
@ -22,7 +23,7 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
|
||||
aii_account_map = get_aii_accounts()
|
||||
if item_list:
|
||||
itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency,
|
||||
doctype="Purchase Invoice", tax_doctype="Purchase Taxes and Charges")
|
||||
doctype='Purchase Invoice', tax_doctype='Purchase Taxes and Charges')
|
||||
|
||||
po_pr_map = get_purchase_receipts_against_purchase_order(item_list)
|
||||
|
||||
@ -34,10 +35,14 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
|
||||
if filters.get('group_by'):
|
||||
grand_total = get_grand_total(filters, 'Purchase Invoice')
|
||||
|
||||
item_details = get_item_details()
|
||||
|
||||
for d in item_list:
|
||||
if not d.stock_qty:
|
||||
continue
|
||||
|
||||
item_record = item_details.get(d.item_code)
|
||||
|
||||
purchase_receipt = None
|
||||
if d.purchase_receipt:
|
||||
purchase_receipt = d.purchase_receipt
|
||||
@ -48,8 +53,8 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
|
||||
|
||||
row = {
|
||||
'item_code': d.item_code,
|
||||
'item_name': d.item_name,
|
||||
'item_group': d.item_group,
|
||||
'item_name': item_record.item_name,
|
||||
'item_group': item_record.item_group,
|
||||
'description': d.description,
|
||||
'invoice': d.parent,
|
||||
'posting_date': d.posting_date,
|
||||
@ -81,10 +86,10 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
|
||||
for tax in tax_columns:
|
||||
item_tax = itemised_tax.get(d.name, {}).get(tax, {})
|
||||
row.update({
|
||||
frappe.scrub(tax + ' Rate'): item_tax.get("tax_rate", 0),
|
||||
frappe.scrub(tax + ' Amount'): item_tax.get("tax_amount", 0),
|
||||
frappe.scrub(tax + ' Rate'): item_tax.get('tax_rate', 0),
|
||||
frappe.scrub(tax + ' Amount'): item_tax.get('tax_amount', 0),
|
||||
})
|
||||
total_tax += flt(item_tax.get("tax_amount"))
|
||||
total_tax += flt(item_tax.get('tax_amount'))
|
||||
|
||||
row.update({
|
||||
'total_tax': total_tax,
|
||||
@ -309,8 +314,8 @@ def get_items(filters, additional_query_columns):
|
||||
select
|
||||
`tabPurchase Invoice Item`.`name`, `tabPurchase Invoice Item`.`parent`,
|
||||
`tabPurchase Invoice`.posting_date, `tabPurchase Invoice`.credit_to, `tabPurchase Invoice`.company,
|
||||
`tabPurchase Invoice`.supplier, `tabPurchase Invoice`.remarks, `tabPurchase Invoice`.base_net_total, `tabPurchase Invoice Item`.`item_code`,
|
||||
`tabPurchase Invoice Item`.`item_name`, `tabPurchase Invoice Item`.`item_group`, `tabPurchase Invoice Item`.description,
|
||||
`tabPurchase Invoice`.supplier, `tabPurchase Invoice`.remarks, `tabPurchase Invoice`.base_net_total,
|
||||
`tabPurchase Invoice Item`.`item_code`, `tabPurchase Invoice Item`.description,
|
||||
`tabPurchase Invoice Item`.`project`, `tabPurchase Invoice Item`.`purchase_order`,
|
||||
`tabPurchase Invoice Item`.`purchase_receipt`, `tabPurchase Invoice Item`.`po_detail`,
|
||||
`tabPurchase Invoice Item`.`expense_account`, `tabPurchase Invoice Item`.`stock_qty`,
|
||||
|
@ -8,6 +8,7 @@ from frappe.utils import flt, cstr
|
||||
from frappe.model.meta import get_field_precision
|
||||
from frappe.utils.xlsxutils import handle_html
|
||||
from erpnext.accounts.report.sales_register.sales_register import get_mode_of_payments
|
||||
from erpnext.selling.report.item_wise_sales_history.item_wise_sales_history import get_item_details, get_customer_details
|
||||
|
||||
def execute(filters=None):
|
||||
return _execute(filters)
|
||||
@ -16,7 +17,7 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
|
||||
if not filters: filters = {}
|
||||
columns = get_columns(additional_table_columns, filters)
|
||||
|
||||
company_currency = frappe.get_cached_value('Company', filters.get("company"), "default_currency")
|
||||
company_currency = frappe.get_cached_value('Company', filters.get('company'), 'default_currency')
|
||||
|
||||
item_list = get_items(filters, additional_query_columns)
|
||||
if item_list:
|
||||
@ -33,7 +34,13 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
|
||||
if filters.get('group_by'):
|
||||
grand_total = get_grand_total(filters, 'Sales Invoice')
|
||||
|
||||
customer_details = get_customer_details()
|
||||
item_details = get_item_details()
|
||||
|
||||
for d in item_list:
|
||||
customer_record = customer_details.get(d.customer)
|
||||
item_record = item_details.get(d.item_code)
|
||||
|
||||
delivery_note = None
|
||||
if d.delivery_note:
|
||||
delivery_note = d.delivery_note
|
||||
@ -45,14 +52,14 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
|
||||
|
||||
row = {
|
||||
'item_code': d.item_code,
|
||||
'item_name': d.item_name,
|
||||
'item_group': d.item_group,
|
||||
'item_name': item_record.item_name,
|
||||
'item_group': item_record.item_group,
|
||||
'description': d.description,
|
||||
'invoice': d.parent,
|
||||
'posting_date': d.posting_date,
|
||||
'customer': d.customer,
|
||||
'customer_name': d.customer_name,
|
||||
'customer_group': d.customer_group,
|
||||
'customer_name': customer_record.customer_name,
|
||||
'customer_group': customer_record.customer_group,
|
||||
}
|
||||
|
||||
if additional_query_columns:
|
||||
@ -90,10 +97,10 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
|
||||
for tax in tax_columns:
|
||||
item_tax = itemised_tax.get(d.name, {}).get(tax, {})
|
||||
row.update({
|
||||
frappe.scrub(tax + ' Rate'): item_tax.get("tax_rate", 0),
|
||||
frappe.scrub(tax + ' Amount'): item_tax.get("tax_amount", 0),
|
||||
frappe.scrub(tax + ' Rate'): item_tax.get('tax_rate', 0),
|
||||
frappe.scrub(tax + ' Amount'): item_tax.get('tax_amount', 0),
|
||||
})
|
||||
total_tax += flt(item_tax.get("tax_amount"))
|
||||
total_tax += flt(item_tax.get('tax_amount'))
|
||||
|
||||
row.update({
|
||||
'total_tax': total_tax,
|
||||
@ -226,7 +233,7 @@ def get_columns(additional_table_columns, filters):
|
||||
if filters.get('group_by') != 'Territory':
|
||||
columns.extend([
|
||||
{
|
||||
'label': _("Territory"),
|
||||
'label': _('Territory'),
|
||||
'fieldname': 'territory',
|
||||
'fieldtype': 'Link',
|
||||
'options': 'Territory',
|
||||
@ -374,13 +381,12 @@ def get_items(filters, additional_query_columns):
|
||||
`tabSales Invoice`.posting_date, `tabSales Invoice`.debit_to,
|
||||
`tabSales Invoice`.project, `tabSales Invoice`.customer, `tabSales Invoice`.remarks,
|
||||
`tabSales Invoice`.territory, `tabSales Invoice`.company, `tabSales Invoice`.base_net_total,
|
||||
`tabSales Invoice Item`.item_code, `tabSales Invoice Item`.item_name,
|
||||
`tabSales Invoice Item`.item_group, `tabSales Invoice Item`.description, `tabSales Invoice Item`.sales_order,
|
||||
`tabSales Invoice Item`.delivery_note, `tabSales Invoice Item`.income_account,
|
||||
`tabSales Invoice Item`.cost_center, `tabSales Invoice Item`.stock_qty,
|
||||
`tabSales Invoice Item`.stock_uom, `tabSales Invoice Item`.base_net_rate,
|
||||
`tabSales Invoice Item`.base_net_amount, `tabSales Invoice`.customer_name,
|
||||
`tabSales Invoice`.customer_group, `tabSales Invoice Item`.so_detail,
|
||||
`tabSales Invoice Item`.item_code, `tabSales Invoice Item`.description,
|
||||
`tabSales Invoice Item`.sales_order, `tabSales Invoice Item`.delivery_note,
|
||||
`tabSales Invoice Item`.income_account, `tabSales Invoice Item`.cost_center,
|
||||
`tabSales Invoice Item`.stock_qty, `tabSales Invoice Item`.stock_uom,
|
||||
`tabSales Invoice Item`.base_net_rate, `tabSales Invoice Item`.base_net_amount,
|
||||
`tabSales Invoice`.customer_name, `tabSales Invoice`.customer_group, `tabSales Invoice Item`.so_detail,
|
||||
`tabSales Invoice`.update_stock, `tabSales Invoice Item`.uom, `tabSales Invoice Item`.qty {0}
|
||||
from `tabSales Invoice`, `tabSales Invoice Item`
|
||||
where `tabSales Invoice`.name = `tabSales Invoice Item`.parent
|
||||
@ -417,14 +423,14 @@ def get_deducted_taxes():
|
||||
return frappe.db.sql_list("select name from `tabPurchase Taxes and Charges` where add_deduct_tax = 'Deduct'")
|
||||
|
||||
def get_tax_accounts(item_list, columns, company_currency,
|
||||
doctype="Sales Invoice", tax_doctype="Sales Taxes and Charges"):
|
||||
doctype='Sales Invoice', tax_doctype='Sales Taxes and Charges'):
|
||||
import json
|
||||
item_row_map = {}
|
||||
tax_columns = []
|
||||
invoice_item_row = {}
|
||||
itemised_tax = {}
|
||||
|
||||
tax_amount_precision = get_field_precision(frappe.get_meta(tax_doctype).get_field("tax_amount"),
|
||||
tax_amount_precision = get_field_precision(frappe.get_meta(tax_doctype).get_field('tax_amount'),
|
||||
currency=company_currency) or 2
|
||||
|
||||
for d in item_list:
|
||||
@ -469,8 +475,8 @@ def get_tax_accounts(item_list, columns, company_currency,
|
||||
tax_rate = tax_data
|
||||
tax_amount = 0
|
||||
|
||||
if charge_type == "Actual" and not tax_rate:
|
||||
tax_rate = "NA"
|
||||
if charge_type == 'Actual' and not tax_rate:
|
||||
tax_rate = 'NA'
|
||||
|
||||
item_net_amount = sum([flt(d.base_net_amount)
|
||||
for d in item_row_map.get(parent, {}).get(item_code, [])])
|
||||
@ -484,17 +490,17 @@ def get_tax_accounts(item_list, columns, company_currency,
|
||||
if (doctype == 'Purchase Invoice' and name in deducted_tax) else tax_value)
|
||||
|
||||
itemised_tax.setdefault(d.name, {})[description] = frappe._dict({
|
||||
"tax_rate": tax_rate,
|
||||
"tax_amount": tax_value
|
||||
'tax_rate': tax_rate,
|
||||
'tax_amount': tax_value
|
||||
})
|
||||
|
||||
except ValueError:
|
||||
continue
|
||||
elif charge_type == "Actual" and tax_amount:
|
||||
elif charge_type == 'Actual' and tax_amount:
|
||||
for d in invoice_item_row.get(parent, []):
|
||||
itemised_tax.setdefault(d.name, {})[description] = frappe._dict({
|
||||
"tax_rate": "NA",
|
||||
"tax_amount": flt((tax_amount * d.base_net_amount) / d.base_net_total,
|
||||
'tax_rate': 'NA',
|
||||
'tax_amount': flt((tax_amount * d.base_net_amount) / d.base_net_total,
|
||||
tax_amount_precision)
|
||||
})
|
||||
|
||||
@ -563,7 +569,7 @@ def add_total_row(data, filters, prev_group_by_value, item, total_row_map,
|
||||
})
|
||||
|
||||
total_row_map.setdefault('total_row', {
|
||||
subtotal_display_field: "Total",
|
||||
subtotal_display_field: 'Total',
|
||||
'stock_qty': 0.0,
|
||||
'amount': 0.0,
|
||||
'bold': 1,
|
||||
|
@ -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():
|
||||
|
@ -78,7 +78,10 @@ def get_fiscal_years(transaction_date=None, fiscal_year=None, label="Date", verb
|
||||
else:
|
||||
return ((fy.name, fy.year_start_date, fy.year_end_date),)
|
||||
|
||||
error_msg = _("""{0} {1} not in any active Fiscal Year.""").format(label, formatdate(transaction_date))
|
||||
error_msg = _("""{0} {1} is not in any active Fiscal Year""").format(label, formatdate(transaction_date))
|
||||
if company:
|
||||
error_msg = _("""{0} for {1}""").format(error_msg, frappe.bold(company))
|
||||
|
||||
if verbose==1: frappe.msgprint(error_msg)
|
||||
raise FiscalYearError(error_msg)
|
||||
|
||||
|
@ -75,24 +75,23 @@ def get_data(filters):
|
||||
for asset in assets_record:
|
||||
asset_value = asset.gross_purchase_amount - flt(asset.opening_accumulated_depreciation) \
|
||||
- flt(depreciation_amount_map.get(asset.name))
|
||||
if asset_value:
|
||||
row = {
|
||||
"asset_id": asset.asset_id,
|
||||
"asset_name": asset.asset_name,
|
||||
"status": asset.status,
|
||||
"department": asset.department,
|
||||
"cost_center": asset.cost_center,
|
||||
"vendor_name": pr_supplier_map.get(asset.purchase_receipt) or pi_supplier_map.get(asset.purchase_invoice),
|
||||
"gross_purchase_amount": asset.gross_purchase_amount,
|
||||
"opening_accumulated_depreciation": asset.opening_accumulated_depreciation,
|
||||
"depreciated_amount": depreciation_amount_map.get(asset.asset_id) or 0.0,
|
||||
"available_for_use_date": asset.available_for_use_date,
|
||||
"location": asset.location,
|
||||
"asset_category": asset.asset_category,
|
||||
"purchase_date": asset.purchase_date,
|
||||
"asset_value": asset_value
|
||||
}
|
||||
data.append(row)
|
||||
row = {
|
||||
"asset_id": asset.asset_id,
|
||||
"asset_name": asset.asset_name,
|
||||
"status": asset.status,
|
||||
"department": asset.department,
|
||||
"cost_center": asset.cost_center,
|
||||
"vendor_name": pr_supplier_map.get(asset.purchase_receipt) or pi_supplier_map.get(asset.purchase_invoice),
|
||||
"gross_purchase_amount": asset.gross_purchase_amount,
|
||||
"opening_accumulated_depreciation": asset.opening_accumulated_depreciation,
|
||||
"depreciated_amount": depreciation_amount_map.get(asset.asset_id) or 0.0,
|
||||
"available_for_use_date": asset.available_for_use_date,
|
||||
"location": asset.location,
|
||||
"asset_category": asset.asset_category,
|
||||
"purchase_date": asset.purchase_date,
|
||||
"asset_value": asset_value
|
||||
}
|
||||
data.append(row)
|
||||
|
||||
return data
|
||||
|
||||
|
@ -168,6 +168,7 @@
|
||||
"bold": 1,
|
||||
"fieldname": "supplier",
|
||||
"fieldtype": "Link",
|
||||
"in_global_search": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Supplier",
|
||||
"oldfieldname": "supplier",
|
||||
@ -1106,7 +1107,7 @@
|
||||
"idx": 105,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-10-30 13:58:14.697921",
|
||||
"modified": "2020-12-03 16:46:44.229351",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Purchase Order",
|
||||
|
@ -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
|
||||
@ -261,20 +288,25 @@ def make_return_doc(doctype, source_name, target_doc=None):
|
||||
doc.run_method("calculate_taxes_and_totals")
|
||||
|
||||
def update_item(source_doc, target_doc, source_parent):
|
||||
target_doc.qty = -1* source_doc.qty
|
||||
target_doc.qty = -1 * source_doc.qty
|
||||
|
||||
if doctype == "Purchase Receipt":
|
||||
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
|
||||
target_doc.purchase_receipt_item = source_doc.name
|
||||
|
||||
elif doctype == "Purchase Invoice":
|
||||
target_doc.received_qty = -1* source_doc.received_qty
|
||||
target_doc.rejected_qty = -1* source_doc.rejected_qty
|
||||
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
|
||||
target_doc.purchase_order = source_doc.purchase_order
|
||||
@ -285,6 +317,10 @@ def make_return_doc(doctype, source_name, target_doc=None):
|
||||
target_doc.purchase_invoice_item = source_doc.name
|
||||
|
||||
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
|
||||
|
@ -42,7 +42,7 @@ class SellingController(StockController):
|
||||
self.validate_max_discount()
|
||||
self.validate_selling_price()
|
||||
self.set_qty_as_per_stock_uom()
|
||||
self.set_po_nos()
|
||||
self.set_po_nos(for_validate=True)
|
||||
self.set_gross_profit()
|
||||
set_default_income_account_for_item(self)
|
||||
self.set_customer_address()
|
||||
@ -370,20 +370,28 @@ class SellingController(StockController):
|
||||
}))
|
||||
self.make_sl_entries(sl_entries)
|
||||
|
||||
def set_po_nos(self):
|
||||
def set_po_nos(self, for_validate=False):
|
||||
if self.doctype == 'Sales Invoice' and hasattr(self, "items"):
|
||||
if for_validate and self.po_no:
|
||||
return
|
||||
self.set_pos_for_sales_invoice()
|
||||
if self.doctype == 'Delivery Note' and hasattr(self, "items"):
|
||||
if for_validate and self.po_no:
|
||||
return
|
||||
self.set_pos_for_delivery_note()
|
||||
|
||||
def set_pos_for_sales_invoice(self):
|
||||
po_nos = []
|
||||
if self.po_no:
|
||||
po_nos.append(self.po_no)
|
||||
self.get_po_nos('Sales Order', 'sales_order', po_nos)
|
||||
self.get_po_nos('Delivery Note', 'delivery_note', po_nos)
|
||||
self.po_no = ', '.join(list(set(x.strip() for x in ','.join(po_nos).split(','))))
|
||||
|
||||
def set_pos_for_delivery_note(self):
|
||||
po_nos = []
|
||||
if self.po_no:
|
||||
po_nos.append(self.po_no)
|
||||
self.get_po_nos('Sales Order', 'against_sales_order', po_nos)
|
||||
self.get_po_nos('Sales Invoice', 'against_sales_invoice', po_nos)
|
||||
self.po_no = ', '.join(list(set(x.strip() for x in ','.join(po_nos).split(','))))
|
||||
|
@ -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,13 +274,19 @@ class StatusUpdater(Document):
|
||||
|
||||
def _update_percent_field_in_targets(self, args, update_modified=True):
|
||||
"""Update percent field in parent transaction"""
|
||||
distinct_transactions = set([d.get(args['percent_join_field'])
|
||||
for d in self.get_all_children(args['source_dt'])])
|
||||
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'])])
|
||||
|
||||
for name in distinct_transactions:
|
||||
if name:
|
||||
args['name'] = name
|
||||
self._update_percent_field(args, update_modified)
|
||||
for name in distinct_transactions:
|
||||
if name:
|
||||
args['name'] = name
|
||||
self._update_percent_field(args, update_modified)
|
||||
|
||||
def _update_percent_field(self, args, update_modified=True):
|
||||
"""Update percent field in parent transaction"""
|
||||
|
@ -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)
|
||||
|
@ -641,7 +641,8 @@ class calculate_taxes_and_totals(object):
|
||||
if default_mode_of_payment:
|
||||
self.doc.append('payments', {
|
||||
'mode_of_payment': default_mode_of_payment.mode_of_payment,
|
||||
'amount': total_amount_to_pay
|
||||
'amount': total_amount_to_pay,
|
||||
'default': 1
|
||||
})
|
||||
else:
|
||||
self.doc.is_pos = 0
|
||||
|
@ -1,23 +1,31 @@
|
||||
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
cur_frm.add_fetch("contract_template", "contract_terms", "contract_terms");
|
||||
cur_frm.add_fetch("contract_template", "requires_fulfilment", "requires_fulfilment");
|
||||
|
||||
// Add fulfilment terms from contract template into contract
|
||||
frappe.ui.form.on("Contract", {
|
||||
contract_template: function (frm) {
|
||||
// Populate the fulfilment terms table from a contract template, if any
|
||||
if (frm.doc.contract_template) {
|
||||
frappe.model.with_doc("Contract Template", frm.doc.contract_template, function () {
|
||||
var tabletransfer = frappe.model.get_doc("Contract Template", frm.doc.contract_template);
|
||||
|
||||
frm.doc.fulfilment_terms = [];
|
||||
$.each(tabletransfer.fulfilment_terms, function (index, row) {
|
||||
var d = frm.add_child("fulfilment_terms");
|
||||
d.requirement = row.requirement;
|
||||
frm.refresh_field("fulfilment_terms");
|
||||
});
|
||||
frappe.call({
|
||||
method: 'erpnext.crm.doctype.contract_template.contract_template.get_contract_template',
|
||||
args: {
|
||||
template_name: frm.doc.contract_template,
|
||||
doc: frm.doc
|
||||
},
|
||||
callback: function(r) {
|
||||
if (r && r.message) {
|
||||
let contract_template = r.message.contract_template;
|
||||
frm.set_value("contract_terms", r.message.contract_terms);
|
||||
frm.set_value("requires_fulfilment", contract_template.requires_fulfilment);
|
||||
|
||||
if (frm.doc.requires_fulfilment) {
|
||||
// Populate the fulfilment terms table from a contract template, if any
|
||||
r.message.contract_template.fulfilment_terms.forEach(element => {
|
||||
let d = frm.add_child("fulfilment_terms");
|
||||
d.requirement = element.requirement;
|
||||
});
|
||||
frm.refresh_field("fulfilment_terms");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_import": 1,
|
||||
"allow_rename": 1,
|
||||
"creation": "2018-04-12 06:32:04.582486",
|
||||
@ -247,7 +248,7 @@
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-03-30 06:56:07.257932",
|
||||
"modified": "2020-12-07 11:15:58.385521",
|
||||
"modified_by": "Administrator",
|
||||
"module": "CRM",
|
||||
"name": "Contract",
|
||||
|
@ -11,7 +11,9 @@
|
||||
"contract_terms",
|
||||
"sb_fulfilment",
|
||||
"requires_fulfilment",
|
||||
"fulfilment_terms"
|
||||
"fulfilment_terms",
|
||||
"section_break_6",
|
||||
"contract_template_help"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@ -41,10 +43,20 @@
|
||||
"fieldtype": "Table",
|
||||
"label": "Fulfilment Terms and Conditions",
|
||||
"options": "Contract Template Fulfilment Terms"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_6",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "contract_template_help",
|
||||
"fieldtype": "HTML",
|
||||
"label": "Contract Template Help",
|
||||
"options": "<h4>Contract Template Example</h4>\n\n<pre>Contract for Customer {{ party_name }}\n\n-Valid From : {{ start_date }} \n-Valid To : {{ end_date }}\n</pre>\n\n<h4>How to get fieldnames</h4>\n\n<p>The field names you can use in your Contract Template are the fields in the Contract for which you are creating the template. You can find out the fields of any documents via Setup > Customize Form View and selecting the document type (e.g. Contract)</p>\n\n<h4>Templating</h4>\n\n<p>Templates are compiled using the Jinja Templating Language. To learn more about Jinja, <a class=\"strong\" href=\"http://jinja.pocoo.org/docs/dev/templates/\">read this documentation.</a></p>"
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2020-11-11 17:49:44.879363",
|
||||
"modified": "2020-12-07 10:44:22.587047",
|
||||
"modified_by": "Administrator",
|
||||
"module": "CRM",
|
||||
"name": "Contract Template",
|
||||
|
@ -5,6 +5,27 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils.jinja import validate_template
|
||||
from six import string_types
|
||||
import json
|
||||
|
||||
class ContractTemplate(Document):
|
||||
pass
|
||||
def validate(self):
|
||||
if self.contract_terms:
|
||||
validate_template(self.contract_terms)
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_contract_template(template_name, doc):
|
||||
if isinstance(doc, string_types):
|
||||
doc = json.loads(doc)
|
||||
|
||||
contract_template = frappe.get_doc("Contract Template", template_name)
|
||||
contract_terms = None
|
||||
|
||||
if contract_template.contract_terms:
|
||||
contract_terms = frappe.render_template(contract_template.contract_terms, doc)
|
||||
|
||||
return {
|
||||
'contract_template': contract_template,
|
||||
'contract_terms': contract_terms
|
||||
}
|
@ -60,4 +60,12 @@ def create_mode_of_payment(gateway, payment_type="General"):
|
||||
"default_account": payment_gateway_account
|
||||
}]
|
||||
})
|
||||
mode_of_payment.insert(ignore_permissions=True)
|
||||
mode_of_payment.insert(ignore_permissions=True)
|
||||
|
||||
def get_tracking_url(carrier, tracking_number):
|
||||
# Return the formatted Tracking URL.
|
||||
tracking_url = ''
|
||||
url_reference = frappe.get_value('Parcel Service', carrier, 'url_reference')
|
||||
if url_reference:
|
||||
tracking_url = frappe.render_template(url_reference, {'tracking_number': tracking_number})
|
||||
return tracking_url
|
||||
|
@ -7,6 +7,7 @@ import frappe
|
||||
import unittest
|
||||
from frappe.utils import nowdate, add_days
|
||||
from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import create_healthcare_docs, create_appointment, create_healthcare_service_items
|
||||
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
|
||||
|
||||
test_dependencies = ["Company"]
|
||||
|
||||
@ -15,6 +16,7 @@ class TestFeeValidity(unittest.TestCase):
|
||||
frappe.db.sql("""delete from `tabPatient Appointment`""")
|
||||
frappe.db.sql("""delete from `tabFee Validity`""")
|
||||
frappe.db.sql("""delete from `tabPatient`""")
|
||||
make_pos_profile()
|
||||
|
||||
def test_fee_validity(self):
|
||||
item = create_healthcare_service_items()
|
||||
|
@ -7,12 +7,14 @@ import frappe
|
||||
from erpnext.healthcare.doctype.patient_appointment.patient_appointment import update_status, make_encounter
|
||||
from frappe.utils import nowdate, add_days
|
||||
from frappe.utils.make_random import get_random
|
||||
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
|
||||
|
||||
class TestPatientAppointment(unittest.TestCase):
|
||||
def setUp(self):
|
||||
frappe.db.sql("""delete from `tabPatient Appointment`""")
|
||||
frappe.db.sql("""delete from `tabFee Validity`""")
|
||||
frappe.db.sql("""delete from `tabPatient Encounter`""")
|
||||
make_pos_profile()
|
||||
|
||||
def test_status(self):
|
||||
patient, medical_department, practitioner = create_healthcare_docs()
|
||||
|
@ -6,11 +6,13 @@ import unittest
|
||||
import frappe
|
||||
from frappe.utils import nowdate
|
||||
from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import create_encounter, create_healthcare_docs, create_appointment
|
||||
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
|
||||
|
||||
class TestPatientMedicalRecord(unittest.TestCase):
|
||||
def setUp(self):
|
||||
frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 0)
|
||||
frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 1)
|
||||
make_pos_profile()
|
||||
|
||||
def test_medical_record(self):
|
||||
patient, medical_department, practitioner = create_healthcare_docs()
|
||||
|
@ -441,42 +441,43 @@ global_search_doctypes = {
|
||||
{"doctype": "Sales Order", "index": 8},
|
||||
{"doctype": "Quotation", "index": 9},
|
||||
{"doctype": "Work Order", "index": 10},
|
||||
{"doctype": "Purchase Receipt", "index": 11},
|
||||
{"doctype": "Purchase Invoice", "index": 12},
|
||||
{"doctype": "Delivery Note", "index": 13},
|
||||
{"doctype": "Stock Entry", "index": 14},
|
||||
{"doctype": "Material Request", "index": 15},
|
||||
{"doctype": "Delivery Trip", "index": 16},
|
||||
{"doctype": "Pick List", "index": 17},
|
||||
{"doctype": "Salary Slip", "index": 18},
|
||||
{"doctype": "Leave Application", "index": 19},
|
||||
{"doctype": "Expense Claim", "index": 20},
|
||||
{"doctype": "Payment Entry", "index": 21},
|
||||
{"doctype": "Lead", "index": 22},
|
||||
{"doctype": "Opportunity", "index": 23},
|
||||
{"doctype": "Item Price", "index": 24},
|
||||
{"doctype": "Purchase Taxes and Charges Template", "index": 25},
|
||||
{"doctype": "Sales Taxes and Charges", "index": 26},
|
||||
{"doctype": "Asset", "index": 27},
|
||||
{"doctype": "Project", "index": 28},
|
||||
{"doctype": "Task", "index": 29},
|
||||
{"doctype": "Timesheet", "index": 30},
|
||||
{"doctype": "Issue", "index": 31},
|
||||
{"doctype": "Serial No", "index": 32},
|
||||
{"doctype": "Batch", "index": 33},
|
||||
{"doctype": "Branch", "index": 34},
|
||||
{"doctype": "Department", "index": 35},
|
||||
{"doctype": "Employee Grade", "index": 36},
|
||||
{"doctype": "Designation", "index": 37},
|
||||
{"doctype": "Job Opening", "index": 38},
|
||||
{"doctype": "Job Applicant", "index": 39},
|
||||
{"doctype": "Job Offer", "index": 40},
|
||||
{"doctype": "Salary Structure Assignment", "index": 41},
|
||||
{"doctype": "Appraisal", "index": 42},
|
||||
{"doctype": "Loan", "index": 43},
|
||||
{"doctype": "Maintenance Schedule", "index": 44},
|
||||
{"doctype": "Maintenance Visit", "index": 45},
|
||||
{"doctype": "Warranty Claim", "index": 46},
|
||||
{"doctype": "Purchase Order", "index": 11},
|
||||
{"doctype": "Purchase Receipt", "index": 12},
|
||||
{"doctype": "Purchase Invoice", "index": 13},
|
||||
{"doctype": "Delivery Note", "index": 14},
|
||||
{"doctype": "Stock Entry", "index": 15},
|
||||
{"doctype": "Material Request", "index": 16},
|
||||
{"doctype": "Delivery Trip", "index": 17},
|
||||
{"doctype": "Pick List", "index": 18},
|
||||
{"doctype": "Salary Slip", "index": 19},
|
||||
{"doctype": "Leave Application", "index": 20},
|
||||
{"doctype": "Expense Claim", "index": 21},
|
||||
{"doctype": "Payment Entry", "index": 22},
|
||||
{"doctype": "Lead", "index": 23},
|
||||
{"doctype": "Opportunity", "index": 24},
|
||||
{"doctype": "Item Price", "index": 25},
|
||||
{"doctype": "Purchase Taxes and Charges Template", "index": 26},
|
||||
{"doctype": "Sales Taxes and Charges", "index": 27},
|
||||
{"doctype": "Asset", "index": 28},
|
||||
{"doctype": "Project", "index": 29},
|
||||
{"doctype": "Task", "index": 30},
|
||||
{"doctype": "Timesheet", "index": 31},
|
||||
{"doctype": "Issue", "index": 32},
|
||||
{"doctype": "Serial No", "index": 33},
|
||||
{"doctype": "Batch", "index": 34},
|
||||
{"doctype": "Branch", "index": 35},
|
||||
{"doctype": "Department", "index": 36},
|
||||
{"doctype": "Employee Grade", "index": 37},
|
||||
{"doctype": "Designation", "index": 38},
|
||||
{"doctype": "Job Opening", "index": 39},
|
||||
{"doctype": "Job Applicant", "index": 40},
|
||||
{"doctype": "Job Offer", "index": 41},
|
||||
{"doctype": "Salary Structure Assignment", "index": 42},
|
||||
{"doctype": "Appraisal", "index": 43},
|
||||
{"doctype": "Loan", "index": 44},
|
||||
{"doctype": "Maintenance Schedule", "index": 45},
|
||||
{"doctype": "Maintenance Visit", "index": 46},
|
||||
{"doctype": "Warranty Claim", "index": 47},
|
||||
],
|
||||
"Healthcare": [
|
||||
{'doctype': 'Patient', 'index': 1},
|
||||
|
@ -124,7 +124,7 @@ class ProductionPlanReport(object):
|
||||
if self.filters.include_subassembly_raw_materials else "(bom_item.qty / bom.quantity)")
|
||||
|
||||
raw_materials = frappe.db.sql(""" SELECT bom_item.parent, bom_item.item_code,
|
||||
bom_item.item_name as raw_material_name, {0} as required_qty
|
||||
bom_item.item_name as raw_material_name, {0} as required_qty_per_unit
|
||||
FROM
|
||||
`tabBOM` as bom, `tab{1}` as bom_item
|
||||
WHERE
|
||||
@ -208,7 +208,7 @@ class ProductionPlanReport(object):
|
||||
warehouses = self.mrp_warehouses or []
|
||||
for d in self.raw_materials_dict.get(key):
|
||||
if self.filters.based_on != "Work Order":
|
||||
d.required_qty = d.required_qty * data.qty_to_manufacture
|
||||
d.required_qty = d.required_qty_per_unit * data.qty_to_manufacture
|
||||
|
||||
if not warehouses:
|
||||
warehouses = [data.warehouse]
|
||||
|
@ -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,5 @@ 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.add_po_to_global_search
|
||||
erpnext.patches.v13_0.update_returned_qty_in_pr_dn
|
||||
|
17
erpnext/patches/v13_0/add_po_to_global_search.py
Normal file
17
erpnext/patches/v13_0/add_po_to_global_search.py
Normal file
@ -0,0 +1,17 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
global_search_settings = frappe.get_single("Global Search Settings")
|
||||
|
||||
if "Purchase Order" in (
|
||||
dt.document_type for dt in global_search_settings.allowed_in_global_search
|
||||
):
|
||||
return
|
||||
|
||||
global_search_settings.append(
|
||||
"allowed_in_global_search", {"document_type": "Purchase Order"}
|
||||
)
|
||||
|
||||
global_search_settings.save(ignore_permissions=True)
|
@ -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
|
||||
|
@ -29,7 +29,7 @@ def execute():
|
||||
'response_by_variance': response_by_variance,
|
||||
'resolution_by_variance': resolution_by_variance,
|
||||
'first_response_time': mins_to_first_response
|
||||
})
|
||||
}, update_modified=False)
|
||||
# commit after every 100 updates
|
||||
count += 1
|
||||
if count%100 == 0:
|
||||
@ -44,7 +44,7 @@ def execute():
|
||||
count = 0
|
||||
for entry in opportunities:
|
||||
mins_to_first_response = convert_to_seconds(entry.mins_to_first_response, 'Minutes')
|
||||
frappe.db.set_value('Opportunity', entry.name, 'first_response_time', mins_to_first_response)
|
||||
frappe.db.set_value('Opportunity', entry.name, 'first_response_time', mins_to_first_response, update_modified=False)
|
||||
# commit after every 100 updates
|
||||
count += 1
|
||||
if count%100 == 0:
|
||||
|
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,19 +7,23 @@ import frappe
|
||||
def execute():
|
||||
parent_list = []
|
||||
count = 0
|
||||
for data in frappe.db.sql("""
|
||||
select
|
||||
|
||||
frappe.reload_doc('stock', 'doctype', 'purchase_receipt')
|
||||
frappe.reload_doc('stock', 'doctype', 'purchase_receipt_item')
|
||||
|
||||
for data in frappe.db.sql("""
|
||||
select
|
||||
`tabPurchase Receipt Item`.purchase_order, `tabPurchase Receipt Item`.name,
|
||||
`tabPurchase Receipt Item`.item_code, `tabPurchase Receipt Item`.idx,
|
||||
`tabPurchase Receipt Item`.parent
|
||||
from
|
||||
from
|
||||
`tabPurchase Receipt Item`, `tabPurchase Receipt`
|
||||
where
|
||||
`tabPurchase Receipt Item`.parent = `tabPurchase Receipt`.name and
|
||||
`tabPurchase Receipt Item`.purchase_order_item is null and
|
||||
`tabPurchase Receipt Item`.purchase_order is not null and
|
||||
`tabPurchase Receipt`.is_return = 1""", as_dict=1):
|
||||
name = frappe.db.get_value('Purchase Order Item',
|
||||
name = frappe.db.get_value('Purchase Order Item',
|
||||
{'item_code': data.item_code, 'parent': data.purchase_order, 'idx': data.idx}, 'name')
|
||||
|
||||
if name:
|
||||
|
@ -289,7 +289,9 @@ class PayrollEntry(Document):
|
||||
jv_name = journal_entry.name
|
||||
self.update_salary_slip_status(jv_name = jv_name)
|
||||
except Exception as e:
|
||||
frappe.msgprint(e)
|
||||
if type(e) in (str, list, tuple):
|
||||
frappe.msgprint(e)
|
||||
raise
|
||||
|
||||
return jv_name
|
||||
|
||||
|
@ -118,11 +118,6 @@ class TestSalarySlip(unittest.TestCase):
|
||||
|
||||
self.assertEqual(ss.payment_days, days_in_month - no_of_holidays - 4)
|
||||
|
||||
#Gross pay calculation based on attendances
|
||||
gross_pay = 78000 - ((78000 / (days_in_month - no_of_holidays)) * flt(ss.leave_without_pay))
|
||||
|
||||
self.assertEqual(flt(ss.gross_pay, 2), flt(gross_pay, 2))
|
||||
|
||||
frappe.db.set_value("Payroll Settings", None, "payroll_based_on", "Leave")
|
||||
|
||||
def test_salary_slip_with_holidays_included(self):
|
||||
|
@ -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 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)
|
||||
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, 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
|
||||
|
@ -8,7 +8,7 @@ frappe.ui.form.on("Sales Order", {
|
||||
frm.custom_make_buttons = {
|
||||
'Delivery Note': 'Delivery Note',
|
||||
'Pick List': 'Pick List',
|
||||
'Sales Invoice': 'Invoice',
|
||||
'Sales Invoice': 'Sales Invoice',
|
||||
'Material Request': 'Material Request',
|
||||
'Purchase Order': 'Purchase Order',
|
||||
'Project': 'Project',
|
||||
|
@ -111,24 +111,24 @@ erpnext.PointOfSale.Controller = class {
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
prepare_app_defaults(data) {
|
||||
async prepare_app_defaults(data) {
|
||||
this.pos_opening = data.name;
|
||||
this.company = data.company;
|
||||
this.pos_profile = data.pos_profile;
|
||||
this.pos_opening_time = data.period_start_date;
|
||||
this.item_stock_map = {};
|
||||
this.settings = {};
|
||||
|
||||
frappe.db.get_value('Stock Settings', undefined, 'allow_negative_stock').then(({ message }) => {
|
||||
this.allow_negative_stock = flt(message.allow_negative_stock) || false;
|
||||
});
|
||||
|
||||
frappe.db.get_doc("POS Profile", this.pos_profile).then((profile) => {
|
||||
this.customer_groups = profile.customer_groups.map(group => group.customer_group);
|
||||
this.cart.make_customer_selector();
|
||||
this.settings.customer_groups = profile.customer_groups.map(group => group.customer_group);
|
||||
this.settings.hide_images = profile.hide_images;
|
||||
this.settings.auto_add_item_to_cart = profile.auto_add_item_to_cart;
|
||||
this.make_app();
|
||||
});
|
||||
|
||||
this.item_stock_map = {};
|
||||
|
||||
this.make_app();
|
||||
}
|
||||
|
||||
set_opening_entry_status() {
|
||||
@ -238,12 +238,11 @@ erpnext.PointOfSale.Controller = class {
|
||||
this.item_selector = new erpnext.PointOfSale.ItemSelector({
|
||||
wrapper: this.$components_wrapper,
|
||||
pos_profile: this.pos_profile,
|
||||
settings: this.settings,
|
||||
events: {
|
||||
item_selected: args => this.on_cart_update(args),
|
||||
|
||||
get_frm: () => this.frm || {},
|
||||
|
||||
get_allowed_item_group: () => this.item_groups
|
||||
get_frm: () => this.frm || {}
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -251,6 +250,7 @@ erpnext.PointOfSale.Controller = class {
|
||||
init_item_cart() {
|
||||
this.cart = new erpnext.PointOfSale.ItemCart({
|
||||
wrapper: this.$components_wrapper,
|
||||
settings: this.settings,
|
||||
events: {
|
||||
get_frm: () => this.frm,
|
||||
|
||||
@ -273,9 +273,7 @@ erpnext.PointOfSale.Controller = class {
|
||||
this.customer_details = details;
|
||||
// will add/remove LP payment method
|
||||
this.payment.render_loyalty_points_payment_mode();
|
||||
},
|
||||
|
||||
get_allowed_customer_group: () => this.customer_groups
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -1,8 +1,10 @@
|
||||
erpnext.PointOfSale.ItemCart = class {
|
||||
constructor({ wrapper, events }) {
|
||||
constructor({ wrapper, events, settings }) {
|
||||
this.wrapper = wrapper;
|
||||
this.events = events;
|
||||
this.customer_info = undefined;
|
||||
this.hide_images = settings.hide_images;
|
||||
this.allowed_customer_groups = settings.customer_groups;
|
||||
|
||||
this.init_component();
|
||||
}
|
||||
@ -32,6 +34,7 @@ erpnext.PointOfSale.ItemCart = class {
|
||||
`<div class="customer-section rounded flex flex-col m-8 mb-0"></div>`
|
||||
)
|
||||
this.$customer_section = this.$component.find('.customer-section');
|
||||
this.make_customer_selector();
|
||||
}
|
||||
|
||||
reset_customer_selector() {
|
||||
@ -302,7 +305,7 @@ erpnext.PointOfSale.ItemCart = class {
|
||||
this.$customer_section.html(`<div class="customer-search-field flex flex-1 items-center"></div>`);
|
||||
const me = this;
|
||||
const query = { query: 'erpnext.controllers.queries.customer_query' };
|
||||
const allowed_customer_group = this.events.get_allowed_customer_group() || [];
|
||||
const allowed_customer_group = this.allowed_customer_groups || [];
|
||||
if (allowed_customer_group.length) {
|
||||
query.filters = {
|
||||
customer_group: ['in', allowed_customer_group]
|
||||
@ -423,6 +426,7 @@ erpnext.PointOfSale.ItemCart = class {
|
||||
}
|
||||
|
||||
update_customer_section() {
|
||||
const me = this;
|
||||
const { customer, email_id='', mobile_no='', image } = this.customer_info || {};
|
||||
|
||||
if (customer) {
|
||||
@ -460,7 +464,7 @@ erpnext.PointOfSale.ItemCart = class {
|
||||
}
|
||||
|
||||
function get_customer_image() {
|
||||
if (image) {
|
||||
if (!me.hide_images && image) {
|
||||
return `<div class="icon flex items-center justify-center w-12 h-12 rounded bg-light-grey mr-4 text-grey-200">
|
||||
<img class="h-full" src="${image}" alt="${image}" style="object-fit: cover;">
|
||||
</div>`
|
||||
|
@ -1,8 +1,10 @@
|
||||
erpnext.PointOfSale.ItemSelector = class {
|
||||
constructor({ frm, wrapper, events, pos_profile }) {
|
||||
constructor({ frm, wrapper, events, pos_profile, settings }) {
|
||||
this.wrapper = wrapper;
|
||||
this.events = events;
|
||||
this.pos_profile = pos_profile;
|
||||
this.hide_images = settings.hide_images;
|
||||
this.auto_add_item = settings.auto_add_item_to_cart;
|
||||
|
||||
this.inti_component();
|
||||
}
|
||||
@ -26,13 +28,14 @@ erpnext.PointOfSale.ItemSelector = class {
|
||||
<div class="flex flex-1 flex-col p-8 pt-2">
|
||||
<div class="text-grey mb-6">ALL ITEMS</div>
|
||||
<div class="items-container grid grid-cols-4 gap-8">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>`
|
||||
);
|
||||
|
||||
this.$component = this.wrapper.find('.items-selector');
|
||||
this.$items_container = this.$component.find('.items-container');
|
||||
}
|
||||
|
||||
async load_items_data() {
|
||||
@ -65,7 +68,6 @@ erpnext.PointOfSale.ItemSelector = class {
|
||||
|
||||
|
||||
render_item_list(items) {
|
||||
this.$items_container = this.$component.find('.items-container');
|
||||
this.$items_container.html('');
|
||||
|
||||
items.forEach(item => {
|
||||
@ -75,11 +77,12 @@ erpnext.PointOfSale.ItemSelector = class {
|
||||
}
|
||||
|
||||
get_item_html(item) {
|
||||
const me = this;
|
||||
const { item_image, serial_no, batch_no, barcode, actual_qty, stock_uom } = item;
|
||||
const indicator_color = actual_qty > 10 ? "green" : actual_qty <= 0 ? "red" : "orange";
|
||||
|
||||
function get_item_image_html() {
|
||||
if (item_image) {
|
||||
if (!me.hide_images && item_image) {
|
||||
return `<div class="flex items-center justify-center h-32 border-b-grey text-6xl text-grey-100">
|
||||
<img class="h-full" src="${item_image}" alt="${frappe.get_abbr(item.item_name)}" style="object-fit: cover;">
|
||||
</div>`
|
||||
@ -203,6 +206,7 @@ erpnext.PointOfSale.ItemSelector = class {
|
||||
ignore_inputs: true,
|
||||
page: cur_page.page.page
|
||||
});
|
||||
|
||||
// for selecting the last filtered item on search
|
||||
frappe.ui.keys.on("enter", () => {
|
||||
const selector_is_visible = this.$component.is(':visible');
|
||||
@ -235,6 +239,7 @@ erpnext.PointOfSale.ItemSelector = class {
|
||||
const items = this.search_index[search_term];
|
||||
this.items = items;
|
||||
this.render_item_list(items);
|
||||
this.auto_add_item && this.items.length == 1 && this.add_filtered_item_to_cart();
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -247,8 +252,13 @@ erpnext.PointOfSale.ItemSelector = class {
|
||||
}
|
||||
this.items = items;
|
||||
this.render_item_list(items);
|
||||
this.auto_add_item && this.items.length == 1 && this.add_filtered_item_to_cart();
|
||||
});
|
||||
}
|
||||
|
||||
add_filtered_item_to_cart() {
|
||||
this.$items_container.find(".item-wrapper").click();
|
||||
}
|
||||
|
||||
resize_selector(minimize) {
|
||||
minimize ?
|
||||
|
@ -10,8 +10,8 @@ from frappe.utils.nestedset import get_descendants_of
|
||||
def execute(filters=None):
|
||||
filters = frappe._dict(filters or {})
|
||||
if filters.from_date > filters.to_date:
|
||||
frappe.throw(_('From Date cannot be greater than To Date'))
|
||||
|
||||
frappe.throw(_("From Date cannot be greater than To Date"))
|
||||
|
||||
columns = get_columns(filters)
|
||||
data = get_data(filters)
|
||||
|
||||
@ -148,14 +148,16 @@ def get_data(filters):
|
||||
company_list.append(filters.get("company"))
|
||||
|
||||
customer_details = get_customer_details()
|
||||
item_details = get_item_details()
|
||||
sales_order_records = get_sales_order_details(company_list, filters)
|
||||
|
||||
for record in sales_order_records:
|
||||
customer_record = customer_details.get(record.customer)
|
||||
item_record = item_details.get(record.item_code)
|
||||
row = {
|
||||
"item_code": record.item_code,
|
||||
"item_name": record.item_name,
|
||||
"item_group": record.item_group,
|
||||
"item_name": item_record.item_name,
|
||||
"item_group": item_record.item_group,
|
||||
"description": record.description,
|
||||
"quantity": record.qty,
|
||||
"uom": record.uom,
|
||||
@ -196,8 +198,8 @@ def get_conditions(filters):
|
||||
return conditions
|
||||
|
||||
def get_customer_details():
|
||||
details = frappe.get_all('Customer',
|
||||
fields=['name', 'customer_name', "customer_group"])
|
||||
details = frappe.get_all("Customer",
|
||||
fields=["name", "customer_name", "customer_group"])
|
||||
customer_details = {}
|
||||
for d in details:
|
||||
customer_details.setdefault(d.name, frappe._dict({
|
||||
@ -206,15 +208,25 @@ def get_customer_details():
|
||||
}))
|
||||
return customer_details
|
||||
|
||||
def get_item_details():
|
||||
details = frappe.db.get_all("Item",
|
||||
fields=["item_code", "item_name", "item_group"])
|
||||
item_details = {}
|
||||
for d in details:
|
||||
item_details.setdefault(d.item_code, frappe._dict({
|
||||
"item_name": d.item_name,
|
||||
"item_group": d.item_group
|
||||
}))
|
||||
return item_details
|
||||
|
||||
def get_sales_order_details(company_list, filters):
|
||||
conditions = get_conditions(filters)
|
||||
|
||||
return frappe.db.sql("""
|
||||
SELECT
|
||||
so_item.item_code, so_item.item_name, so_item.item_group,
|
||||
so_item.description, so_item.qty, so_item.uom,
|
||||
so_item.base_rate, so_item.base_amount, so.name,
|
||||
so.transaction_date, so.customer, so.territory,
|
||||
so_item.item_code, so_item.description, so_item.qty,
|
||||
so_item.uom, so_item.base_rate, so_item.base_amount,
|
||||
so.name, so.transaction_date, so.customer,so.territory,
|
||||
so.project, so_item.delivered_qty,
|
||||
so_item.billed_amt, so.company
|
||||
FROM
|
||||
|
@ -1,25 +1,5 @@
|
||||
{
|
||||
"cards": [
|
||||
{
|
||||
"hidden": 0,
|
||||
"label": "Healthcare",
|
||||
"links": "[\n {\n \"label\": \"Patient\",\n \"name\": \"Patient\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Diagnosis\",\n \"name\": \"Diagnosis\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n }\n]"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"label": "Agriculture",
|
||||
"links": "[\n {\n \"label\": \"Crop\",\n \"name\": \"Crop\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Crop Cycle\",\n \"name\": \"Crop Cycle\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Location\",\n \"name\": \"Location\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Fertilizer\",\n \"name\": \"Fertilizer\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n }\n]"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"label": "Education",
|
||||
"links": "[\n {\n \"label\": \"Student\",\n \"name\": \"Student\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Course\",\n \"name\": \"Course\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Instructor\",\n \"name\": \"Instructor\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Room\",\n \"name\": \"Room\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n }\n]"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"label": "Non Profit",
|
||||
"links": "[\n {\n \"description\": \"Member information.\",\n \"label\": \"Member\",\n \"name\": \"Member\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Volunteer information.\",\n \"label\": \"Volunteer\",\n \"name\": \"Volunteer\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Chapter information.\",\n \"label\": \"Chapter\",\n \"name\": \"Chapter\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Donor information.\",\n \"label\": \"Donor\",\n \"name\": \"Donor\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n }\n]"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"label": "Stock",
|
||||
@ -54,10 +34,11 @@
|
||||
"docstatus": 0,
|
||||
"doctype": "Desk Page",
|
||||
"extends_another_page": 0,
|
||||
"hide_custom": 0,
|
||||
"idx": 0,
|
||||
"is_standard": 1,
|
||||
"label": "Home",
|
||||
"modified": "2020-05-11 10:20:37.358701",
|
||||
"modified": "2020-12-07 14:22:38.667767",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Setup",
|
||||
"name": "Home",
|
||||
@ -96,4 +77,4 @@
|
||||
"type": "Page"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@
|
||||
{
|
||||
"hidden": 0,
|
||||
"label": "Stock Transactions",
|
||||
"links": "[\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"label\": \"Material Request\",\n \"name\": \"Material Request\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"label\": \"Stock Entry\",\n \"name\": \"Stock Entry\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Customer\"\n ],\n \"label\": \"Delivery Note\",\n \"name\": \"Delivery Note\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Supplier\"\n ],\n \"label\": \"Purchase Receipt\",\n \"name\": \"Purchase Receipt\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"label\": \"Pick List\",\n \"name\": \"Pick List\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Delivery Trip\",\n \"name\": \"Delivery Trip\",\n \"type\": \"doctype\"\n }\n]"
|
||||
"links": "[\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"label\": \"Material Request\",\n \"name\": \"Material Request\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"label\": \"Stock Entry\",\n \"name\": \"Stock Entry\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Customer\"\n ],\n \"label\": \"Delivery Note\",\n \"name\": \"Delivery Note\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Supplier\"\n ],\n \"label\": \"Purchase Receipt\",\n \"name\": \"Purchase Receipt\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"label\": \"Pick List\",\n \"name\": \"Pick List\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Shipment\",\n \"name\": \"Shipment\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Delivery Trip\",\n \"name\": \"Delivery Trip\",\n \"type\": \"doctype\"\n }\n]"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
@ -58,7 +58,7 @@
|
||||
"idx": 0,
|
||||
"is_standard": 1,
|
||||
"label": "Stock",
|
||||
"modified": "2020-10-07 18:40:17.130207",
|
||||
"modified": "2020-12-02 15:47:41.532942",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Stock",
|
||||
|
@ -156,6 +156,11 @@ erpnext.stock.DeliveryNoteController = erpnext.selling.SellingController.extend(
|
||||
}
|
||||
|
||||
if (!doc.is_return && doc.status!="Closed") {
|
||||
if(doc.docstatus == 1) {
|
||||
this.frm.add_custom_button(__('Shipment'), function() {
|
||||
me.make_shipment() }, __('Create'));
|
||||
}
|
||||
|
||||
if(flt(doc.per_installed, 2) < 100 && doc.docstatus==1)
|
||||
this.frm.add_custom_button(__('Installation Note'), function() {
|
||||
me.make_installation_note() }, __('Create'));
|
||||
@ -220,6 +225,13 @@ erpnext.stock.DeliveryNoteController = erpnext.selling.SellingController.extend(
|
||||
}
|
||||
},
|
||||
|
||||
make_shipment: function() {
|
||||
frappe.model.open_mapped_doc({
|
||||
method: "erpnext.stock.doctype.delivery_note.delivery_note.make_shipment",
|
||||
frm: this.frm
|
||||
})
|
||||
},
|
||||
|
||||
make_sales_invoice: function() {
|
||||
frappe.model.open_mapped_doc({
|
||||
method: "erpnext.stock.doctype.delivery_note.delivery_note.make_sales_invoice",
|
||||
|
@ -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):
|
||||
@ -569,6 +581,62 @@ def make_packing_slip(source_name, target_doc=None):
|
||||
|
||||
return doclist
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_shipment(source_name, target_doc=None):
|
||||
def postprocess(source, target):
|
||||
user = frappe.db.get_value("User", frappe.session.user, ['email', 'full_name', 'phone', 'mobile_no'], as_dict=1)
|
||||
target.pickup_contact_email = user.email
|
||||
pickup_contact_display = '{}'.format(user.full_name)
|
||||
if user:
|
||||
if user.email:
|
||||
pickup_contact_display += '<br>' + user.email
|
||||
if user.phone:
|
||||
pickup_contact_display += '<br>' + user.phone
|
||||
if user.mobile_no and not user.phone:
|
||||
pickup_contact_display += '<br>' + user.mobile_no
|
||||
target.pickup_contact = pickup_contact_display
|
||||
|
||||
contact = frappe.db.get_value("Contact", source.contact_person, ['email_id', 'phone', 'mobile_no'], as_dict=1)
|
||||
delivery_contact_display = '{}'.format(source.contact_display)
|
||||
if contact:
|
||||
if contact.email_id:
|
||||
delivery_contact_display += '<br>' + contact.email_id
|
||||
if contact.phone:
|
||||
delivery_contact_display += '<br>' + contact.phone
|
||||
if contact.mobile_no and not contact.phone:
|
||||
delivery_contact_display += '<br>' + contact.mobile_no
|
||||
target.delivery_contact = delivery_contact_display
|
||||
|
||||
doclist = get_mapped_doc("Delivery Note", source_name, {
|
||||
"Delivery Note": {
|
||||
"doctype": "Shipment",
|
||||
"field_map": {
|
||||
"grand_total": "value_of_goods",
|
||||
"company": "pickup_company",
|
||||
"company_address": "pickup_address_name",
|
||||
"company_address_display": "pickup_address",
|
||||
"address_display": "delivery_address",
|
||||
"customer": "delivery_customer",
|
||||
"shipping_address_name": "delivery_address_name",
|
||||
"contact_person": "delivery_contact_name",
|
||||
"contact_email": "delivery_contact_email"
|
||||
},
|
||||
"validation": {
|
||||
"docstatus": ["=", 1]
|
||||
}
|
||||
},
|
||||
"Delivery Note Item": {
|
||||
"doctype": "Shipment Delivery Note",
|
||||
"field_map": {
|
||||
"name": "prevdoc_detail_docname",
|
||||
"parent": "prevdoc_docname",
|
||||
"parenttype": "prevdoc_doctype",
|
||||
"base_amount": "grand_total"
|
||||
}
|
||||
}
|
||||
}, target_doc, postprocess)
|
||||
|
||||
return doclist
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_sales_return(source_name, target_doc=None):
|
||||
|
@ -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,20 +55,33 @@ class PurchaseReceipt(BuyingController):
|
||||
'percent_join_field': 'material_request'
|
||||
}]
|
||||
if cint(self.is_return):
|
||||
self.status_updater.append({
|
||||
'source_dt': 'Purchase Receipt Item',
|
||||
'target_dt': 'Purchase Order Item',
|
||||
'join_field': 'purchase_order_item',
|
||||
'target_field': 'returned_qty',
|
||||
'source_field': '-1 * qty',
|
||||
'second_source_dt': 'Purchase Invoice Item',
|
||||
'second_source_field': '-1 * qty',
|
||||
'second_join_field': 'po_detail',
|
||||
'extra_cond': """ and exists (select name from `tabPurchase Receipt`
|
||||
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)"""
|
||||
})
|
||||
self.status_updater.extend([
|
||||
{
|
||||
'source_dt': 'Purchase Receipt Item',
|
||||
'target_dt': 'Purchase Order Item',
|
||||
'join_field': 'purchase_order_item',
|
||||
'target_field': 'returned_qty',
|
||||
'source_field': '-1 * qty',
|
||||
'second_source_dt': 'Purchase Invoice Item',
|
||||
'second_source_field': '-1 * qty',
|
||||
'second_join_field': 'po_detail',
|
||||
'extra_cond': """ and exists (select name from `tabPurchase Receipt`
|
||||
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()
|
||||
@ -478,7 +491,7 @@ class PurchaseReceipt(BuyingController):
|
||||
frappe.db.set_value("Asset", asset.name, "purchase_receipt_amount", flt(valuation_rate))
|
||||
|
||||
def update_status(self, status):
|
||||
self.set_status(update=True, status = status)
|
||||
self.set_status(update=True, status=status)
|
||||
self.notify_update()
|
||||
clear_doctype_notifications(self)
|
||||
|
||||
@ -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",
|
||||
|
0
erpnext/stock/doctype/shipment/__init__.py
Normal file
0
erpnext/stock/doctype/shipment/__init__.py
Normal file
447
erpnext/stock/doctype/shipment/shipment.js
Normal file
447
erpnext/stock/doctype/shipment/shipment.js
Normal file
@ -0,0 +1,447 @@
|
||||
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Shipment', {
|
||||
address_query: function(frm, link_doctype, link_name, is_your_company_address) {
|
||||
return {
|
||||
query: 'frappe.contacts.doctype.address.address.address_query',
|
||||
filters: {
|
||||
link_doctype: link_doctype,
|
||||
link_name: link_name,
|
||||
is_your_company_address: is_your_company_address
|
||||
}
|
||||
};
|
||||
},
|
||||
contact_query: function(frm, link_doctype, link_name) {
|
||||
return {
|
||||
query: 'frappe.contacts.doctype.contact.contact.contact_query',
|
||||
filters: {
|
||||
link_doctype: link_doctype,
|
||||
link_name: link_name
|
||||
}
|
||||
};
|
||||
},
|
||||
onload: function(frm) {
|
||||
frm.set_query("delivery_address_name", () => {
|
||||
let delivery_to = `delivery_${frappe.model.scrub(frm.doc.delivery_to_type)}`;
|
||||
return frm.events.address_query(frm, frm.doc.delivery_to_type, frm.doc[delivery_to], frm.doc.delivery_to_type === 'Company' ? 1 : 0);
|
||||
});
|
||||
frm.set_query("pickup_address_name", () => {
|
||||
let pickup_from = `pickup_${frappe.model.scrub(frm.doc.pickup_from_type)}`;
|
||||
return frm.events.address_query(frm, frm.doc.pickup_from_type, frm.doc[pickup_from], frm.doc.pickup_from_type === 'Company' ? 1 : 0);
|
||||
});
|
||||
frm.set_query("delivery_contact_name", () => {
|
||||
let delivery_to = `delivery_${frappe.model.scrub(frm.doc.delivery_to_type)}`;
|
||||
return frm.events.contact_query(frm, frm.doc.delivery_to_type, frm.doc[delivery_to]);
|
||||
});
|
||||
frm.set_query("pickup_contact_name", () => {
|
||||
let pickup_from = `pickup_${frappe.model.scrub(frm.doc.pickup_from_type)}`;
|
||||
return frm.events.contact_query(frm, frm.doc.pickup_from_type, frm.doc[pickup_from]);
|
||||
});
|
||||
frm.set_query("delivery_note", "shipment_delivery_note", function() {
|
||||
let customer = '';
|
||||
if (frm.doc.delivery_to_type == "Customer") {
|
||||
customer = frm.doc.delivery_customer;
|
||||
}
|
||||
if (frm.doc.delivery_to_type == "Company") {
|
||||
customer = frm.doc.delivery_company;
|
||||
}
|
||||
if (customer) {
|
||||
return {
|
||||
filters: {
|
||||
customer: customer,
|
||||
docstatus: 1,
|
||||
status: ["not in", ["Cancelled"]]
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
},
|
||||
refresh: function() {
|
||||
$('div[data-fieldname=pickup_address] > div > .clearfix').hide();
|
||||
$('div[data-fieldname=pickup_contact] > div > .clearfix').hide();
|
||||
$('div[data-fieldname=delivery_address] > div > .clearfix').hide();
|
||||
$('div[data-fieldname=delivery_contact] > div > .clearfix').hide();
|
||||
},
|
||||
before_save: function(frm) {
|
||||
let delivery_to = `delivery_${frappe.model.scrub(frm.doc.delivery_to_type)}`;
|
||||
frm.set_value("delivery_to", frm.doc[delivery_to]);
|
||||
let pickup_from = `pickup_${frappe.model.scrub(frm.doc.pickup_from_type)}`;
|
||||
frm.set_value("pickup", frm.doc[pickup_from]);
|
||||
},
|
||||
set_pickup_company_address: function(frm) {
|
||||
frappe.db.get_value('Address', {
|
||||
address_title: frm.doc.pickup_company,
|
||||
is_your_company_address: 1
|
||||
}, 'name', (r) => {
|
||||
frm.set_value("pickup_address_name", r.name);
|
||||
});
|
||||
},
|
||||
set_delivery_company_address: function(frm) {
|
||||
frappe.db.get_value('Address', {
|
||||
address_title: frm.doc.delivery_company,
|
||||
is_your_company_address: 1
|
||||
}, 'name', (r) => {
|
||||
frm.set_value("delivery_address_name", r.name);
|
||||
});
|
||||
},
|
||||
pickup_from_type: function(frm) {
|
||||
if (frm.doc.pickup_from_type == 'Company') {
|
||||
frm.set_value("pickup_company", frappe.defaults.get_default('company'));
|
||||
frm.set_value("pickup_customer", '');
|
||||
frm.set_value("pickup_supplier", '');
|
||||
} else {
|
||||
frm.trigger('clear_pickup_fields');
|
||||
}
|
||||
if (frm.doc.pickup_from_type == 'Customer') {
|
||||
frm.set_value("pickup_company", '');
|
||||
frm.set_value("pickup_supplier", '');
|
||||
}
|
||||
if (frm.doc.pickup_from_type == 'Supplier') {
|
||||
frm.set_value("pickup_customer", '');
|
||||
frm.set_value("pickup_company", '');
|
||||
}
|
||||
},
|
||||
delivery_to_type: function(frm) {
|
||||
if (frm.doc.delivery_to_type == 'Company') {
|
||||
frm.set_value("delivery_company", frappe.defaults.get_default('company'));
|
||||
frm.set_value("delivery_customer", '');
|
||||
frm.set_value("delivery_supplier", '');
|
||||
} else {
|
||||
frm.trigger('clear_delivery_fields');
|
||||
}
|
||||
if (frm.doc.delivery_to_type == 'Customer') {
|
||||
frm.set_value("delivery_company", '');
|
||||
frm.set_value("delivery_supplier", '');
|
||||
}
|
||||
if (frm.doc.delivery_to_type == 'Supplier') {
|
||||
frm.set_value("delivery_customer", '');
|
||||
frm.set_value("delivery_company", '');
|
||||
frm.toggle_display("shipment_delivery_note", false);
|
||||
} else {
|
||||
frm.toggle_display("shipment_delivery_note", true);
|
||||
}
|
||||
},
|
||||
delivery_address_name: function(frm) {
|
||||
if (frm.doc.delivery_to_type == 'Company') {
|
||||
erpnext.utils.get_address_display(frm, 'delivery_address_name', 'delivery_address', true);
|
||||
} else {
|
||||
erpnext.utils.get_address_display(frm, 'delivery_address_name', 'delivery_address', false);
|
||||
}
|
||||
},
|
||||
pickup_address_name: function(frm) {
|
||||
if (frm.doc.pickup_from_type == 'Company') {
|
||||
erpnext.utils.get_address_display(frm, 'pickup_address_name', 'pickup_address', true);
|
||||
} else {
|
||||
erpnext.utils.get_address_display(frm, 'pickup_address_name', 'pickup_address', false);
|
||||
}
|
||||
},
|
||||
get_contact_display: function(frm, contact_name, contact_type) {
|
||||
frappe.call({
|
||||
method: "frappe.contacts.doctype.contact.contact.get_contact_details",
|
||||
args: { contact: contact_name },
|
||||
callback: function(r) {
|
||||
if (r.message) {
|
||||
if (!(r.message.contact_email && (r.message.contact_phone || r.message.contact_mobile))) {
|
||||
if (contact_type == 'Delivery') {
|
||||
frm.set_value('delivery_contact_name', '');
|
||||
frm.set_value('delivery_contact', '');
|
||||
} else {
|
||||
frm.set_value('pickup_contact_name', '');
|
||||
frm.set_value('pickup_contact', '');
|
||||
}
|
||||
frappe.throw(__("Email or Phone/Mobile of the Contact are mandatory to continue.") + "</br>" + __("Please set Email/Phone for the contact") + ` <a href='#Form/Contact/${contact_name}'>${contact_name}</a>`);
|
||||
}
|
||||
let contact_display = r.message.contact_display;
|
||||
if (r.message.contact_email) {
|
||||
contact_display += '<br>' + r.message.contact_email;
|
||||
}
|
||||
if (r.message.contact_phone) {
|
||||
contact_display += '<br>' + r.message.contact_phone;
|
||||
}
|
||||
if (r.message.contact_mobile && !r.message.contact_phone) {
|
||||
contact_display += '<br>' + r.message.contact_mobile;
|
||||
}
|
||||
if (contact_type == 'Delivery') {
|
||||
frm.set_value('delivery_contact', contact_display);
|
||||
if (r.message.contact_email) {
|
||||
frm.set_value('delivery_contact_email', r.message.contact_email);
|
||||
}
|
||||
} else {
|
||||
frm.set_value('pickup_contact', contact_display);
|
||||
if (r.message.contact_email) {
|
||||
frm.set_value('pickup_contact_email', r.message.contact_email);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
delivery_contact_name: function(frm) {
|
||||
if (frm.doc.delivery_contact_name) {
|
||||
frm.events.get_contact_display(frm, frm.doc.delivery_contact_name, 'Delivery');
|
||||
}
|
||||
},
|
||||
pickup_contact_name: function(frm) {
|
||||
if (frm.doc.pickup_contact_name) {
|
||||
frm.events.get_contact_display(frm, frm.doc.pickup_contact_name, 'Pickup');
|
||||
}
|
||||
},
|
||||
pickup_contact_person: function(frm) {
|
||||
if (frm.doc.pickup_contact_person) {
|
||||
frappe.call({
|
||||
method: "erpnext.stock.doctype.shipment.shipment.get_company_contact",
|
||||
args: { user: frm.doc.pickup_contact_person },
|
||||
callback: function({ message }) {
|
||||
const r = message;
|
||||
let contact_display = `${r.first_name} ${r.last_name}`;
|
||||
if (r.email) {
|
||||
contact_display += `<br>${ r.email }`;
|
||||
frm.set_value('pickup_contact_email', r.email);
|
||||
}
|
||||
if (r.phone) {
|
||||
contact_display += `<br>${ r.phone }`;
|
||||
}
|
||||
if (r.mobile_no && !r.phone) {
|
||||
contact_display += `<br>${ r.mobile_no }`;
|
||||
}
|
||||
frm.set_value('pickup_contact', contact_display);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (frm.doc.pickup_from_type === 'Company') {
|
||||
frappe.call({
|
||||
method: "erpnext.stock.doctype.shipment.shipment.get_company_contact",
|
||||
args: { user: frappe.session.user },
|
||||
callback: function({ message }) {
|
||||
const r = message;
|
||||
let contact_display = `${r.first_name} ${r.last_name}`;
|
||||
if (r.email) {
|
||||
contact_display += `<br>${ r.email }`;
|
||||
frm.set_value('pickup_contact_email', r.email);
|
||||
}
|
||||
if (r.phone) {
|
||||
contact_display += `<br>${ r.phone }`;
|
||||
}
|
||||
if (r.mobile_no && !r.phone) {
|
||||
contact_display += `<br>${ r.mobile_no }`;
|
||||
}
|
||||
frm.set_value('pickup_contact', contact_display);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
set_company_contact: function(frm, delivery_type) {
|
||||
frappe.db.get_value('User', { name: frappe.session.user }, ['full_name', 'last_name', 'email', 'phone', 'mobile_no'], (r) => {
|
||||
if (!(r.last_name && r.email && (r.phone || r.mobile_no))) {
|
||||
if (delivery_type == 'Delivery') {
|
||||
frm.set_value('delivery_company', '');
|
||||
frm.set_value('delivery_contact', '');
|
||||
} else {
|
||||
frm.set_value('pickup_company', '');
|
||||
frm.set_value('pickup_contact', '');
|
||||
}
|
||||
frappe.throw(__("Last Name, Email or Phone/Mobile of the user are mandatory to continue.") + "</br>" + __("Please first set Last Name, Email and Phone for the user") + ` <a href="#Form/User/${frappe.session.user}">${frappe.session.user}</a>`);
|
||||
}
|
||||
let contact_display = r.full_name;
|
||||
if (r.email) {
|
||||
contact_display += '<br>' + r.email;
|
||||
}
|
||||
if (r.phone) {
|
||||
contact_display += '<br>' + r.phone;
|
||||
}
|
||||
if (r.mobile_no && !r.phone) {
|
||||
contact_display += '<br>' + r.mobile_no;
|
||||
}
|
||||
if (delivery_type == 'Delivery') {
|
||||
frm.set_value('delivery_contact', contact_display);
|
||||
if (r.email) {
|
||||
frm.set_value('delivery_contact_email', r.email);
|
||||
}
|
||||
} else {
|
||||
frm.set_value('pickup_contact', contact_display);
|
||||
if (r.email) {
|
||||
frm.set_value('pickup_contact_email', r.email);
|
||||
}
|
||||
}
|
||||
});
|
||||
frm.set_value('pickup_contact_person', frappe.session.user);
|
||||
},
|
||||
pickup_company: function(frm) {
|
||||
if (frm.doc.pickup_from_type == 'Company' && frm.doc.pickup_company) {
|
||||
frm.trigger('set_pickup_company_address');
|
||||
frm.events.set_company_contact(frm, 'Pickup');
|
||||
}
|
||||
},
|
||||
delivery_company: function(frm) {
|
||||
if (frm.doc.delivery_to_type == 'Company' && frm.doc.delivery_company) {
|
||||
frm.trigger('set_delivery_company_address');
|
||||
frm.events.set_company_contact(frm, 'Delivery');
|
||||
}
|
||||
},
|
||||
delivery_customer: function(frm) {
|
||||
frm.trigger('clear_delivery_fields');
|
||||
if (frm.doc.delivery_customer) {
|
||||
frm.events.set_address_name(frm, 'Customer', frm.doc.delivery_customer, 'Delivery');
|
||||
frm.events.set_contact_name(frm, 'Customer', frm.doc.delivery_customer, 'Delivery');
|
||||
}
|
||||
},
|
||||
delivery_supplier: function(frm) {
|
||||
frm.trigger('clear_delivery_fields');
|
||||
if (frm.doc.delivery_supplier) {
|
||||
frm.events.set_address_name(frm, 'Supplier', frm.doc.delivery_supplier, 'Delivery');
|
||||
frm.events.set_contact_name(frm, 'Supplier', frm.doc.delivery_supplier, 'Delivery');
|
||||
}
|
||||
},
|
||||
pickup_customer: function(frm) {
|
||||
if (frm.doc.pickup_customer) {
|
||||
frm.events.set_address_name(frm, 'Customer', frm.doc.pickup_customer, 'Pickup');
|
||||
frm.events.set_contact_name(frm, 'Customer', frm.doc.pickup_customer, 'Pickup');
|
||||
}
|
||||
},
|
||||
pickup_supplier: function(frm) {
|
||||
if (frm.doc.pickup_supplier) {
|
||||
frm.events.set_address_name(frm, 'Supplier', frm.doc.pickup_supplier, 'Pickup');
|
||||
frm.events.set_contact_name(frm, 'Supplier', frm.doc.pickup_supplier, 'Pickup');
|
||||
}
|
||||
},
|
||||
set_address_name: function(frm, ref_doctype, ref_docname, delivery_type) {
|
||||
frappe.call({
|
||||
method: "erpnext.stock.doctype.shipment.shipment.get_address_name",
|
||||
args: {
|
||||
ref_doctype: ref_doctype,
|
||||
docname: ref_docname
|
||||
},
|
||||
callback: function(r) {
|
||||
if (r.message) {
|
||||
if (delivery_type == 'Delivery') {
|
||||
frm.set_value('delivery_address_name', r.message);
|
||||
} else {
|
||||
frm.set_value('pickup_address_name', r.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
set_contact_name: function(frm, ref_doctype, ref_docname, delivery_type) {
|
||||
frappe.call({
|
||||
method: "erpnext.stock.doctype.shipment.shipment.get_contact_name",
|
||||
args: {
|
||||
ref_doctype: ref_doctype,
|
||||
docname: ref_docname
|
||||
},
|
||||
callback: function(r) {
|
||||
if (r.message) {
|
||||
if (delivery_type == 'Delivery') {
|
||||
frm.set_value('delivery_contact_name', r.message);
|
||||
} else {
|
||||
frm.set_value('pickup_contact_name', r.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
add_template: function(frm) {
|
||||
if (frm.doc.parcel_template) {
|
||||
frappe.model.with_doc("Shipment Parcel Template", frm.doc.parcel_template, () => {
|
||||
let parcel_template = frappe.model.get_doc("Shipment Parcel Template", frm.doc.parcel_template);
|
||||
let row = frappe.model.add_child(frm.doc, "Shipment Parcel", "shipment_parcel");
|
||||
row.length = parcel_template.length;
|
||||
row.width = parcel_template.width;
|
||||
row.height = parcel_template.height;
|
||||
row.weight = parcel_template.weight;
|
||||
frm.refresh_fields("shipment_parcel");
|
||||
});
|
||||
}
|
||||
},
|
||||
pickup_date: function(frm) {
|
||||
if (frm.doc.pickup_date < frappe.datetime.get_today()) {
|
||||
frappe.throw(__("Pickup Date cannot be before this day"));
|
||||
}
|
||||
if (frm.doc.pickup_date == frappe.datetime.get_today()) {
|
||||
var pickup_time = frm.events.get_pickup_time(frm);
|
||||
frm.set_value("pickup_from", pickup_time);
|
||||
frm.trigger('set_pickup_to_time');
|
||||
}
|
||||
},
|
||||
pickup_from: function(frm) {
|
||||
var pickup_time = frm.events.get_pickup_time(frm);
|
||||
if (frm.doc.pickup_from && frm.doc.pickup_date == frappe.datetime.get_today()) {
|
||||
let current_hour = pickup_time.split(':')[0];
|
||||
let current_min = pickup_time.split(':')[1];
|
||||
let pickup_hour = frm.doc.pickup_from.split(':')[0];
|
||||
let pickup_min = frm.doc.pickup_from.split(':')[1];
|
||||
if (pickup_hour < current_hour || (pickup_hour == current_hour && pickup_min < current_min)) {
|
||||
frm.set_value("pickup_from", pickup_time);
|
||||
frappe.throw(__("Pickup Time cannot be in the past"));
|
||||
}
|
||||
}
|
||||
frm.trigger('set_pickup_to_time');
|
||||
},
|
||||
get_pickup_time: function() {
|
||||
let current_hour = new Date().getHours();
|
||||
let current_min = new Date().toLocaleString('en-US', {minute: 'numeric'});
|
||||
if (current_min < 30) {
|
||||
current_min = '30';
|
||||
} else {
|
||||
current_min = '00';
|
||||
current_hour = Number(current_hour)+1;
|
||||
}
|
||||
let pickup_time = current_hour +':'+ current_min;
|
||||
return pickup_time;
|
||||
},
|
||||
set_pickup_to_time: function(frm) {
|
||||
let pickup_to_hour = Number(frm.doc.pickup_from.split(':')[0])+5;
|
||||
let pickup_to_min = frm.doc.pickup_from.split(':')[1];
|
||||
let pickup_to = pickup_to_hour +':'+ pickup_to_min;
|
||||
frm.set_value("pickup_to", pickup_to);
|
||||
},
|
||||
clear_pickup_fields: function(frm) {
|
||||
let fields = ["pickup_address_name", "pickup_contact_name", "pickup_address", "pickup_contact", "pickup_contact_email", "pickup_contact_person"];
|
||||
for (let field of fields) {
|
||||
frm.set_value(field, '');
|
||||
}
|
||||
},
|
||||
clear_delivery_fields: function(frm) {
|
||||
let fields = ["delivery_address_name", "delivery_contact_name", "delivery_address", "delivery_contact", "delivery_contact_email"];
|
||||
for (let field of fields) {
|
||||
frm.set_value(field, '');
|
||||
}
|
||||
},
|
||||
remove_email_row: function(frm, table, fieldname) {
|
||||
$.each(frm.doc[table] || [], function(i, detail) {
|
||||
if (detail.email === fieldname) {
|
||||
cur_frm.get_field(table).grid.grid_rows[i].remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
frappe.ui.form.on('Shipment Delivery Note', {
|
||||
delivery_note: function(frm, cdt, cdn) {
|
||||
let row = locals[cdt][cdn];
|
||||
if (row.delivery_note) {
|
||||
let row_index = row.idx - 1;
|
||||
if (validate_duplicate(frm, 'shipment_delivery_note', row.delivery_note, row_index)) {
|
||||
frappe.throw(__("You have entered a duplicate Delivery Note on Row") + ` ${row.idx}. ` + __("Please rectify and try again."));
|
||||
}
|
||||
}
|
||||
},
|
||||
grand_total: function(frm, cdt, cdn) {
|
||||
let row = locals[cdt][cdn];
|
||||
if (row.grand_total) {
|
||||
var value_of_goods = parseFloat(frm.doc.value_of_goods)+parseFloat(row.grand_total);
|
||||
frm.set_value("value_of_goods", Math.round(value_of_goods));
|
||||
frm.refresh_fields("value_of_goods");
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
var validate_duplicate = function(frm, table, fieldname, index) {
|
||||
return (
|
||||
table === 'shipment_delivery_note'
|
||||
? frm.doc[table].some((detail, i) => detail.delivery_note === fieldname && !(index === i))
|
||||
: frm.doc[table].some((detail, i) => detail.email === fieldname && !(index === i))
|
||||
);
|
||||
};
|
471
erpnext/stock/doctype/shipment/shipment.json
Normal file
471
erpnext/stock/doctype/shipment/shipment.json
Normal file
@ -0,0 +1,471 @@
|
||||
{
|
||||
"actions": [],
|
||||
"autoname": "SHIPMENT-.#####",
|
||||
"creation": "2020-07-09 10:58:52.508703",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"heading_pickup_from",
|
||||
"pickup_from_type",
|
||||
"pickup_company",
|
||||
"pickup_customer",
|
||||
"pickup_supplier",
|
||||
"pickup",
|
||||
"pickup_address_name",
|
||||
"pickup_address",
|
||||
"pickup_contact_person",
|
||||
"pickup_contact_name",
|
||||
"pickup_contact_email",
|
||||
"pickup_contact",
|
||||
"column_break_2",
|
||||
"heading_delivery_to",
|
||||
"delivery_to_type",
|
||||
"delivery_company",
|
||||
"delivery_customer",
|
||||
"delivery_supplier",
|
||||
"delivery_to",
|
||||
"delivery_address_name",
|
||||
"delivery_address",
|
||||
"delivery_contact_name",
|
||||
"delivery_contact_email",
|
||||
"delivery_contact",
|
||||
"parcels_section",
|
||||
"shipment_parcel",
|
||||
"parcel_template",
|
||||
"add_template",
|
||||
"column_break_28",
|
||||
"shipment_delivery_note",
|
||||
"shipment_details_section",
|
||||
"pallets",
|
||||
"value_of_goods",
|
||||
"pickup_date",
|
||||
"pickup_from",
|
||||
"pickup_to",
|
||||
"column_break_36",
|
||||
"shipment_type",
|
||||
"pickup_type",
|
||||
"incoterm",
|
||||
"description_of_content",
|
||||
"section_break_40",
|
||||
"shipment_information_section",
|
||||
"service_provider",
|
||||
"shipment_id",
|
||||
"shipment_amount",
|
||||
"status",
|
||||
"tracking_url",
|
||||
"column_break_55",
|
||||
"carrier",
|
||||
"carrier_service",
|
||||
"awb_number",
|
||||
"tracking_status",
|
||||
"tracking_status_info",
|
||||
"amended_from"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "heading_pickup_from",
|
||||
"fieldtype": "Heading",
|
||||
"label": "Pickup from"
|
||||
},
|
||||
{
|
||||
"default": "Company",
|
||||
"fieldname": "pickup_from_type",
|
||||
"fieldtype": "Select",
|
||||
"label": "Pickup from",
|
||||
"options": "Company\nCustomer\nSupplier"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.pickup_from_type == 'Company'",
|
||||
"fieldname": "pickup_company",
|
||||
"fieldtype": "Link",
|
||||
"label": "Company",
|
||||
"options": "Company"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.pickup_from_type == 'Customer'",
|
||||
"fieldname": "pickup_customer",
|
||||
"fieldtype": "Link",
|
||||
"label": "Customer",
|
||||
"options": "Customer"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.pickup_from_type == 'Supplier'",
|
||||
"fieldname": "pickup_supplier",
|
||||
"fieldtype": "Link",
|
||||
"label": "Supplier",
|
||||
"options": "Supplier"
|
||||
},
|
||||
{
|
||||
"fieldname": "pickup",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Pickup From",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.pickup_customer || doc.pickup_supplier || doc.pickup_from_type == \"Company\"",
|
||||
"fieldname": "pickup_address_name",
|
||||
"fieldtype": "Link",
|
||||
"label": "Address",
|
||||
"options": "Address",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "pickup_address",
|
||||
"fieldtype": "Small Text",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.pickup_customer || doc.pickup_supplier || doc.pickup_from_type !== \"Company\"",
|
||||
"fieldname": "pickup_contact_name",
|
||||
"fieldtype": "Link",
|
||||
"label": "Contact",
|
||||
"mandatory_depends_on": "eval: doc.pickup_from_type !== 'Company'",
|
||||
"options": "Contact"
|
||||
},
|
||||
{
|
||||
"fieldname": "pickup_contact_email",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"label": "Contact Email",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "pickup_contact",
|
||||
"fieldtype": "Small Text",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_2",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "heading_delivery_to",
|
||||
"fieldtype": "Heading",
|
||||
"label": "Delivery to"
|
||||
},
|
||||
{
|
||||
"default": "Customer",
|
||||
"fieldname": "delivery_to_type",
|
||||
"fieldtype": "Select",
|
||||
"label": "Delivery to",
|
||||
"options": "Company\nCustomer\nSupplier"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.delivery_to_type == 'Company'",
|
||||
"fieldname": "delivery_company",
|
||||
"fieldtype": "Link",
|
||||
"label": "Company",
|
||||
"options": "Company"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.delivery_to_type == 'Customer'",
|
||||
"fieldname": "delivery_customer",
|
||||
"fieldtype": "Link",
|
||||
"label": "Customer",
|
||||
"options": "Customer"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.delivery_to_type == 'Supplier'",
|
||||
"fieldname": "delivery_supplier",
|
||||
"fieldtype": "Link",
|
||||
"label": "Supplier",
|
||||
"options": "Supplier"
|
||||
},
|
||||
{
|
||||
"fieldname": "delivery_to",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Delivery To",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.delivery_customer || doc.delivery_supplier || doc.delivery_to_type == \"Company\"",
|
||||
"fieldname": "delivery_address_name",
|
||||
"fieldtype": "Link",
|
||||
"label": "Address",
|
||||
"options": "Address",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "delivery_address",
|
||||
"fieldtype": "Small Text",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.delivery_customer || doc.delivery_supplier || doc.delivery_to_type == \"Company\"",
|
||||
"fieldname": "delivery_contact_name",
|
||||
"fieldtype": "Link",
|
||||
"label": "Contact",
|
||||
"mandatory_depends_on": "eval: doc.delivery_from_type !== 'Company'",
|
||||
"options": "Contact"
|
||||
},
|
||||
{
|
||||
"fieldname": "delivery_contact_email",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"label": "Contact Email",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.delivery_contact_name",
|
||||
"fieldname": "delivery_contact",
|
||||
"fieldtype": "Small Text",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "parcels_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Parcels"
|
||||
},
|
||||
{
|
||||
"fieldname": "shipment_parcel",
|
||||
"fieldtype": "Table",
|
||||
"label": "Shipment Parcel",
|
||||
"options": "Shipment Parcel"
|
||||
},
|
||||
{
|
||||
"fieldname": "parcel_template",
|
||||
"fieldtype": "Link",
|
||||
"label": "Parcel Template",
|
||||
"options": "Shipment Parcel Template"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.docstatus !== 1\n",
|
||||
"fieldname": "add_template",
|
||||
"fieldtype": "Button",
|
||||
"label": "Add Template"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_28",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "shipment_details_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Shipment details"
|
||||
},
|
||||
{
|
||||
"default": "No",
|
||||
"fieldname": "pallets",
|
||||
"fieldtype": "Select",
|
||||
"label": "Pallets",
|
||||
"options": "No\nYes"
|
||||
},
|
||||
{
|
||||
"fieldname": "value_of_goods",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Value of Goods",
|
||||
"precision": "2",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "pickup_date",
|
||||
"fieldtype": "Date",
|
||||
"in_list_view": 1,
|
||||
"label": "Pickup Date",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"default": "09:00",
|
||||
"fieldname": "pickup_from",
|
||||
"fieldtype": "Time",
|
||||
"label": "Pickup from"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"default": "17:00",
|
||||
"fieldname": "pickup_to",
|
||||
"fieldtype": "Time",
|
||||
"label": "Pickup to"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_36",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "Goods",
|
||||
"fieldname": "shipment_type",
|
||||
"fieldtype": "Select",
|
||||
"label": "Shipment Type",
|
||||
"options": "Goods\nDocuments"
|
||||
},
|
||||
{
|
||||
"default": "Pickup",
|
||||
"fieldname": "pickup_type",
|
||||
"fieldtype": "Select",
|
||||
"label": "Pickup Type",
|
||||
"options": "Pickup\nSelf delivery"
|
||||
},
|
||||
{
|
||||
"fieldname": "description_of_content",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Description of Content",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_40",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "shipment_information_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Shipment Information"
|
||||
},
|
||||
{
|
||||
"fieldname": "service_provider",
|
||||
"fieldtype": "Data",
|
||||
"label": "Service Provider",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "shipment_id",
|
||||
"fieldtype": "Data",
|
||||
"label": "Shipment ID",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "shipment_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Shipment Amount",
|
||||
"no_copy": 1,
|
||||
"precision": "2",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "status",
|
||||
"fieldtype": "Select",
|
||||
"label": "Status",
|
||||
"no_copy": 1,
|
||||
"options": "Draft\nSubmitted\nBooked\nCancelled\nCompleted",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "tracking_url",
|
||||
"fieldtype": "Small Text",
|
||||
"hidden": 1,
|
||||
"label": "Tracking URL",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "carrier",
|
||||
"fieldtype": "Data",
|
||||
"label": "Carrier",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "carrier_service",
|
||||
"fieldtype": "Data",
|
||||
"label": "Carrier Service",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "awb_number",
|
||||
"fieldtype": "Data",
|
||||
"label": "AWB Number",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "tracking_status",
|
||||
"fieldtype": "Select",
|
||||
"label": "Tracking Status",
|
||||
"no_copy": 1,
|
||||
"options": "\nIn Progress\nDelivered\nReturned\nLost",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "tracking_status_info",
|
||||
"fieldtype": "Data",
|
||||
"label": "Tracking Status Info",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "amended_from",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 1,
|
||||
"label": "Amended From",
|
||||
"no_copy": 1,
|
||||
"options": "Shipment",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_55",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "incoterm",
|
||||
"fieldtype": "Select",
|
||||
"label": "Incoterm",
|
||||
"options": "EXW (Ex Works)\nFCA (Free Carrier)\nCPT (Carriage Paid To)\nCIP (Carriage and Insurance Paid to)\nDPU (Delivered At Place Unloaded)\nDAP (Delivered At Place)\nDDP (Delivered Duty Paid)"
|
||||
},
|
||||
{
|
||||
"fieldname": "shipment_delivery_note",
|
||||
"fieldtype": "Table",
|
||||
"label": "Shipment Delivery Note",
|
||||
"options": "Shipment Delivery Note"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.pickup_from_type === 'Company'",
|
||||
"fieldname": "pickup_contact_person",
|
||||
"fieldtype": "Link",
|
||||
"label": "Pickup Contact Person",
|
||||
"mandatory_depends_on": "eval:doc.pickup_from_type === 'Company'",
|
||||
"options": "User"
|
||||
}
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-12-02 15:43:44.607039",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Shipment",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"cancel": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Stock Manager",
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"cancel": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
63
erpnext/stock/doctype/shipment/shipment.py
Normal file
63
erpnext/stock/doctype/shipment/shipment.py
Normal file
@ -0,0 +1,63 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import flt
|
||||
from frappe.model.document import Document
|
||||
from erpnext.accounts.party import get_party_shipping_address
|
||||
from frappe.contacts.doctype.contact.contact import get_default_contact
|
||||
|
||||
class Shipment(Document):
|
||||
def validate(self):
|
||||
self.validate_weight()
|
||||
self.set_value_of_goods()
|
||||
if self.docstatus == 0:
|
||||
self.status = 'Draft'
|
||||
|
||||
def on_submit(self):
|
||||
if not self.shipment_parcel:
|
||||
frappe.throw(_('Please enter Shipment Parcel information'))
|
||||
if self.value_of_goods == 0:
|
||||
frappe.throw(_('Value of goods cannot be 0'))
|
||||
self.status = 'Submitted'
|
||||
|
||||
def on_cancel(self):
|
||||
self.status = 'Cancelled'
|
||||
|
||||
def validate_weight(self):
|
||||
for parcel in self.shipment_parcel:
|
||||
if flt(parcel.weight) <= 0:
|
||||
frappe.throw(_('Parcel weight cannot be 0'))
|
||||
|
||||
def set_value_of_goods(self):
|
||||
value_of_goods = 0
|
||||
for entry in self.get("shipment_delivery_note"):
|
||||
value_of_goods += flt(entry.get("grand_total"))
|
||||
self.value_of_goods = value_of_goods if value_of_goods else self.value_of_goods
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_address_name(ref_doctype, docname):
|
||||
# Return address name
|
||||
return get_party_shipping_address(ref_doctype, docname)
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_contact_name(ref_doctype, docname):
|
||||
# Return address name
|
||||
return get_default_contact(ref_doctype, docname)
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_company_contact(user):
|
||||
contact = frappe.db.get_value('User', user, [
|
||||
'first_name',
|
||||
'last_name',
|
||||
'email',
|
||||
'phone',
|
||||
'mobile_no',
|
||||
'gender',
|
||||
], as_dict=1)
|
||||
if not contact.phone:
|
||||
contact.phone = contact.mobile_no
|
||||
return contact
|
8
erpnext/stock/doctype/shipment/shipment_list.js
Normal file
8
erpnext/stock/doctype/shipment/shipment_list.js
Normal file
@ -0,0 +1,8 @@
|
||||
frappe.listview_settings['Shipment'] = {
|
||||
add_fields: ["status"],
|
||||
get_indicator: function(doc) {
|
||||
if (doc.status=='Booked') {
|
||||
return [__("Booked"), "green"];
|
||||
}
|
||||
}
|
||||
};
|
240
erpnext/stock/doctype/shipment/test_shipment.py
Normal file
240
erpnext/stock/doctype/shipment/test_shipment.py
Normal file
@ -0,0 +1,240 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
from datetime import date, timedelta
|
||||
|
||||
import frappe
|
||||
import unittest
|
||||
from erpnext.stock.doctype.delivery_note.delivery_note import make_shipment
|
||||
|
||||
class TestShipment(unittest.TestCase):
|
||||
def test_shipment_from_delivery_note(self):
|
||||
delivery_note = create_test_delivery_note()
|
||||
delivery_note.submit()
|
||||
shipment = create_test_shipment([ delivery_note ])
|
||||
shipment.submit()
|
||||
second_shipment = make_shipment(delivery_note.name)
|
||||
self.assertEqual(second_shipment.value_of_goods, delivery_note.grand_total)
|
||||
self.assertEqual(len(second_shipment.shipment_delivery_note), 1)
|
||||
self.assertEqual(second_shipment.shipment_delivery_note[0].delivery_note, delivery_note.name)
|
||||
|
||||
def create_test_delivery_note():
|
||||
company = get_shipment_company()
|
||||
customer = get_shipment_customer()
|
||||
item = get_shipment_item(company.name)
|
||||
posting_date = date.today() + timedelta(days=1)
|
||||
|
||||
create_material_receipt(item, company.name)
|
||||
delivery_note = frappe.new_doc("Delivery Note")
|
||||
delivery_note.company = company.name
|
||||
delivery_note.posting_date = posting_date.strftime("%Y-%m-%d")
|
||||
delivery_note.posting_time = '10:00'
|
||||
delivery_note.customer = customer.name
|
||||
delivery_note.append('items',
|
||||
{
|
||||
"item_code": item.name,
|
||||
"item_name": item.item_name,
|
||||
"description": 'Test delivery note for shipment',
|
||||
"qty": 5,
|
||||
"uom": 'Nos',
|
||||
"warehouse": 'Stores - SC',
|
||||
"rate": item.standard_rate,
|
||||
"cost_center": 'Main - SC'
|
||||
}
|
||||
)
|
||||
delivery_note.insert()
|
||||
frappe.db.commit()
|
||||
return delivery_note
|
||||
|
||||
|
||||
def create_test_shipment(delivery_notes = None):
|
||||
company = get_shipment_company()
|
||||
company_address = get_shipment_company_address(company.name)
|
||||
customer = get_shipment_customer()
|
||||
customer_address = get_shipment_customer_address(customer.name)
|
||||
customer_contact = get_shipment_customer_contact(customer.name)
|
||||
posting_date = date.today() + timedelta(days=5)
|
||||
|
||||
shipment = frappe.new_doc("Shipment")
|
||||
shipment.pickup_from_type = 'Company'
|
||||
shipment.pickup_company = company.name
|
||||
shipment.pickup_address_name = company_address.name
|
||||
shipment.delivery_to_type = 'Customer'
|
||||
shipment.delivery_customer = customer.name
|
||||
shipment.delivery_address_name = customer_address.name
|
||||
shipment.delivery_contact_name = customer_contact.name
|
||||
shipment.pallets = 'No'
|
||||
shipment.shipment_type = 'Goods'
|
||||
shipment.value_of_goods = 1000
|
||||
shipment.pickup_type = 'Pickup'
|
||||
shipment.pickup_date = posting_date.strftime("%Y-%m-%d")
|
||||
shipment.pickup_from = '09:00'
|
||||
shipment.pickup_to = '17:00'
|
||||
shipment.description_of_content = 'unit test entry'
|
||||
for delivery_note in delivery_notes:
|
||||
shipment.append('shipment_delivery_note',
|
||||
{
|
||||
"delivery_note": delivery_note.name
|
||||
}
|
||||
)
|
||||
shipment.append('shipment_parcel',
|
||||
{
|
||||
"length": 5,
|
||||
"width": 5,
|
||||
"height": 5,
|
||||
"weight": 5,
|
||||
"count": 5
|
||||
}
|
||||
)
|
||||
shipment.insert()
|
||||
frappe.db.commit()
|
||||
return shipment
|
||||
|
||||
|
||||
def get_shipment_customer_contact(customer_name):
|
||||
contact_fname = 'Customer Shipment'
|
||||
contact_lname = 'Testing'
|
||||
customer_name = contact_fname + ' ' + contact_lname
|
||||
contacts = frappe.get_all("Contact", fields=["name"], filters = {"name": customer_name})
|
||||
if len(contacts):
|
||||
return contacts[0]
|
||||
else:
|
||||
return create_customer_contact(contact_fname, contact_lname)
|
||||
|
||||
|
||||
def get_shipment_customer_address(customer_name):
|
||||
address_title = customer_name + ' address 123'
|
||||
customer_address = frappe.get_all("Address", fields=["name"], filters = {"address_title": address_title})
|
||||
if len(customer_address):
|
||||
return customer_address[0]
|
||||
else:
|
||||
return create_shipment_address(address_title, customer_name, 81929)
|
||||
|
||||
def get_shipment_customer():
|
||||
customer_name = 'Shipment Customer'
|
||||
customer = frappe.get_all("Customer", fields=["name"], filters = {"name": customer_name})
|
||||
if len(customer):
|
||||
return customer[0]
|
||||
else:
|
||||
return create_shipment_customer(customer_name)
|
||||
|
||||
def get_shipment_company_address(company_name):
|
||||
address_title = company_name + ' address 123'
|
||||
addresses = frappe.get_all("Address", fields=["name"], filters = {"address_title": address_title})
|
||||
if len(addresses):
|
||||
return addresses[0]
|
||||
else:
|
||||
return create_shipment_address(address_title, company_name, 80331)
|
||||
|
||||
def get_shipment_company():
|
||||
company_name = 'Shipment Company'
|
||||
abbr = 'SC'
|
||||
companies = frappe.get_all("Company", fields=["name"], filters = {"company_name": company_name})
|
||||
if len(companies):
|
||||
return companies[0]
|
||||
else:
|
||||
return create_shipment_company(company_name, abbr)
|
||||
|
||||
def get_shipment_item(company_name):
|
||||
item_name = 'Testing Shipment item'
|
||||
items = frappe.get_all("Item",
|
||||
fields=["name", "item_name", "item_code", "standard_rate"],
|
||||
filters = {"item_name": item_name}
|
||||
)
|
||||
if len(items):
|
||||
return items[0]
|
||||
else:
|
||||
return create_shipment_item(item_name, company_name)
|
||||
|
||||
def create_shipment_address(address_title, company_name, postal_code):
|
||||
address = frappe.new_doc("Address")
|
||||
address.address_title = address_title
|
||||
address.address_type = 'Shipping'
|
||||
address.address_line1 = company_name + ' address line 1'
|
||||
address.city = 'Random City'
|
||||
address.postal_code = postal_code
|
||||
address.country = 'Germany'
|
||||
address.insert()
|
||||
return address
|
||||
|
||||
|
||||
def create_customer_contact(fname, lname):
|
||||
customer = frappe.new_doc("Contact")
|
||||
customer.customer_name = fname + ' ' + lname
|
||||
customer.first_name = fname
|
||||
customer.last_name = lname
|
||||
customer.is_primary_contact = 1
|
||||
customer.is_billing_contact = 1
|
||||
customer.append('email_ids',
|
||||
{
|
||||
'email_id': 'randomme@email.com',
|
||||
'is_primary': 1
|
||||
}
|
||||
)
|
||||
customer.append('phone_nos',
|
||||
{
|
||||
'phone': '123123123',
|
||||
'is_primary_phone': 1,
|
||||
'is_primary_mobile_no': 1
|
||||
}
|
||||
)
|
||||
customer.status = 'Passive'
|
||||
customer.insert()
|
||||
return customer
|
||||
|
||||
|
||||
def create_shipment_company(company_name, abbr):
|
||||
company = frappe.new_doc("Company")
|
||||
company.company_name = company_name
|
||||
company.abbr = abbr
|
||||
company.default_currency = 'EUR'
|
||||
company.country = 'Germany'
|
||||
company.insert()
|
||||
return company
|
||||
|
||||
def create_shipment_customer(customer_name):
|
||||
customer = frappe.new_doc("Customer")
|
||||
customer.customer_name = customer_name
|
||||
customer.customer_type = 'Company'
|
||||
customer.customer_group = 'All Customer Groups'
|
||||
customer.territory = 'All Territories'
|
||||
customer.gst_category = 'Unregistered'
|
||||
customer.insert()
|
||||
return customer
|
||||
|
||||
def create_material_receipt(item, company):
|
||||
posting_date = date.today()
|
||||
stock = frappe.new_doc("Stock Entry")
|
||||
stock.company = company
|
||||
stock.stock_entry_type = 'Material Receipt'
|
||||
stock.posting_date = posting_date.strftime("%Y-%m-%d")
|
||||
stock.append('items',
|
||||
{
|
||||
"t_warehouse": 'Stores - SC',
|
||||
"item_code": item.name,
|
||||
"qty": 5,
|
||||
"uom": 'Nos',
|
||||
"basic_rate": item.standard_rate,
|
||||
"cost_center": 'Main - SC'
|
||||
}
|
||||
)
|
||||
stock.insert()
|
||||
stock.submit()
|
||||
|
||||
|
||||
def create_shipment_item(item_name, company_name):
|
||||
item = frappe.new_doc("Item")
|
||||
item.item_name = item_name
|
||||
item.item_code = item_name
|
||||
item.item_group = 'All Item Groups'
|
||||
item.stock_uom = 'Nos'
|
||||
item.standard_rate = 50
|
||||
item.append('item_defaults',
|
||||
{
|
||||
"company": company_name,
|
||||
"default_warehouse": 'Stores - SC'
|
||||
}
|
||||
)
|
||||
item.insert()
|
||||
return item
|
@ -0,0 +1,40 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2020-07-09 11:52:57.939021",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"delivery_note",
|
||||
"grand_total"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "delivery_note",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Delivery Note",
|
||||
"options": "Delivery Note",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "grand_total",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Value",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-12-02 15:44:34.028703",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Shipment Delivery Note",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class ShipmentDeliveryNote(Document):
|
||||
pass
|
0
erpnext/stock/doctype/shipment_parcel/__init__.py
Normal file
0
erpnext/stock/doctype/shipment_parcel/__init__.py
Normal file
65
erpnext/stock/doctype/shipment_parcel/shipment_parcel.json
Normal file
65
erpnext/stock/doctype/shipment_parcel/shipment_parcel.json
Normal file
@ -0,0 +1,65 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2020-07-09 11:28:48.887737",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"length",
|
||||
"width",
|
||||
"height",
|
||||
"weight",
|
||||
"count"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "length",
|
||||
"fieldtype": "Int",
|
||||
"in_list_view": 1,
|
||||
"label": "Length (cm)",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "width",
|
||||
"fieldtype": "Int",
|
||||
"in_list_view": 1,
|
||||
"label": "Width (cm)",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "height",
|
||||
"fieldtype": "Int",
|
||||
"in_list_view": 1,
|
||||
"label": "Height (cm)",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "weight",
|
||||
"fieldtype": "Float",
|
||||
"in_list_view": 1,
|
||||
"label": "Weight (kg)",
|
||||
"precision": "1",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "count",
|
||||
"fieldtype": "Int",
|
||||
"in_list_view": 1,
|
||||
"label": "Count",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-07-09 12:54:14.847170",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Shipment Parcel",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
10
erpnext/stock/doctype/shipment_parcel/shipment_parcel.py
Normal file
10
erpnext/stock/doctype/shipment_parcel/shipment_parcel.py
Normal file
@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class ShipmentParcel(Document):
|
||||
pass
|
@ -0,0 +1,8 @@
|
||||
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Shipment Parcel Template', {
|
||||
// refresh: function(frm) {
|
||||
|
||||
// }
|
||||
});
|
@ -0,0 +1,78 @@
|
||||
{
|
||||
"actions": [],
|
||||
"autoname": "field:parcel_template_name",
|
||||
"creation": "2020-07-09 11:43:43.470339",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"parcel_template_name",
|
||||
"length",
|
||||
"width",
|
||||
"height",
|
||||
"weight"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "length",
|
||||
"fieldtype": "Int",
|
||||
"in_list_view": 1,
|
||||
"label": "Length (cm)",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "width",
|
||||
"fieldtype": "Int",
|
||||
"in_list_view": 1,
|
||||
"label": "Width (cm)",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "height",
|
||||
"fieldtype": "Int",
|
||||
"in_list_view": 1,
|
||||
"label": "Height (cm)",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "weight",
|
||||
"fieldtype": "Float",
|
||||
"in_list_view": 1,
|
||||
"label": "Weight (kg)",
|
||||
"precision": "1",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "parcel_template_name",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Parcel Template Name",
|
||||
"reqd": 1,
|
||||
"unique": 1
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2020-09-28 12:51:00.320421",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Shipment Parcel Template",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class ShipmentParcelTemplate(Document):
|
||||
pass
|
@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
# import frappe
|
||||
import unittest
|
||||
|
||||
class TestShipmentParcelTemplate(unittest.TestCase):
|
||||
pass
|
@ -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};
|
||||
}
|
||||
|
||||
|
@ -20,10 +20,10 @@
|
||||
{%- if (charge.tax_amount or doc.flags.print_taxes_with_zero_amount) and (not charge.included_in_print_rate or doc.flags.show_inclusive_tax_in_print) -%}
|
||||
<div class="row">
|
||||
<div class="col-xs-5 {%- if doc.align_labels_right %} text-right{%- endif -%}">
|
||||
<label>{{ charge.get_formatted("description") }}</label></div>
|
||||
<label>{{ charge.get_formatted("description") }}</label>
|
||||
</div>
|
||||
<div class="col-xs-7 text-right">
|
||||
{{ frappe.format_value(frappe.utils.flt(charge.tax_amount),
|
||||
table_meta.get_field("tax_amount"), doc, currency=doc.currency) }}
|
||||
{{ charge.get_formatted('tax_amount', doc) }}
|
||||
</div>
|
||||
</div>
|
||||
{%- endif -%}
|
||||
|
Loading…
x
Reference in New Issue
Block a user