diff --git a/erpnext/__init__.py b/erpnext/__init__.py index f702dc90b46..41ee03971f8 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -5,7 +5,7 @@ import frappe from erpnext.hooks import regional_overrides from frappe.utils import getdate -__version__ = '12.2.2' +__version__ = '12.3.0' def get_default_company(user=None): '''Get default company for user''' diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py index cccced8e0be..cf1748f6a7f 100644 --- a/erpnext/accounts/doctype/account/account.py +++ b/erpnext/accounts/doctype/account/account.py @@ -109,12 +109,13 @@ class Account(NestedSet): if not descendants: return parent_acc_name_map = {} - parent_acc_name = frappe.db.get_value('Account', self.parent_account, "account_name") + parent_acc_name, parent_acc_number = frappe.db.get_value('Account', self.parent_account, \ + ["account_name", "account_number"]) for d in frappe.db.get_values('Account', - {"company": ["in", descendants], "account_name": parent_acc_name}, + { "company": ["in", descendants], "account_name": parent_acc_name, + "account_number": parent_acc_number }, ["company", "name"], as_dict=True): parent_acc_name_map[d["company"]] = d["name"] - if not parent_acc_name_map: return self.create_account_for_child_company(parent_acc_name_map, descendants, parent_acc_name) diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction_upload.py b/erpnext/accounts/doctype/bank_transaction/bank_transaction_upload.py index deedafdfb5d..33ae45439e7 100644 --- a/erpnext/accounts/doctype/bank_transaction/bank_transaction_upload.py +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction_upload.py @@ -15,8 +15,8 @@ def upload_bank_statement(): with open(frappe.uploaded_file, "rb") as upfile: fcontent = upfile.read() else: - from frappe.utils.file_manager import get_uploaded_content - fname, fcontent = get_uploaded_content() + fcontent = frappe.local.uploaded_file + fname = frappe.local.uploaded_filename if frappe.safe_encode(fname).lower().endswith("csv".encode('utf-8')): from frappe.utils.csvutils import read_csv_content diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js index 221e3a72803..d6236cdb04f 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.js +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js @@ -398,7 +398,7 @@ cur_frm.cscript.voucher_type = function(doc, cdt, cdn) { method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_default_bank_cash_account", args: { "account_type": (doc.voucher_type=="Bank Entry" ? - "Bank" : (doc.voucher_type=="Cash" ? "Cash" : null)), + "Bank" : (doc.voucher_type=="Cash Entry" ? "Cash" : null)), "company": doc.company }, callback: function(r) { diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index bf7e833285c..9530fc9556b 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -931,9 +931,9 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount= grand_total = doc.rounded_total or doc.grand_total outstanding_amount = doc.outstanding_amount elif dt in ("Expense Claim"): - grand_total = doc.total_sanctioned_amount - outstanding_amount = doc.total_sanctioned_amount \ - - doc.total_amount_reimbursed - flt(doc.total_advance_amount) + grand_total = doc.total_sanctioned_amount + doc.total_taxes_and_charges + outstanding_amount = doc.grand_total \ + - doc.total_amount_reimbursed elif dt == "Employee Advance": grand_total = doc.advance_amount outstanding_amount = flt(doc.advance_amount) - flt(doc.paid_amount) diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py index eda59abf04e..6133b1ccd4b 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.py +++ b/erpnext/accounts/doctype/payment_request/payment_request.py @@ -350,13 +350,13 @@ def get_amount(ref_doc): if dt in ["Sales Order", "Purchase Order"]: grand_total = flt(ref_doc.grand_total) - flt(ref_doc.advance_paid) - if dt in ["Sales Invoice", "Purchase Invoice"]: + elif dt in ["Sales Invoice", "Purchase Invoice"]: if ref_doc.party_account_currency == ref_doc.currency: grand_total = flt(ref_doc.outstanding_amount) else: grand_total = flt(ref_doc.outstanding_amount) / ref_doc.conversion_rate - if dt == "Fees": + elif dt == "Fees": grand_total = ref_doc.outstanding_amount if grand_total > 0 : diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json index 971d308368a..29d83783d07 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json @@ -389,8 +389,7 @@ "fieldname": "rate_or_discount", "fieldtype": "Select", "label": "Rate or Discount", - "options": "\nRate\nDiscount Percentage\nDiscount Amount", - "reqd": 1 + "options": "\nRate\nDiscount Percentage\nDiscount Amount" }, { "default": "Grand Total", @@ -439,19 +438,20 @@ }, { "default": "0", - "depends_on": "eval:!doc.mixed_conditions", + "depends_on": "eval:!doc.mixed_conditions && doc.apply_on != 'Transaction'", "fieldname": "same_item", "fieldtype": "Check", "label": "Same Item" }, { - "depends_on": "eval:!doc.same_item || doc.mixed_conditions", + "depends_on": "eval:(!doc.same_item || doc.apply_on == 'Transaction') || doc.mixed_conditions", "fieldname": "free_item", "fieldtype": "Link", "label": "Free Item", "options": "Item" }, { + "default": "0", "fieldname": "free_qty", "fieldtype": "Float", "label": "Qty" @@ -554,7 +554,7 @@ ], "icon": "fa fa-gift", "idx": 1, - "modified": "2019-10-15 12:39:40.399792", + "modified": "2019-12-18 17:29:22.957077", "modified_by": "Administrator", "module": "Accounts", "name": "Pricing Rule", diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py index efa7b5f1000..5fd933b3d98 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py @@ -48,6 +48,9 @@ class PricingRule(Document): if tocheck and not self.get(tocheck): throw(_("{0} is required").format(self.meta.get_label(tocheck)), frappe.MandatoryError) + if self.price_or_product_discount == 'Price' and not self.rate_or_discount: + throw(_("Rate or Discount is required for the price discount."), frappe.MandatoryError) + def validate_applicable_for_selling_or_buying(self): if not self.selling and not self.buying: throw(_("Atleast one of the Selling or Buying must be selected")) @@ -181,8 +184,9 @@ def get_serial_no_for_item(args): item_details.serial_no = get_serial_no(args) return item_details -def get_pricing_rule_for_item(args, price_list_rate=0, doc=None): - from erpnext.accounts.doctype.pricing_rule.utils import get_pricing_rules +def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=False): + from erpnext.accounts.doctype.pricing_rule.utils import (get_pricing_rules, + get_applied_pricing_rules, get_pricing_rule_items, get_product_discount_rule) if isinstance(doc, string_types): doc = json.loads(doc) @@ -209,6 +213,57 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None): item_details, args.get('item_code')) return item_details + update_args_for_pricing_rule(args) + + pricing_rules = (get_applied_pricing_rules(args) + if for_validate and args.get("pricing_rules") else get_pricing_rules(args, doc)) + + if pricing_rules: + rules = [] + + for pricing_rule in pricing_rules: + if not pricing_rule: continue + + if isinstance(pricing_rule, string_types): + pricing_rule = frappe.get_cached_doc("Pricing Rule", pricing_rule) + pricing_rule.apply_rule_on_other_items = get_pricing_rule_items(pricing_rule) + + if pricing_rule.get('suggestion'): continue + + item_details.validate_applied_rule = pricing_rule.get("validate_applied_rule", 0) + item_details.price_or_product_discount = pricing_rule.get("price_or_product_discount") + + rules.append(get_pricing_rule_details(args, pricing_rule)) + + if pricing_rule.mixed_conditions or pricing_rule.apply_rule_on_other: + item_details.update({ + 'apply_rule_on_other_items': json.dumps(pricing_rule.apply_rule_on_other_items), + 'apply_rule_on': (frappe.scrub(pricing_rule.apply_rule_on_other) + if pricing_rule.apply_rule_on_other else frappe.scrub(pricing_rule.get('apply_on'))) + }) + + if pricing_rule.coupon_code_based==1 and args.coupon_code==None: + return item_details + + if not pricing_rule.validate_applied_rule: + if pricing_rule.price_or_product_discount == "Price": + apply_price_discount_rule(pricing_rule, item_details, args) + else: + get_product_discount_rule(pricing_rule, item_details, doc) + + item_details.has_pricing_rule = 1 + + item_details.pricing_rules = ','.join([d.pricing_rule for d in rules]) + + if not doc: return item_details + + elif args.get("pricing_rules"): + item_details = remove_pricing_rule_for_item(args.get("pricing_rules"), + item_details, args.get('item_code')) + + return item_details + +def update_args_for_pricing_rule(args): if not (args.item_group and args.brand): try: args.item_group, args.brand = frappe.get_cached_value("Item", args.item_code, ["item_group", "brand"]) @@ -235,57 +290,16 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None): args.supplier_group = frappe.get_cached_value("Supplier", args.supplier, "supplier_group") args.customer = args.customer_group = args.territory = None - pricing_rules = get_pricing_rules(args, doc) - - if pricing_rules: - rules = [] - - for pricing_rule in pricing_rules: - if not pricing_rule or pricing_rule.get('suggestion'): continue - - item_details.validate_applied_rule = pricing_rule.get("validate_applied_rule", 0) - - rules.append(get_pricing_rule_details(args, pricing_rule)) - if pricing_rule.mixed_conditions or pricing_rule.apply_rule_on_other: - item_details.apply_rule_on_other_items = 1 - continue - - if pricing_rule.coupon_code_based==1 and args.coupon_code==None: - return item_details - - if (not pricing_rule.validate_applied_rule and - pricing_rule.price_or_product_discount == "Price"): - apply_price_discount_pricing_rule(pricing_rule, item_details, args) - - item_details.has_pricing_rule = 1 - - # if discount is applied on the rate and not on price list rate - # if price_list_rate: - # set_discount_amount(price_list_rate, item_details) - - item_details.pricing_rules = ','.join([d.pricing_rule for d in rules]) - - if not doc: return item_details - - for rule in rules: - doc.append('pricing_rules', rule) - - elif args.get("pricing_rules"): - item_details = remove_pricing_rule_for_item(args.get("pricing_rules"), - item_details, args.get('item_code')) - - return item_details - def get_pricing_rule_details(args, pricing_rule): return frappe._dict({ 'pricing_rule': pricing_rule.name, 'rate_or_discount': pricing_rule.rate_or_discount, 'margin_type': pricing_rule.margin_type, - 'item_code': pricing_rule.item_code or args.get("item_code"), + 'item_code': args.get("item_code"), 'child_docname': args.get('child_docname') }) -def apply_price_discount_pricing_rule(pricing_rule, item_details, args): +def apply_price_discount_rule(pricing_rule, item_details, args): item_details.pricing_rule_for = pricing_rule.rate_or_discount if ((pricing_rule.margin_type == 'Amount' and pricing_rule.currency == args.currency) @@ -328,10 +342,10 @@ def set_discount_amount(rate, item_details): item_details.rate = rate def remove_pricing_rule_for_item(pricing_rules, item_details, item_code=None): - from erpnext.accounts.doctype.pricing_rule.utils import get_apply_on_and_items + from erpnext.accounts.doctype.pricing_rule.utils import get_pricing_rule_items for d in pricing_rules.split(','): if not d or not frappe.db.exists("Pricing Rule", d): continue - pricing_rule = frappe.get_doc('Pricing Rule', d) + pricing_rule = frappe.get_cached_doc('Pricing Rule', d) if pricing_rule.price_or_product_discount == 'Price': if pricing_rule.rate_or_discount == 'Discount Percentage': @@ -349,8 +363,9 @@ def remove_pricing_rule_for_item(pricing_rules, item_details, item_code=None): else pricing_rule.get('free_item')) if pricing_rule.get("mixed_conditions") or pricing_rule.get("apply_rule_on_other"): - apply_on, items = get_apply_on_and_items(pricing_rule, item_details) - item_details.apply_on = apply_on + items = get_pricing_rule_items(pricing_rule) + item_details.apply_on = (frappe.scrub(pricing_rule.apply_rule_on_other) + if pricing_rule.apply_rule_on_other else frappe.scrub(pricing_rule.get('apply_on'))) item_details.applied_on_items = ','.join(items) item_details.pricing_rules = '' diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index a9ef29598f0..87f68225fe5 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -7,7 +7,8 @@ from __future__ import unicode_literals import frappe, copy, json from frappe import throw, _ from six import string_types -from frappe.utils import flt, cint, get_datetime +from frappe.utils import flt, cint, get_datetime, get_link_to_form, today +from erpnext.setup.doctype.item_group.item_group import get_child_item_groups from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses from erpnext.stock.get_item_details import get_conversion_factor @@ -173,10 +174,11 @@ def filter_pricing_rules(args, pricing_rules, doc=None): if (field and pricing_rules[0].get('other_' + field) != args.get(field)): return - pr_doc = frappe.get_doc('Pricing Rule', pricing_rules[0].name) + pr_doc = frappe.get_cached_doc('Pricing Rule', pricing_rules[0].name) if pricing_rules[0].mixed_conditions and doc: - stock_qty, amount = get_qty_and_rate_for_mixed_conditions(doc, pr_doc, args) + stock_qty, amount, items = get_qty_and_rate_for_mixed_conditions(doc, pr_doc, args) + pricing_rules[0].apply_rule_on_other_items = items elif pricing_rules[0].is_cumulative: items = [args.get(frappe.scrub(pr_doc.get('apply_on')))] @@ -282,7 +284,7 @@ def filter_pricing_rules_for_qty_amount(qty, rate, pricing_rules, args=None): status = True # if user has created item price against the transaction UOM - if rule.get("uom") == args.get("uom"): + if args and rule.get("uom") == args.get("uom"): conversion_factor = 1.0 if status and (flt(rate) >= (flt(rule.min_amt) * conversion_factor) @@ -339,17 +341,19 @@ def get_qty_and_rate_for_mixed_conditions(doc, pr_doc, args): sum_qty += data[0] sum_amt += data[1] - return sum_qty, sum_amt + return sum_qty, sum_amt, items def get_qty_and_rate_for_other_item(doc, pr_doc, pricing_rules): - for d in get_pricing_rule_items(pr_doc): - for row in doc.items: - if d == row.get(frappe.scrub(pr_doc.apply_on)): - pricing_rules = filter_pricing_rules_for_qty_amount(row.get("stock_qty"), - row.get("amount"), pricing_rules, row) + items = get_pricing_rule_items(pr_doc) - if pricing_rules and pricing_rules[0]: - return pricing_rules + for row in doc.items: + if row.get(frappe.scrub(pr_doc.apply_rule_on_other)) in items: + pricing_rules = filter_pricing_rules_for_qty_amount(row.get("stock_qty"), + row.get("amount"), pricing_rules, row) + + if pricing_rules and pricing_rules[0]: + pricing_rules[0].apply_rule_on_other_items = items + return pricing_rules def get_qty_amount_data_for_cumulative(pr_doc, doc, items=[]): sum_qty, sum_amt = [0, 0] @@ -397,39 +401,15 @@ def get_qty_amount_data_for_cumulative(pr_doc, doc, items=[]): return [sum_qty, sum_amt] -def validate_pricing_rules(doc): - validate_pricing_rule_on_transactions(doc) - - for d in doc.items: - validate_pricing_rule_on_items(doc, d) - - doc.calculate_taxes_and_totals() - -def validate_pricing_rule_on_items(doc, item_row, do_not_validate = False): - value = 0 - for pricing_rule in get_applied_pricing_rules(doc, item_row): - pr_doc = frappe.get_doc('Pricing Rule', pricing_rule) - - if (pr_doc.get('apply_on') == 'Transaction' - or not pr_doc.get("mixed_conditions")): continue - - if pr_doc.get('price_or_product_discount') == 'Product': - apply_pricing_rule_for_free_items(doc, pr_doc) - else: - for field in ['discount_percentage', 'discount_amount', 'rate']: - if not pr_doc.get(field): continue - - value += pr_doc.get(field) - apply_pricing_rule(doc, pr_doc, item_row, value, do_not_validate) - -def validate_pricing_rule_on_transactions(doc): +def apply_pricing_rule_on_transaction(doc): conditions = "apply_on = 'Transaction'" values = {} conditions = get_other_conditions(conditions, values, doc) pricing_rules = frappe.db.sql(""" Select `tabPricing Rule`.* from `tabPricing Rule` - where {conditions} """.format(conditions = conditions), values, as_dict=1) + where {conditions} and `tabPricing Rule`.disable = 0 + """.format(conditions = conditions), values, as_dict=1) if pricing_rules: pricing_rules = filter_pricing_rules_for_qty_amount(doc.total_qty, @@ -441,98 +421,83 @@ def validate_pricing_rule_on_transactions(doc): doc.set('apply_discount_on', d.apply_discount_on) for field in ['additional_discount_percentage', 'discount_amount']: - if not d.get(field): continue - pr_field = ('discount_percentage' if field == 'additional_discount_percentage' else field) + if not d.get(pr_field): continue + if d.validate_applied_rule and doc.get(field) < d.get(pr_field): frappe.msgprint(_("User has not applied rule on the invoice {0}") .format(doc.name)) else: doc.set(field, d.get(pr_field)) - elif d.price_or_product_discount == 'Product': - apply_pricing_rule_for_free_items(doc, d) -def get_applied_pricing_rules(doc, item_row): + doc.calculate_taxes_and_totals() + elif d.price_or_product_discount == 'Product': + item_details = frappe._dict({'parenttype': doc.doctype}) + get_product_discount_rule(d, item_details, doc) + apply_pricing_rule_for_free_items(doc, item_details.free_item_data) + doc.set_missing_values() + +def get_applied_pricing_rules(item_row): return (item_row.get("pricing_rules").split(',') if item_row.get("pricing_rules") else []) -def apply_pricing_rule_for_free_items(doc, pricing_rule): - if pricing_rule.get('free_item'): +def get_product_discount_rule(pricing_rule, item_details, doc=None): + free_item = (pricing_rule.free_item + if not pricing_rule.same_item or pricing_rule.apply_on == 'Transaction' else item_details.item_code) + + if not free_item: + frappe.throw(_("Free item not set in the pricing rule {0}") + .format(get_link_to_form("Pricing Rule", pricing_rule.name))) + + item_details.free_item_data = { + 'item_code': free_item, + 'qty': pricing_rule.free_qty or 1, + 'rate': pricing_rule.free_item_rate or 0, + 'price_list_rate': pricing_rule.free_item_rate or 0, + 'is_free_item': 1 + } + + item_data = frappe.get_cached_value('Item', free_item, ['item_name', + 'description', 'stock_uom'], as_dict=1) + + item_details.free_item_data.update(item_data) + item_details.free_item_data['uom'] = pricing_rule.free_item_uom or item_data.stock_uom + item_details.free_item_data['conversion_factor'] = get_conversion_factor(free_item, + item_details.free_item_data['uom']).get("conversion_factor", 1) + + if item_details.get("parenttype") == 'Purchase Order': + item_details.free_item_data['schedule_date'] = doc.schedule_date if doc else today() + + if item_details.get("parenttype") == 'Sales Order': + item_details.free_item_data['delivery_date'] = doc.delivery_date if doc else today() + +def apply_pricing_rule_for_free_items(doc, pricing_rule_args, set_missing_values=False): + if pricing_rule_args.get('item_code'): items = [d.item_code for d in doc.items - if d.item_code == (d.item_code - if pricing_rule.get('same_item') else pricing_rule.get('free_item')) and d.is_free_item] + if d.item_code == (pricing_rule_args.get("item_code")) and d.is_free_item] if not items: - doc.append('items', { - 'item_code': pricing_rule.get('free_item'), - 'qty': pricing_rule.get('free_qty'), - 'uom': pricing_rule.get('free_item_uom'), - 'rate': pricing_rule.get('free_item_rate'), - 'is_free_item': 1 - }) - - doc.set_missing_values() - -def apply_pricing_rule(doc, pr_doc, item_row, value, do_not_validate=False): - apply_on, items = get_apply_on_and_items(pr_doc, item_row) - - rule_applied = {} - - for item in doc.get("items"): - if item.get(apply_on) in items: - if not item.pricing_rules: - item.pricing_rules = item_row.pricing_rules - - for field in ['discount_percentage', 'discount_amount', 'rate']: - if not pr_doc.get(field): continue - - key = (item.name, item.pricing_rules) - if not pr_doc.validate_applied_rule: - rule_applied[key] = 1 - item.set(field, value) - elif item.get(field) < value: - if not do_not_validate and item.idx == item_row.idx: - rule_applied[key] = 0 - frappe.msgprint(_("Row {0}: user has not applied rule {1} on the item {2}") - .format(item.idx, pr_doc.title, item.item_code)) - - if rule_applied and doc.get("pricing_rules"): - for d in doc.get("pricing_rules"): - key = (d.child_docname, d.pricing_rule) - if key in rule_applied: - d.rule_applied = 1 - -def get_apply_on_and_items(pr_doc, item_row): - # for mixed or other items conditions - apply_on = frappe.scrub(pr_doc.get('apply_on')) - items = (get_pricing_rule_items(pr_doc) - if pr_doc.mixed_conditions else [item_row.get(apply_on)]) - - if pr_doc.apply_rule_on_other: - apply_on = frappe.scrub(pr_doc.apply_rule_on_other) - items = [pr_doc.get(apply_on)] - - return apply_on, items + doc.append('items', pricing_rule_args) def get_pricing_rule_items(pr_doc): + apply_on_data = [] apply_on = frappe.scrub(pr_doc.get('apply_on')) pricing_rule_apply_on = apply_on_table.get(pr_doc.get('apply_on')) - return [item.get(apply_on) for item in pr_doc.get(pricing_rule_apply_on)] or [] + for d in pr_doc.get(pricing_rule_apply_on): + if apply_on == 'item_group': + get_child_item_groups(d.get(apply_on)) + else: + apply_on_data.append(d.get(apply_on)) -@frappe.whitelist() -def validate_pricing_rule_for_different_cond(doc): - if isinstance(doc, string_types): - doc = json.loads(doc) + if pr_doc.apply_rule_on_other: + apply_on = frappe.scrub(pr_doc.apply_rule_on_other) + apply_on_data.append(pr_doc.get(apply_on)) - doc = frappe.get_doc(doc) - for d in doc.get("items"): - validate_pricing_rule_on_items(doc, d, True) - - return doc + return list(set(apply_on_data)) def validate_coupon_code(coupon_name): from frappe.utils import today,getdate diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 7b2061ac168..917acba92c9 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -248,7 +248,7 @@ class PurchaseInvoice(BuyingController): def set_against_expense_account(self): against_accounts = [] for item in self.get("items"): - if item.expense_account not in against_accounts: + if item.expense_account and (item.expense_account not in against_accounts): against_accounts.append(item.expense_account) self.against_expense_account = ",".join(against_accounts) diff --git a/erpnext/accounts/doctype/purchase_invoice/regional/india.js b/erpnext/accounts/doctype/purchase_invoice/regional/india.js new file mode 100644 index 00000000000..81488a2c52a --- /dev/null +++ b/erpnext/accounts/doctype/purchase_invoice/regional/india.js @@ -0,0 +1,3 @@ +{% include "erpnext/regional/india/taxes.js" %} + +erpnext.setup_auto_gst_taxation('Purchase Invoice'); diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json index 27d8233a44b..acb0398b5c0 100644 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -1,4 +1,5 @@ { + "actions": [], "autoname": "hash", "creation": "2013-05-22 12:43:10", "doctype": "DocType", @@ -507,7 +508,8 @@ "depends_on": "enable_deferred_expense", "fieldname": "service_stop_date", "fieldtype": "Date", - "label": "Service Stop Date" + "label": "Service Stop Date", + "no_copy": 1 }, { "default": "0", @@ -523,13 +525,15 @@ "depends_on": "enable_deferred_expense", "fieldname": "service_start_date", "fieldtype": "Date", - "label": "Service Start Date" + "label": "Service Start Date", + "no_copy": 1 }, { "depends_on": "enable_deferred_expense", "fieldname": "service_end_date", "fieldtype": "Date", - "label": "Service End Date" + "label": "Service End Date", + "no_copy": 1 }, { "fieldname": "reference", @@ -766,7 +770,8 @@ ], "idx": 1, "istable": 1, - "modified": "2019-11-21 16:27:52.043744", + "links": [], + "modified": "2019-12-04 12:23:17.046413", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice Item", diff --git a/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.json b/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.json index bc42630d474..a18fec61cf3 100644 --- a/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.json +++ b/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.json @@ -1,300 +1,108 @@ { - "allow_copy": 0, - "allow_import": 1, - "allow_rename": 1, - "autoname": "field:title", - "beta": 0, - "creation": "2013-01-10 16:34:08", - "custom": 0, - "description": "Standard tax template that can be applied to all Purchase Transactions. This template can contain list of tax heads and also other expense heads like \"Shipping\", \"Insurance\", \"Handling\" etc.\n\n#### Note\n\nThe tax rate you define here will be the standard tax rate for all **Items**. If there are **Items** that have different rates, they must be added in the **Item Tax** table in the **Item** master.\n\n#### Description of Columns\n\n1. Calculation Type: \n - This can be on **Net Total** (that is the sum of basic amount).\n - **On Previous Row Total / Amount** (for cumulative taxes or charges). If you select this option, the tax will be applied as a percentage of the previous row (in the tax table) amount or total.\n - **Actual** (as mentioned).\n2. Account Head: The Account ledger under which this tax will be booked\n3. Cost Center: If the tax / charge is an income (like shipping) or expense it needs to be booked against a Cost Center.\n4. Description: Description of the tax (that will be printed in invoices / quotes).\n5. Rate: Tax rate.\n6. Amount: Tax amount.\n7. Total: Cumulative total to this point.\n8. Enter Row: If based on \"Previous Row Total\" you can select the row number which will be taken as a base for this calculation (default is the previous row).\n9. Consider Tax or Charge for: In this section you can specify if the tax / charge is only for valuation (not a part of total) or only for total (does not add value to the item) or for both.\n10. Add or Deduct: Whether you want to add or deduct the tax.", - "docstatus": 0, - "doctype": "DocType", - "document_type": "Setup", - "editable_grid": 0, + "allow_import": 1, + "allow_rename": 1, + "creation": "2013-01-10 16:34:08", + "description": "Standard tax template that can be applied to all Purchase Transactions. This template can contain list of tax heads and also other expense heads like \"Shipping\", \"Insurance\", \"Handling\" etc.\n\n#### Note\n\nThe tax rate you define here will be the standard tax rate for all **Items**. If there are **Items** that have different rates, they must be added in the **Item Tax** table in the **Item** master.\n\n#### Description of Columns\n\n1. Calculation Type: \n - This can be on **Net Total** (that is the sum of basic amount).\n - **On Previous Row Total / Amount** (for cumulative taxes or charges). If you select this option, the tax will be applied as a percentage of the previous row (in the tax table) amount or total.\n - **Actual** (as mentioned).\n2. Account Head: The Account ledger under which this tax will be booked\n3. Cost Center: If the tax / charge is an income (like shipping) or expense it needs to be booked against a Cost Center.\n4. Description: Description of the tax (that will be printed in invoices / quotes).\n5. Rate: Tax rate.\n6. Amount: Tax amount.\n7. Total: Cumulative total to this point.\n8. Enter Row: If based on \"Previous Row Total\" you can select the row number which will be taken as a base for this calculation (default is the previous row).\n9. Consider Tax or Charge for: In this section you can specify if the tax / charge is only for valuation (not a part of total) or only for total (does not add value to the item) or for both.\n10. Add or Deduct: Whether you want to add or deduct the tax.", + "doctype": "DocType", + "document_type": "Setup", + "field_order": [ + "title", + "is_default", + "disabled", + "column_break4", + "company", + "tax_category", + "section_break6", + "taxes" + ], "fields": [ { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "title", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 1, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Title", - "length": 0, - "no_copy": 1, - "oldfieldname": "title", - "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "title", + "fieldtype": "Data", + "label": "Title", + "no_copy": 1, + "oldfieldname": "title", + "oldfieldtype": "Data", + "reqd": 1 + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "is_default", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Default", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "default": "0", + "fieldname": "is_default", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Default" + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "disabled", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Disabled", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "default": "0", + "fieldname": "disabled", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Disabled" + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break4", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "column_break4", + "fieldtype": "Column Break" + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "company", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 1, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Company", - "length": 0, - "no_copy": 0, - "options": "Company", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 1, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "company", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Company", + "options": "Company", + "remember_last_selected_value": 1, + "reqd": 1 + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break6", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "section_break6", + "fieldtype": "Section Break" + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "taxes", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Purchase Taxes and Charges", - "length": 0, - "no_copy": 0, - "oldfieldname": "purchase_tax_details", - "oldfieldtype": "Table", - "options": "Purchase Taxes and Charges", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 + "fieldname": "taxes", + "fieldtype": "Table", + "label": "Purchase Taxes and Charges", + "oldfieldname": "purchase_tax_details", + "oldfieldtype": "Table", + "options": "Purchase Taxes and Charges" + }, + { + "fieldname": "tax_category", + "fieldtype": "Link", + "label": "Tax Category", + "options": "Tax Category" } - ], - "hide_heading": 0, - "hide_toolbar": 0, - "icon": "fa fa-money", - "idx": 1, - "image_view": 0, - "in_create": 0, - - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2016-11-07 05:18:44.095798", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Purchase Taxes and Charges Template", - "owner": "wasim@webnotestech.com", + ], + "icon": "fa fa-money", + "idx": 1, + "modified": "2019-11-25 13:05:26.220275", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Purchase Taxes and Charges Template", + "owner": "wasim@webnotestech.com", "permissions": [ { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "is_custom": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Purchase Manager", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 - }, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Purchase Manager" + }, { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "is_custom": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Purchase Master Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Purchase Master Manager", + "share": 1, "write": 1 - }, + }, { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, - "is_custom": 0, - "permlevel": 0, - "print": 0, - "read": 1, - "report": 0, - "role": "Purchase User", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 + "read": 1, + "role": "Purchase User" } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "sort_order": "DESC", - "track_seen": 0 + ], + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/accounts/doctype/sales_invoice/pos.py b/erpnext/accounts/doctype/sales_invoice/pos.py index ed45b2cc2c7..a48d2244893 100755 --- a/erpnext/accounts/doctype/sales_invoice/pos.py +++ b/erpnext/accounts/doctype/sales_invoice/pos.py @@ -357,14 +357,11 @@ def get_customer_wise_price_list(): def get_bin_data(pos_profile): itemwise_bin_data = {} - cond = "1=1" + filters = { 'actual_qty': ['>', 0] } if pos_profile.get('warehouse'): - cond = "warehouse = %(warehouse)s" + filters.update({ 'warehouse': pos_profile.get('warehouse') }) - bin_data = frappe.db.sql(""" select item_code, warehouse, actual_qty from `tabBin` - where actual_qty > 0 and {cond}""".format(cond=cond), { - 'warehouse': frappe.db.escape(pos_profile.get('warehouse')) - }, as_dict=1) + bin_data = frappe.db.get_all('Bin', fields = ['item_code', 'warehouse', 'actual_qty'], filters=filters) for bins in bin_data: if bins.item_code not in itemwise_bin_data: @@ -550,11 +547,15 @@ def make_address(args, customer): def make_email_queue(email_queue): name_list = [] + for key, data in iteritems(email_queue): name = frappe.db.get_value('Sales Invoice', {'offline_pos_name': key}, 'name') + if not name: continue + data = json.loads(data) sender = frappe.session.user print_format = "POS Invoice" if not cint(frappe.db.get_value('Print Format', 'POS Invoice', 'disabled')) else None + attachments = [frappe.attach_print('Sales Invoice', name, print_format=print_format)] make(subject=data.get('subject'), content=data.get('content'), recipients=data.get('recipients'), diff --git a/erpnext/accounts/doctype/sales_invoice/regional/india.js b/erpnext/accounts/doctype/sales_invoice/regional/india.js index c8305e325f6..7941a59ac36 100644 --- a/erpnext/accounts/doctype/sales_invoice/regional/india.js +++ b/erpnext/accounts/doctype/sales_invoice/regional/india.js @@ -1,3 +1,7 @@ +{% include "erpnext/regional/india/taxes.js" %} + +erpnext.setup_auto_gst_taxation('Sales Invoice'); + frappe.ui.form.on("Sales Invoice", { setup: function(frm) { frm.set_query('transporter', function() { @@ -34,5 +38,8 @@ frappe.ui.form.on("Sales Invoice", { } }, __("Make")); } - } + }, + }); + + diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 3c852106635..61aa0aa2ca9 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -697,8 +697,8 @@ frappe.ui.form.on('Sales Invoice', { if (frm.doc.company) { frappe.call({ - method:"frappe.contacts.doctype.address.address.get_default_address", - args:{ doctype:'Company',name:frm.doc.company}, + method:"erpnext.setup.doctype.company.company.get_default_company_address", + args:{name:frm.doc.company, existing_address: frm.doc.company_address}, callback: function(r){ if (r.message){ frm.set_value("company_address",r.message) diff --git a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json index 779ac4f656c..b2294e4318f 100644 --- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json +++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json @@ -1,4 +1,5 @@ { + "actions": [], "autoname": "hash", "creation": "2013-06-04 11:02:19", "doctype": "DocType", @@ -484,7 +485,8 @@ "depends_on": "enable_deferred_revenue", "fieldname": "service_stop_date", "fieldtype": "Date", - "label": "Service Stop Date" + "label": "Service Stop Date", + "no_copy": 1 }, { "default": "0", @@ -500,13 +502,15 @@ "depends_on": "enable_deferred_revenue", "fieldname": "service_start_date", "fieldtype": "Date", - "label": "Service Start Date" + "label": "Service Start Date", + "no_copy": 1 }, { "depends_on": "enable_deferred_revenue", "fieldname": "service_end_date", "fieldtype": "Date", - "label": "Service End Date" + "label": "Service End Date", + "no_copy": 1 }, { "collapsible": 1, @@ -783,7 +787,8 @@ ], "idx": 1, "istable": 1, - "modified": "2019-07-16 16:36:46.527606", + "links": [], + "modified": "2019-12-04 12:22:38.517710", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice Item", diff --git a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.json b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.json index 29e15d165fa..19781bdffaa 100644 --- a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.json +++ b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.json @@ -1,299 +1,119 @@ { - "allow_copy": 0, - "allow_import": 1, - "allow_rename": 1, - "autoname": "field:title", - "beta": 0, - "creation": "2013-01-10 16:34:09", - "custom": 0, - "description": "Standard tax template that can be applied to all Sales Transactions. This template can contain list of tax heads and also other expense / income heads like \"Shipping\", \"Insurance\", \"Handling\" etc.\n\n#### Note\n\nThe tax rate you define here will be the standard tax rate for all **Items**. If there are **Items** that have different rates, they must be added in the **Item Tax** table in the **Item** master.\n\n#### Description of Columns\n\n1. Calculation Type: \n - This can be on **Net Total** (that is the sum of basic amount).\n - **On Previous Row Total / Amount** (for cumulative taxes or charges). If you select this option, the tax will be applied as a percentage of the previous row (in the tax table) amount or total.\n - **Actual** (as mentioned).\n2. Account Head: The Account ledger under which this tax will be booked\n3. Cost Center: If the tax / charge is an income (like shipping) or expense it needs to be booked against a Cost Center.\n4. Description: Description of the tax (that will be printed in invoices / quotes).\n5. Rate: Tax rate.\n6. Amount: Tax amount.\n7. Total: Cumulative total to this point.\n8. Enter Row: If based on \"Previous Row Total\" you can select the row number which will be taken as a base for this calculation (default is the previous row).\n9. Is this Tax included in Basic Rate?: If you check this, it means that this tax will not be shown below the item table, but will be included in the Basic Rate in your main item table. This is useful where you want give a flat price (inclusive of all taxes) price to customers.", - "docstatus": 0, - "doctype": "DocType", - "document_type": "Setup", - "editable_grid": 0, + "allow_import": 1, + "allow_rename": 1, + "creation": "2013-01-10 16:34:09", + "description": "Standard tax template that can be applied to all Sales Transactions. This template can contain list of tax heads and also other expense / income heads like \"Shipping\", \"Insurance\", \"Handling\" etc.\n\n#### Note\n\nThe tax rate you define here will be the standard tax rate for all **Items**. If there are **Items** that have different rates, they must be added in the **Item Tax** table in the **Item** master.\n\n#### Description of Columns\n\n1. Calculation Type: \n - This can be on **Net Total** (that is the sum of basic amount).\n - **On Previous Row Total / Amount** (for cumulative taxes or charges). If you select this option, the tax will be applied as a percentage of the previous row (in the tax table) amount or total.\n - **Actual** (as mentioned).\n2. Account Head: The Account ledger under which this tax will be booked\n3. Cost Center: If the tax / charge is an income (like shipping) or expense it needs to be booked against a Cost Center.\n4. Description: Description of the tax (that will be printed in invoices / quotes).\n5. Rate: Tax rate.\n6. Amount: Tax amount.\n7. Total: Cumulative total to this point.\n8. Enter Row: If based on \"Previous Row Total\" you can select the row number which will be taken as a base for this calculation (default is the previous row).\n9. Is this Tax included in Basic Rate?: If you check this, it means that this tax will not be shown below the item table, but will be included in the Basic Rate in your main item table. This is useful where you want give a flat price (inclusive of all taxes) price to customers.", + "doctype": "DocType", + "document_type": "Setup", + "engine": "InnoDB", + "field_order": [ + "title", + "is_default", + "disabled", + "column_break_3", + "company", + "tax_category", + "section_break_5", + "taxes" + ], "fields": [ { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "title", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 1, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Title", - "length": 0, - "no_copy": 1, - "oldfieldname": "title", - "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "title", + "fieldtype": "Data", + "label": "Title", + "no_copy": 1, + "oldfieldname": "title", + "oldfieldtype": "Data", + "reqd": 1 + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "is_default", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Default", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "default": "0", + "fieldname": "is_default", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Default" + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "disabled", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Disabled", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "default": "0", + "fieldname": "disabled", + "fieldtype": "Check", + "label": "Disabled" + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_3", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "company", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 1, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Company", - "length": 0, - "no_copy": 0, - "oldfieldname": "company", - "oldfieldtype": "Link", - "options": "Company", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 1, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "company", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Company", + "oldfieldname": "company", + "oldfieldtype": "Link", + "options": "Company", + "remember_last_selected_value": 1, + "reqd": 1 + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_5", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "section_break_5", + "fieldtype": "Section Break" + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "* Will be calculated in the transaction.", - "fieldname": "taxes", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Sales Taxes and Charges", - "length": 0, - "no_copy": 0, - "oldfieldname": "other_charges", - "oldfieldtype": "Table", - "options": "Sales Taxes and Charges", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 + "description": "* Will be calculated in the transaction.", + "fieldname": "taxes", + "fieldtype": "Table", + "label": "Sales Taxes and Charges", + "oldfieldname": "other_charges", + "oldfieldtype": "Table", + "options": "Sales Taxes and Charges" + }, + { + "fieldname": "tax_category", + "fieldtype": "Link", + "label": "Tax Category", + "options": "Tax Category" } - ], - "hide_heading": 0, - "hide_toolbar": 0, - "icon": "fa fa-money", - "idx": 1, - "image_view": 0, - "in_create": 0, - - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2016-11-07 05:18:41.743257", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Sales Taxes and Charges Template", - "owner": "Administrator", + ], + "icon": "fa fa-money", + "idx": 1, + "modified": "2019-11-25 13:06:03.279099", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Sales Taxes and Charges Template", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "apply_user_permissions": 1, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "is_custom": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Sales User", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 - }, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Sales User" + }, { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "is_custom": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Accounts Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "share": 1, "write": 1 - }, + }, { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "is_custom": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Sales Master Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Sales Master Manager", + "share": 1, "write": 1 } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "sort_order": "ASC", - "track_seen": 0 + ], + "sort_field": "modified", + "sort_order": "ASC", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/accounts/doctype/share_transfer/share_transfer.py b/erpnext/accounts/doctype/share_transfer/share_transfer.py index e95c69413f5..65f248e7bde 100644 --- a/erpnext/accounts/doctype/share_transfer/share_transfer.py +++ b/erpnext/accounts/doctype/share_transfer/share_transfer.py @@ -13,9 +13,9 @@ from frappe.utils import nowdate class ShareDontExists(ValidationError): pass class ShareTransfer(Document): - def before_submit(self): + def on_submit(self): if self.transfer_type == 'Issue': - shareholder = self.get_shareholder_doc(self.company) + shareholder = self.get_company_shareholder() shareholder.append('share_balance', { 'share_type': self.share_type, 'from_no': self.from_no, @@ -28,7 +28,7 @@ class ShareTransfer(Document): }) shareholder.save() - doc = frappe.get_doc('Shareholder', self.to_shareholder) + doc = self.get_shareholder_doc(self.to_shareholder) doc.append('share_balance', { 'share_type': self.share_type, 'from_no': self.from_no, @@ -41,11 +41,11 @@ class ShareTransfer(Document): elif self.transfer_type == 'Purchase': self.remove_shares(self.from_shareholder) - self.remove_shares(self.get_shareholder_doc(self.company).name) + self.remove_shares(self.get_company_shareholder().name) elif self.transfer_type == 'Transfer': self.remove_shares(self.from_shareholder) - doc = frappe.get_doc('Shareholder', self.to_shareholder) + doc = self.get_shareholder_doc(self.to_shareholder) doc.append('share_balance', { 'share_type': self.share_type, 'from_no': self.from_no, @@ -56,143 +56,127 @@ class ShareTransfer(Document): }) doc.save() + def on_cancel(self): + if self.transfer_type == 'Issue': + compnay_shareholder = self.get_company_shareholder() + self.remove_shares(compnay_shareholder.name) + self.remove_shares(self.to_shareholder) + + elif self.transfer_type == 'Purchase': + compnay_shareholder = self.get_company_shareholder() + from_shareholder = self.get_shareholder_doc(self.from_shareholder) + + from_shareholder.append('share_balance', { + 'share_type': self.share_type, + 'from_no': self.from_no, + 'to_no': self.to_no, + 'rate': self.rate, + 'amount': self.amount, + 'no_of_shares': self.no_of_shares + }) + + from_shareholder.save() + + compnay_shareholder.append('share_balance', { + 'share_type': self.share_type, + 'from_no': self.from_no, + 'to_no': self.to_no, + 'rate': self.rate, + 'amount': self.amount, + 'no_of_shares': self.no_of_shares + }) + + compnay_shareholder.save() + + elif self.transfer_type == 'Transfer': + self.remove_shares(self.to_shareholder) + from_shareholder = self.get_shareholder_doc(self.from_shareholder) + from_shareholder.append('share_balance', { + 'share_type': self.share_type, + 'from_no': self.from_no, + 'to_no': self.to_no, + 'rate': self.rate, + 'amount': self.amount, + 'no_of_shares': self.no_of_shares + }) + from_shareholder.save() + def validate(self): + self.get_company_shareholder() self.basic_validations() self.folio_no_validation() + if self.transfer_type == 'Issue': - if not self.get_shareholder_doc(self.company): - shareholder = frappe.get_doc({ - 'doctype': 'Shareholder', - 'title': self.company, - 'company': self.company, - 'is_company': 1 - }) - shareholder.insert() - # validate share doesnt exist in company - ret_val = self.share_exists(self.get_shareholder_doc(self.company).name) - if ret_val != False: + # validate share doesn't exist in company + ret_val = self.share_exists(self.get_company_shareholder().name) + if ret_val in ('Complete', 'Partial'): frappe.throw(_('The shares already exist'), frappe.DuplicateEntryError) else: # validate share exists with from_shareholder ret_val = self.share_exists(self.from_shareholder) - if ret_val != True: + if ret_val in ('Outside', 'Partial'): frappe.throw(_("The shares don't exist with the {0}") .format(self.from_shareholder), ShareDontExists) def basic_validations(self): if self.transfer_type == 'Purchase': self.to_shareholder = '' - if self.from_shareholder is None or self.from_shareholder is '': + if not self.from_shareholder: frappe.throw(_('The field From Shareholder cannot be blank')) - if self.from_folio_no is None or self.from_folio_no is '': + if not self.from_folio_no: self.to_folio_no = self.autoname_folio(self.to_shareholder) - if self.asset_account is None: + if not self.asset_account: frappe.throw(_('The field Asset Account cannot be blank')) elif (self.transfer_type == 'Issue'): self.from_shareholder = '' - if self.to_shareholder is None or self.to_shareholder == '': + if not self.to_shareholder: frappe.throw(_('The field To Shareholder cannot be blank')) - if self.to_folio_no is None or self.to_folio_no is '': + if not self.to_folio_no: self.to_folio_no = self.autoname_folio(self.to_shareholder) - if self.asset_account is None: + if not self.asset_account: frappe.throw(_('The field Asset Account cannot be blank')) else: - if self.from_shareholder is None or self.to_shareholder is None: + if not self.from_shareholder or not self.to_shareholder: frappe.throw(_('The fields From Shareholder and To Shareholder cannot be blank')) - if self.to_folio_no is None or self.to_folio_no is '': + if not self.to_folio_no: self.to_folio_no = self.autoname_folio(self.to_shareholder) - if self.equity_or_liability_account is None: + if not self.equity_or_liability_account: frappe.throw(_('The field Equity/Liability Account cannot be blank')) if self.from_shareholder == self.to_shareholder: frappe.throw(_('The seller and the buyer cannot be the same')) if self.no_of_shares != self.to_no - self.from_no + 1: frappe.throw(_('The number of shares and the share numbers are inconsistent')) - if self.amount is None: + if not self.amount: self.amount = self.rate * self.no_of_shares if self.amount != self.rate * self.no_of_shares: frappe.throw(_('There are inconsistencies between the rate, no of shares and the amount calculated')) def share_exists(self, shareholder): - # return True if exits, - # False if completely doesn't exist, - # 'partially exists' if partailly doesn't exist - ret_val = self.recursive_share_check(shareholder, self.share_type, - query = { - 'from_no': self.from_no, - 'to_no': self.to_no - } - ) - if all(boolean == True for boolean in ret_val): - return True - elif True in ret_val: - return 'partially exists' - else: - return False - - def recursive_share_check(self, shareholder, share_type, query): - # query = {'from_no': share_starting_no, 'to_no': share_ending_no} - # Recursive check if a given part of shares is held by the shareholder - # return a list containing True and False - # Eg. [True, False, True] - # All True implies its completely inside - # All False implies its completely outside - # A mix implies its partially inside/outside - does_share_exist = [] - doc = frappe.get_doc('Shareholder', shareholder) + doc = self.get_shareholder_doc(shareholder) for entry in doc.share_balance: - if entry.share_type != share_type or \ - entry.from_no > query['to_no'] or \ - entry.to_no < query['from_no']: + if entry.share_type != self.share_type or \ + entry.from_no > self.to_no or \ + entry.to_no < self.from_no: continue # since query lies outside bounds - elif entry.from_no <= query['from_no'] and entry.to_no >= query['to_no']: - return [True] # absolute truth! - elif entry.from_no >= query['from_no'] and entry.to_no <= query['to_no']: - # split and check - does_share_exist.extend(self.recursive_share_check(shareholder, - share_type, - { - 'from_no': query['from_no'], - 'to_no': entry.from_no - 1 - } - )) - does_share_exist.append(True) - does_share_exist.extend(self.recursive_share_check(shareholder, - share_type, - { - 'from_no': entry.to_no + 1, - 'to_no': query['to_no'] - } - )) - elif query['from_no'] <= entry.from_no <= query['to_no'] and entry.to_no >= query['to_no']: - does_share_exist.extend(self.recursive_share_check(shareholder, - share_type, - { - 'from_no': query['from_no'], - 'to_no': entry.from_no - 1 - } - )) - elif query['from_no'] <= entry.to_no <= query['to_no'] and entry.from_no <= query['from_no']: - does_share_exist.extend(self.recursive_share_check(shareholder, - share_type, - { - 'from_no': entry.to_no + 1, - 'to_no': query['to_no'] - } - )) + elif entry.from_no <= self.from_no and entry.to_no >= self.to_no: #both inside + return 'Complete' # absolute truth! + elif entry.from_no <= self.from_no <= self.to_no: + return 'Partial' + elif entry.from_no <= self.to_no <= entry.to_no: + return 'Partial' - does_share_exist.append(False) - return does_share_exist + return 'Outside' def folio_no_validation(self): shareholders = ['from_shareholder', 'to_shareholder'] shareholders = [shareholder for shareholder in shareholders if self.get(shareholder) is not ''] for shareholder in shareholders: - doc = frappe.get_doc('Shareholder', self.get(shareholder)) + doc = self.get_shareholder_doc(self.get(shareholder)) if doc.company != self.company: frappe.throw(_('The shareholder does not belong to this company')) - if doc.folio_no is '' or doc.folio_no is None: + if not doc.folio_no: doc.folio_no = self.from_folio_no \ - if (shareholder == 'from_shareholder') else self.to_folio_no; + if (shareholder == 'from_shareholder') else self.to_folio_no doc.save() else: if doc.folio_no and doc.folio_no != (self.from_folio_no if (shareholder == 'from_shareholder') else self.to_folio_no): @@ -200,24 +184,14 @@ class ShareTransfer(Document): def autoname_folio(self, shareholder, is_company=False): if is_company: - doc = self.get_shareholder_doc(shareholder) + doc = self.get_company_shareholder() else: - doc = frappe.get_doc('Shareholder' , shareholder) + doc = self.get_shareholder_doc(shareholder) doc.folio_no = make_autoname('FN.#####') doc.save() return doc.folio_no def remove_shares(self, shareholder): - self.iterative_share_removal(shareholder, self.share_type, - { - 'from_no': self.from_no, - 'to_no' : self.to_no - }, - rate = self.rate, - amount = self.amount - ) - - def iterative_share_removal(self, shareholder, share_type, query, rate, amount): # query = {'from_no': share_starting_no, 'to_no': share_ending_no} # Shares exist for sure # Iterate over all entries and modify entry if in entry @@ -227,31 +201,31 @@ class ShareTransfer(Document): for entry in current_entries: # use spaceage logic here - if entry.share_type != share_type or \ - entry.from_no > query['to_no'] or \ - entry.to_no < query['from_no']: + if entry.share_type != self.share_type or \ + entry.from_no > self.to_no or \ + entry.to_no < self.from_no: new_entries.append(entry) continue # since query lies outside bounds - elif entry.from_no <= query['from_no'] and entry.to_no >= query['to_no']: + elif entry.from_no <= self.from_no and entry.to_no >= self.to_no: #split - if entry.from_no == query['from_no']: - if entry.to_no == query['to_no']: + if entry.from_no == self.from_no: + if entry.to_no == self.to_no: pass #nothing to append else: - new_entries.append(self.return_share_balance_entry(query['to_no']+1, entry.to_no, entry.rate)) + new_entries.append(self.return_share_balance_entry(self.to_no+1, entry.to_no, entry.rate)) else: - if entry.to_no == query['to_no']: - new_entries.append(self.return_share_balance_entry(entry.from_no, query['from_no']-1, entry.rate)) + if entry.to_no == self.to_no: + new_entries.append(self.return_share_balance_entry(entry.from_no, self.from_no-1, entry.rate)) else: - new_entries.append(self.return_share_balance_entry(entry.from_no, query['from_no']-1, entry.rate)) - new_entries.append(self.return_share_balance_entry(query['to_no']+1, entry.to_no, entry.rate)) - elif entry.from_no >= query['from_no'] and entry.to_no <= query['to_no']: + new_entries.append(self.return_share_balance_entry(entry.from_no, self.from_no-1, entry.rate)) + new_entries.append(self.return_share_balance_entry(self.to_no+1, entry.to_no, entry.rate)) + elif entry.from_no >= self.from_no and entry.to_no <= self.to_no: # split and check pass #nothing to append - elif query['from_no'] <= entry.from_no <= query['to_no'] and entry.to_no >= query['to_no']: - new_entries.append(self.return_share_balance_entry(query['to_no']+1, entry.to_no, entry.rate)) - elif query['from_no'] <= entry.to_no <= query['to_no'] and entry.from_no <= query['from_no']: - new_entries.append(self.return_share_balance_entry(entry.from_no, query['from_no']-1, entry.rate)) + elif self.from_no <= entry.from_no <= self.to_no and entry.to_no >= self.to_no: + new_entries.append(self.return_share_balance_entry(self.to_no+1, entry.to_no, entry.rate)) + elif self.from_no <= entry.to_no <= self.to_no and entry.from_no <= self.from_no: + new_entries.append(self.return_share_balance_entry(entry.from_no, self.from_no-1, entry.rate)) else: new_entries.append(entry) @@ -272,16 +246,34 @@ class ShareTransfer(Document): } def get_shareholder_doc(self, shareholder): - # Get Shareholder doc based on the Shareholder title - doc = frappe.get_list('Shareholder', - filters = [ - ('Shareholder', 'title', '=', shareholder) - ] - ) - if len(doc) == 1: - return frappe.get_doc('Shareholder', doc[0]['name']) - else: #It will necessarily by 0 indicating it doesn't exist - return False + # Get Shareholder doc based on the Shareholder name + if shareholder: + query_filters = {'name': shareholder} + + name = frappe.db.get_value('Shareholder', {'name': shareholder}, 'name') + + return frappe.get_doc('Shareholder', name) + + def get_company_shareholder(self): + # Get company doc or create one if not present + company_shareholder = frappe.db.get_value('Shareholder', + { + 'company': self.company, + 'is_company': 1 + }, 'name') + + if company_shareholder: + return frappe.get_doc('Shareholder', company_shareholder) + else: + shareholder = frappe.get_doc({ + 'doctype': 'Shareholder', + 'title': self.company, + 'company': self.company, + 'is_company': 1 + }) + shareholder.insert() + + return shareholder @frappe.whitelist() def make_jv_entry( company, account, amount, payment_account,\ diff --git a/erpnext/accounts/doctype/share_transfer/test_share_transfer.py b/erpnext/accounts/doctype/share_transfer/test_share_transfer.py index 910dfd05dab..2ff9b02129f 100644 --- a/erpnext/accounts/doctype/share_transfer/test_share_transfer.py +++ b/erpnext/accounts/doctype/share_transfer/test_share_transfer.py @@ -15,73 +15,73 @@ class TestShareTransfer(unittest.TestCase): frappe.db.sql("delete from `tabShare Balance`") share_transfers = [ { - "doctype" : "Share Transfer", - "transfer_type" : "Issue", - "date" : "2018-01-01", - "to_shareholder" : "SH-00001", - "share_type" : "Equity", - "from_no" : 1, - "to_no" : 500, - "no_of_shares" : 500, - "rate" : 10, - "company" : "_Test Company", - "asset_account" : "Cash - _TC", + "doctype": "Share Transfer", + "transfer_type": "Issue", + "date": "2018-01-01", + "to_shareholder": "SH-00001", + "share_type": "Equity", + "from_no": 1, + "to_no": 500, + "no_of_shares": 500, + "rate": 10, + "company": "_Test Company", + "asset_account": "Cash - _TC", "equity_or_liability_account": "Creditors - _TC" }, { - "doctype" : "Share Transfer", - "transfer_type" : "Transfer", - "date" : "2018-01-02", - "from_shareholder" : "SH-00001", - "to_shareholder" : "SH-00002", - "share_type" : "Equity", - "from_no" : 101, - "to_no" : 200, - "no_of_shares" : 100, - "rate" : 15, - "company" : "_Test Company", + "doctype": "Share Transfer", + "transfer_type": "Transfer", + "date": "2018-01-02", + "from_shareholder": "SH-00001", + "to_shareholder": "SH-00002", + "share_type": "Equity", + "from_no": 101, + "to_no": 200, + "no_of_shares": 100, + "rate": 15, + "company": "_Test Company", "equity_or_liability_account": "Creditors - _TC" }, { - "doctype" : "Share Transfer", - "transfer_type" : "Transfer", - "date" : "2018-01-03", - "from_shareholder" : "SH-00001", - "to_shareholder" : "SH-00003", - "share_type" : "Equity", - "from_no" : 201, - "to_no" : 500, - "no_of_shares" : 300, - "rate" : 20, - "company" : "_Test Company", + "doctype": "Share Transfer", + "transfer_type": "Transfer", + "date": "2018-01-03", + "from_shareholder": "SH-00001", + "to_shareholder": "SH-00003", + "share_type": "Equity", + "from_no": 201, + "to_no": 500, + "no_of_shares": 300, + "rate": 20, + "company": "_Test Company", "equity_or_liability_account": "Creditors - _TC" }, { - "doctype" : "Share Transfer", - "transfer_type" : "Transfer", - "date" : "2018-01-04", - "from_shareholder" : "SH-00003", - "to_shareholder" : "SH-00002", - "share_type" : "Equity", - "from_no" : 201, - "to_no" : 400, - "no_of_shares" : 200, - "rate" : 15, - "company" : "_Test Company", + "doctype": "Share Transfer", + "transfer_type": "Transfer", + "date": "2018-01-04", + "from_shareholder": "SH-00003", + "to_shareholder": "SH-00002", + "share_type": "Equity", + "from_no": 201, + "to_no": 400, + "no_of_shares": 200, + "rate": 15, + "company": "_Test Company", "equity_or_liability_account": "Creditors - _TC" }, { - "doctype" : "Share Transfer", - "transfer_type" : "Purchase", - "date" : "2018-01-05", - "from_shareholder" : "SH-00003", - "share_type" : "Equity", - "from_no" : 401, - "to_no" : 500, - "no_of_shares" : 100, - "rate" : 25, - "company" : "_Test Company", - "asset_account" : "Cash - _TC", + "doctype": "Share Transfer", + "transfer_type": "Purchase", + "date": "2018-01-05", + "from_shareholder": "SH-00003", + "share_type": "Equity", + "from_no": 401, + "to_no": 500, + "no_of_shares": 100, + "rate": 25, + "company": "_Test Company", + "asset_account": "Cash - _TC", "equity_or_liability_account": "Creditors - _TC" } ] @@ -91,33 +91,33 @@ class TestShareTransfer(unittest.TestCase): def test_invalid_share_transfer(self): doc = frappe.get_doc({ - "doctype" : "Share Transfer", - "transfer_type" : "Transfer", - "date" : "2018-01-05", - "from_shareholder" : "SH-00003", - "to_shareholder" : "SH-00002", - "share_type" : "Equity", - "from_no" : 1, - "to_no" : 100, - "no_of_shares" : 100, - "rate" : 15, - "company" : "_Test Company", + "doctype": "Share Transfer", + "transfer_type": "Transfer", + "date": "2018-01-05", + "from_shareholder": "SH-00003", + "to_shareholder": "SH-00002", + "share_type": "Equity", + "from_no": 1, + "to_no": 100, + "no_of_shares": 100, + "rate": 15, + "company": "_Test Company", "equity_or_liability_account": "Creditors - _TC" }) self.assertRaises(ShareDontExists, doc.insert) doc = frappe.get_doc({ - "doctype" : "Share Transfer", - "transfer_type" : "Purchase", - "date" : "2018-01-02", - "from_shareholder" : "SH-00001", - "share_type" : "Equity", - "from_no" : 1, - "to_no" : 200, - "no_of_shares" : 200, - "rate" : 15, - "company" : "_Test Company", - "asset_account" : "Cash - _TC", + "doctype": "Share Transfer", + "transfer_type": "Purchase", + "date": "2018-01-02", + "from_shareholder": "SH-00001", + "share_type": "Equity", + "from_no": 1, + "to_no": 200, + "no_of_shares": 200, + "rate": 15, + "company": "_Test Company", + "asset_account": "Cash - _TC", "equity_or_liability_account": "Creditors - _TC" }) self.assertRaises(ShareDontExists, doc.insert) diff --git a/erpnext/accounts/doctype/shareholder/shareholder.json b/erpnext/accounts/doctype/shareholder/shareholder.json index 873a3e76a3f..e94aea94b75 100644 --- a/erpnext/accounts/doctype/shareholder/shareholder.json +++ b/erpnext/accounts/doctype/shareholder/shareholder.json @@ -1,587 +1,163 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "naming_series:", - "beta": 0, - "creation": "2017-12-25 16:50:53.878430", - "custom": 0, - "description": "", - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "autoname": "naming_series:", + "creation": "2017-12-25 16:50:53.878430", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "title", + "column_break_2", + "naming_series", + "section_break_2", + "folio_no", + "column_break_4", + "company", + "is_company", + "address_contacts", + "address_html", + "column_break_9", + "contact_html", + "section_break_3", + "share_balance", + "contact_list" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "title", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Title", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "title", + "fieldtype": "Data", + "label": "Title", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_2", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", - "fieldname": "naming_series", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "", - "length": 0, - "no_copy": 0, - "options": "ACC-SH-.YYYY.-", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "naming_series", + "fieldtype": "Select", + "options": "ACC-SH-.YYYY.-" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_2", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "section_break_2", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "folio_no", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Folio no.", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "fieldname": "folio_no", + "fieldtype": "Data", + "label": "Folio no.", + "read_only": 1, "unique": 1 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_4", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "company", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Company", - "length": 0, - "no_copy": 0, - "options": "Company", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "company", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Company", + "options": "Company", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "is_company", - "fieldtype": "Check", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Is Company", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "fieldname": "is_company", + "fieldtype": "Check", + "hidden": 1, + "label": "Is Company", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "address_contacts", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Address and Contacts", - "length": 0, - "no_copy": 0, - "options": "fa fa-map-marker", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "address_contacts", + "fieldtype": "Section Break", + "label": "Address and Contacts", + "options": "fa fa-map-marker" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "address_html", - "fieldtype": "HTML", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Address HTML", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "address_html", + "fieldtype": "HTML", + "label": "Address HTML", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_9", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_9", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "contact_html", - "fieldtype": "HTML", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Contact HTML", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "contact_html", + "fieldtype": "HTML", + "label": "Contact HTML", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_3", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Share Balance", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "section_break_3", + "fieldtype": "Section Break", + "label": "Share Balance" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "share_balance", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Share Balance", - "length": 0, - "no_copy": 0, - "options": "Share Balance", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "share_balance", + "fieldtype": "Table", + "label": "Share Balance", + "options": "Share Balance", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "Hidden list maintaining the list of contacts linked to Shareholder", - "fieldname": "contact_list", - "fieldtype": "Code", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Contact List", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "description": "Hidden list maintaining the list of contacts linked to Shareholder", + "fieldname": "contact_list", + "fieldtype": "Code", + "hidden": 1, + "label": "Contact List", + "read_only": 1 } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-09-18 14:14:24.953014", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Shareholder", - "name_case": "Title Case", - "owner": "Administrator", + ], + "modified": "2019-11-17 23:24:11.395882", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Shareholder", + "name_case": "Title Case", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, "write": 1 - }, + }, { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Accounts Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "share": 1, "write": 1 - }, + }, { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Accounts User", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts User", + "share": 1, "write": 1 } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "search_fields": "folio_no", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "title", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + ], + "search_fields": "folio_no", + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "title", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/accounts/doctype/shipping_rule/shipping_rule.py b/erpnext/accounts/doctype/shipping_rule/shipping_rule.py index a20f5c08726..8c4efbebe88 100644 --- a/erpnext/accounts/doctype/shipping_rule/shipping_rule.py +++ b/erpnext/accounts/doctype/shipping_rule/shipping_rule.py @@ -70,7 +70,7 @@ class ShippingRule(Document): def get_shipping_amount_from_rules(self, value): for condition in self.get("conditions"): - if not condition.to_value or (flt(condition.from_value) <= value <= flt(condition.to_value)): + if not condition.to_value or (flt(condition.from_value) <= flt(value) <= flt(condition.to_value)): return condition.shipping_amount return 0.0 diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index feb598a2e51..bb1b7e392dc 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -90,8 +90,12 @@ def merge_similar_entries(gl_map): else: merged_gl_map.append(entry) + company = gl_map[0].company if gl_map else erpnext.get_default_company() + company_currency = erpnext.get_company_currency(company) + precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"), company_currency) + # filter zero debit and credit entries - merged_gl_map = filter(lambda x: flt(x.debit, 9)!=0 or flt(x.credit, 9)!=0, merged_gl_map) + merged_gl_map = filter(lambda x: flt(x.debit, precision)!=0 or flt(x.credit, precision)!=0, merged_gl_map) merged_gl_map = list(merged_gl_map) return merged_gl_map diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index 59936d5116d..156f2181b87 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -23,7 +23,7 @@ class DuplicatePartyAccountError(frappe.ValidationError): pass @frappe.whitelist() def get_party_details(party=None, account=None, party_type="Customer", company=None, posting_date=None, bill_date=None, price_list=None, currency=None, doctype=None, ignore_permissions=False, fetch_payment_terms_template=True, - party_address=None, shipping_address=None, pos_profile=None): + party_address=None, company_address=None, shipping_address=None, pos_profile=None): if not party: return {} @@ -31,14 +31,14 @@ def get_party_details(party=None, account=None, party_type="Customer", company=N frappe.throw(_("{0}: {1} does not exists").format(party_type, party)) return _get_party_details(party, account, party_type, company, posting_date, bill_date, price_list, currency, doctype, ignore_permissions, - fetch_payment_terms_template, party_address, shipping_address, pos_profile) + fetch_payment_terms_template, party_address, company_address, shipping_address, pos_profile) def _get_party_details(party=None, account=None, party_type="Customer", company=None, posting_date=None, bill_date=None, price_list=None, currency=None, doctype=None, ignore_permissions=False, - fetch_payment_terms_template=True, party_address=None, shipping_address=None, pos_profile=None): + fetch_payment_terms_template=True, party_address=None, company_address=None,shipping_address=None, pos_profile=None): - out = frappe._dict(set_account_and_due_date(party, account, party_type, company, posting_date, bill_date, doctype)) - party = out[party_type.lower()] + party_details = frappe._dict(set_account_and_due_date(party, account, party_type, company, posting_date, bill_date, doctype)) + party = party_details[party_type.lower()] if not ignore_permissions and not frappe.has_permission(party_type, "read", party): frappe.throw(_("Not permitted for {0}").format(party), frappe.PermissionError) @@ -46,76 +46,81 @@ def _get_party_details(party=None, account=None, party_type="Customer", company= party = frappe.get_doc(party_type, party) currency = party.default_currency if party.get("default_currency") else get_company_currency(company) - party_address, shipping_address = set_address_details(out, party, party_type, doctype, company, party_address, shipping_address) - set_contact_details(out, party, party_type) - set_other_values(out, party, party_type) - set_price_list(out, party, party_type, price_list, pos_profile) + party_address, shipping_address = set_address_details(party_details, party, party_type, doctype, company, party_address, company_address, shipping_address) + set_contact_details(party_details, party, party_type) + set_other_values(party_details, party, party_type) + set_price_list(party_details, party, party_type, price_list, pos_profile) - out["tax_category"] = get_address_tax_category(party.get("tax_category"), + party_details["tax_category"] = get_address_tax_category(party.get("tax_category"), party_address, shipping_address if party_type != "Supplier" else party_address) - out["taxes_and_charges"] = set_taxes(party.name, party_type, posting_date, company, - customer_group=out.customer_group, supplier_group=out.supplier_group, tax_category=out.tax_category, - billing_address=party_address, shipping_address=shipping_address) + + if not party_details.get("taxes_and_charges"): + party_details["taxes_and_charges"] = set_taxes(party.name, party_type, posting_date, company, + customer_group=party_details.customer_group, supplier_group=party_details.supplier_group, tax_category=party_details.tax_category, + billing_address=party_address, shipping_address=shipping_address) if fetch_payment_terms_template: - out["payment_terms_template"] = get_pyt_term_template(party.name, party_type, company) + party_details["payment_terms_template"] = get_pyt_term_template(party.name, party_type, company) - if not out.get("currency"): - out["currency"] = currency + if not party_details.get("currency"): + party_details["currency"] = currency # sales team if party_type=="Customer": - out["sales_team"] = [{ + party_details["sales_team"] = [{ "sales_person": d.sales_person, "allocated_percentage": d.allocated_percentage or None } for d in party.get("sales_team")] # supplier tax withholding category if party_type == "Supplier" and party: - out["supplier_tds"] = frappe.get_value(party_type, party.name, "tax_withholding_category") + party_details["supplier_tds"] = frappe.get_value(party_type, party.name, "tax_withholding_category") - return out + return party_details -def set_address_details(out, party, party_type, doctype=None, company=None, party_address=None, shipping_address=None): +def set_address_details(party_details, party, party_type, doctype=None, company=None, party_address=None, company_address=None, shipping_address=None): billing_address_field = "customer_address" if party_type == "Lead" \ else party_type.lower() + "_address" - out[billing_address_field] = party_address or get_default_address(party_type, party.name) + party_details[billing_address_field] = party_address or get_default_address(party_type, party.name) if doctype: - out.update(get_fetch_values(doctype, billing_address_field, out[billing_address_field])) + party_details.update(get_fetch_values(doctype, billing_address_field, party_details[billing_address_field])) # address display - out.address_display = get_address_display(out[billing_address_field]) + party_details.address_display = get_address_display(party_details[billing_address_field]) # shipping address if party_type in ["Customer", "Lead"]: - out.shipping_address_name = shipping_address or get_party_shipping_address(party_type, party.name) - out.shipping_address = get_address_display(out["shipping_address_name"]) + party_details.shipping_address_name = shipping_address or get_party_shipping_address(party_type, party.name) + party_details.shipping_address = get_address_display(party_details["shipping_address_name"]) if doctype: - out.update(get_fetch_values(doctype, 'shipping_address_name', out.shipping_address_name)) + party_details.update(get_fetch_values(doctype, 'shipping_address_name', party_details.shipping_address_name)) - if doctype and doctype in ['Delivery Note', 'Sales Invoice']: - out.update(get_company_address(company)) - if out.company_address: - out.update(get_fetch_values(doctype, 'company_address', out.company_address)) - get_regional_address_details(out, doctype, company) + if company_address: + party_details.update({'company_address': company_address}) + else: + party_details.update(get_company_address(company)) - elif doctype and doctype == "Purchase Invoice": - out.update(get_company_address(company)) - if out.company_address: - out["shipping_address"] = shipping_address or out["company_address"] - out.shipping_address_display = get_address_display(out["shipping_address"]) - out.update(get_fetch_values(doctype, 'shipping_address', out.shipping_address)) - get_regional_address_details(out, doctype, company) + if doctype and doctype in ['Delivery Note', 'Sales Invoice', 'Sales Order']: + if party_details.company_address: + party_details.update(get_fetch_values(doctype, 'company_address', party_details.company_address)) + get_regional_address_details(party_details, doctype, company) - return out.get(billing_address_field), out.shipping_address_name + elif doctype and doctype in ["Purchase Invoice", "Purchase Order", "Purchase Receipt"]: + if party_details.company_address: + party_details["shipping_address"] = shipping_address or party_details["company_address"] + party_details.shipping_address_display = get_address_display(party_details["shipping_address"]) + party_details.update(get_fetch_values(doctype, 'shipping_address', party_details.shipping_address)) + get_regional_address_details(party_details, doctype, company) + + return party_details.get(billing_address_field), party_details.shipping_address_name @erpnext.allow_regional -def get_regional_address_details(out, doctype, company): +def get_regional_address_details(party_details, doctype, company): pass -def set_contact_details(out, party, party_type): - out.contact_person = get_default_contact(party_type, party.name) +def set_contact_details(party_details, party, party_type): + party_details.contact_person = get_default_contact(party_type, party.name) - if not out.contact_person: - out.update({ + if not party_details.contact_person: + party_details.update({ "contact_person": None, "contact_display": None, "contact_email": None, @@ -125,22 +130,22 @@ def set_contact_details(out, party, party_type): "contact_department": None }) else: - out.update(get_contact_details(out.contact_person)) + party_details.update(get_contact_details(party_details.contact_person)) -def set_other_values(out, party, party_type): +def set_other_values(party_details, party, party_type): # copy if party_type=="Customer": to_copy = ["customer_name", "customer_group", "territory", "language"] else: to_copy = ["supplier_name", "supplier_group", "language"] for f in to_copy: - out[f] = party.get(f) + party_details[f] = party.get(f) # fields prepended with default in Customer doctype for f in ['currency'] \ + (['sales_partner', 'commission_rate'] if party_type=="Customer" else []): if party.get("default_" + f): - out[f] = party.get("default_" + f) + party_details[f] = party.get("default_" + f) def get_default_price_list(party): """Return default price list for party (Document object)""" @@ -155,7 +160,7 @@ def get_default_price_list(party): return None -def set_price_list(out, party, party_type, given_price_list, pos=None): +def set_price_list(party_details, party, party_type, given_price_list, pos=None): # price list price_list = get_permitted_documents('Price List') @@ -173,9 +178,9 @@ def set_price_list(out, party, party_type, given_price_list, pos=None): price_list = get_default_price_list(party) or given_price_list if price_list: - out.price_list_currency = frappe.db.get_value("Price List", price_list, "currency", cache=True) + party_details.price_list_currency = frappe.db.get_value("Price List", price_list, "currency", cache=True) - out["selling_price_list" if party.doctype=="Customer" else "buying_price_list"] = price_list + party_details["selling_price_list" if party.doctype=="Customer" else "buying_price_list"] = price_list def set_account_and_due_date(party, account, party_type, company, posting_date, bill_date, doctype): diff --git a/erpnext/accounts/report/accounts_payable/accounts_payable.js b/erpnext/accounts/report/accounts_payable/accounts_payable.js index 8eb670de510..b1f427ca7f6 100644 --- a/erpnext/accounts/report/accounts_payable/accounts_payable.js +++ b/erpnext/accounts/report/accounts_payable/accounts_payable.js @@ -100,6 +100,11 @@ frappe.query_reports["Accounts Payable"] = { "fieldtype": "Link", "options": "Supplier Group" }, + { + "fieldname":"based_on_payment_terms", + "label": __("Based On Payment Terms"), + "fieldtype": "Check", + }, { "fieldname":"tax_id", "label": __("Tax Id"), diff --git a/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js b/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js index 5f0fdc9f2c7..4a9f1b0dc44 100644 --- a/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js +++ b/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js @@ -88,6 +88,11 @@ frappe.query_reports["Accounts Payable Summary"] = { "label": __("Supplier Group"), "fieldtype": "Link", "options": "Supplier Group" + }, + { + "fieldname":"based_on_payment_terms", + "label": __("Based On Payment Terms"), + "fieldtype": "Check", } ], diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 14906f2c2e6..2c53f6e9971 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -60,6 +60,7 @@ class ReceivablePayableReport(object): def get_data(self): self.get_gl_entries() + self.get_sales_invoices_or_customers_based_on_sales_person() self.voucher_balance = OrderedDict() self.init_voucher_balance() # invoiced, paid, credit_note, outstanding @@ -103,12 +104,18 @@ class ReceivablePayableReport(object): def get_invoices(self, gle): if gle.voucher_type in ('Sales Invoice', 'Purchase Invoice'): - self.invoices.add(gle.voucher_no) + if self.filters.get("sales_person"): + if gle.voucher_no in self.sales_person_records.get("Sales Invoice", []) \ + or gle.party in self.sales_person_records.get("Customer", []): + self.invoices.add(gle.voucher_no) + else: + self.invoices.add(gle.voucher_no) def update_voucher_balance(self, gle): # get the row where this balance needs to be updated # if its a payment, it will return the linked invoice or will be considered as advance row = self.get_voucher_balance(gle) + if not row: return # gle_balance will be the total "debit - credit" for receivable type reports and # and vice-versa for payable type reports gle_balance = self.get_gle_balance(gle) @@ -129,8 +136,13 @@ class ReceivablePayableReport(object): row.paid -= gle_balance def get_voucher_balance(self, gle): - voucher_balance = None + if self.filters.get("sales_person"): + against_voucher = gle.against_voucher or gle.voucher_no + if not (gle.party in self.sales_person_records.get("Customer", []) or \ + against_voucher in self.sales_person_records.get("Sales Invoice", [])): + return + voucher_balance = None if gle.against_voucher: # find invoice against_voucher = gle.against_voucher @@ -318,7 +330,7 @@ class ReceivablePayableReport(object): self.append_payment_term(row, d, term) def append_payment_term(self, row, d, term): - if self.filters.get("customer") and d.currency == d.party_account_currency: + if (self.filters.get("customer") or self.filters.get("supplier")) and d.currency == d.party_account_currency: invoiced = d.payment_amount else: invoiced = flt(flt(d.payment_amount) * flt(d.conversion_rate), self.currency_precision) @@ -512,6 +524,22 @@ class ReceivablePayableReport(object): order by posting_date, party""" .format(select_fields, conditions), values, as_dict=True) + def get_sales_invoices_or_customers_based_on_sales_person(self): + if self.filters.get("sales_person"): + lft, rgt = frappe.db.get_value("Sales Person", + self.filters.get("sales_person"), ["lft", "rgt"]) + + records = frappe.db.sql(""" + select distinct parent, parenttype + from `tabSales Team` steam + where parenttype in ('Customer', 'Sales Invoice') + and exists(select name from `tabSales Person` where lft >= %s and rgt <= %s and name = steam.sales_person) + """, (lft, rgt), as_dict=1) + + self.sales_person_records = frappe._dict() + for d in records: + self.sales_person_records.setdefault(d.parenttype, set()).add(d.parent) + def prepare_conditions(self): conditions = [""] values = [self.party_type, self.filters.report_date] @@ -564,16 +592,6 @@ class ReceivablePayableReport(object): conditions.append("party in (select name from tabCustomer where default_sales_partner=%s)") values.append(self.filters.get("sales_partner")) - if self.filters.get("sales_person"): - lft, rgt = frappe.db.get_value("Sales Person", - self.filters.get("sales_person"), ["lft", "rgt"]) - - conditions.append("""exists(select name from `tabSales Team` steam where - steam.sales_person in (select name from `tabSales Person` where lft >= {0} and rgt <= {1}) - and ((steam.parent = voucher_no and steam.parenttype = voucher_type) - or (steam.parent = against_voucher and steam.parenttype = against_voucher_type) - or (steam.parent = party and steam.parenttype = 'Customer')))""".format(lft, rgt)) - def add_supplier_filters(self, conditions, values): if self.filters.get("supplier_group"): conditions.append("""party in (select name from tabSupplier diff --git a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js index 0120608a8ff..d54824b6855 100644 --- a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js +++ b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js @@ -106,6 +106,11 @@ frappe.query_reports["Accounts Receivable Summary"] = { "label": __("Sales Person"), "fieldtype": "Link", "options": "Sales Person" + }, + { + "fieldname":"based_on_payment_terms", + "label": __("Based On Payment Terms"), + "fieldtype": "Check", } ], diff --git a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py index 8955830e09b..b607c0f7028 100644 --- a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py +++ b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py @@ -36,7 +36,7 @@ class AccountsReceivableSummary(ReceivablePayableReport): self.filters.report_date) or {} for party, party_dict in iteritems(self.party_total): - if party_dict.outstanding <= 0: + if party_dict.outstanding == 0: continue row = frappe._dict() diff --git a/erpnext/accounts/report/financial_statements.html b/erpnext/accounts/report/financial_statements.html index 4081723bf0f..50947ecf5ef 100644 --- a/erpnext/accounts/report/financial_statements.html +++ b/erpnext/accounts/report/financial_statements.html @@ -1,5 +1,6 @@ {% var report_columns = report.get_columns_for_print(); + report_columns = report_columns.filter(col => !col.hidden); if (report_columns.length > 8) { frappe.throw(__("Too many columns. Export the report and print it using a spreadsheet application.")); @@ -15,34 +16,35 @@ height: 37px; } -{% var letterhead= filters.letter_head || (frappe.get_doc(":Company", filters.company) && frappe.get_doc(":Company", filters.company).default_letter_head) %} -{% if(letterhead) { %} -
- {%= frappe.boot.letter_heads[letterhead].header %} -
-{% } %} +

{%= __(report.report_name) %}

{%= filters.company %}

+ {% if 'cost_center' in filters %}

{%= filters.cost_center %}

{% endif %} +

{%= filters.fiscal_year %}

-
{%= __("Currency") %} : {%= filters.presentation_currency || erpnext.get_currency(filters.company) %}
+
+ {%= __("Currency") %} : {%= filters.presentation_currency || erpnext.get_currency(filters.company) %} +
{% if (filters.from_date) { %} -

{%= frappe.datetime.str_to_user(filters.from_date) %} - {%= frappe.datetime.str_to_user(filters.to_date) %}

+
+ {%= frappe.datetime.str_to_user(filters.from_date) %} - {%= frappe.datetime.str_to_user(filters.to_date) %} +
{% } %}
- - {% for(var i=2, l=report_columns.length; i + {% for (let i=1, l=report_columns.length; i{%= report_columns[i].label %} {% } %} - {% for(var j=0, k=data.length-1; j {%= row.account_name %} - {% for(var i=2, l=report_columns.length; i - {% var fieldname = report_columns[i].fieldname; %} + {% const fieldname = report_columns[i].fieldname; %} {% if (!is_null(row[fieldname])) { %} - {%= format_currency(row[fieldname], filters.presentation_currency) %} + {%= frappe.format(row[fieldname], report_columns[i], {}, row) %} {% } %} {% } %} @@ -64,4 +66,6 @@ {% } %}
-

Printed On {%= frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string()) %}

+

+ Printed On {%= frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string()) %} +

diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py index 3c8de6026a6..40d5682726d 100644 --- a/erpnext/accounts/report/financial_statements.py +++ b/erpnext/accounts/report/financial_statements.py @@ -264,8 +264,8 @@ def filter_out_zero_value_rows(data, parent_children_map, show_zero_values=False def add_total_row(out, root_type, balance_must_be, period_list, company_currency): total_row = { - "account_name": "'" + _("Total {0} ({1})").format(_(root_type), _(balance_must_be)) + "'", - "account": "'" + _("Total {0} ({1})").format(_(root_type), _(balance_must_be)) + "'", + "account_name": _("Total {0} ({1})").format(_(root_type), _(balance_must_be)), + "account": _("Total {0} ({1})").format(_(root_type), _(balance_must_be)), "currency": company_currency } diff --git a/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.py b/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.py index bd2c34b3b4c..3e47906a98b 100644 --- a/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.py +++ b/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.py @@ -18,14 +18,17 @@ def execute(filters=None): return columns, data def get_data(filters, show_party_name): - party_name_field = "{0}_name".format(frappe.scrub(filters.get('party_type'))) + if filters.get('party_type') in ('Customer', 'Supplier', 'Employee', 'Member'): + party_name_field = "{0}_name".format(frappe.scrub(filters.get('party_type'))) if filters.get('party_type') == 'Student': party_name_field = 'first_name' elif filters.get('party_type') == 'Shareholder': party_name_field = 'title' + else: + party_name_field = 'name' party_filters = {"name": filters.get("party")} if filters.get("party") else {} - parties = frappe.get_all(filters.get("party_type"), fields = ["name", party_name_field], + parties = frappe.get_all(filters.get("party_type"), fields = ["name", party_name_field], filters = party_filters, order_by="name") company_currency = frappe.get_cached_value('Company', filters.company, "default_currency") opening_balances = get_opening_balances(filters) @@ -70,7 +73,7 @@ def get_data(filters, show_party_name): # totals for col in total_row: total_row[col] += row.get(col) - + row.update({ "currency": company_currency }) @@ -78,7 +81,7 @@ def get_data(filters, show_party_name): has_value = False if (opening_debit or opening_credit or debit or credit or closing_debit or closing_credit): has_value =True - + if cint(filters.show_zero_values) or has_value: data.append(row) @@ -94,9 +97,9 @@ def get_data(filters, show_party_name): def get_opening_balances(filters): gle = frappe.db.sql(""" - select party, sum(debit) as opening_debit, sum(credit) as opening_credit + select party, sum(debit) as opening_debit, sum(credit) as opening_credit from `tabGL Entry` - where company=%(company)s + where company=%(company)s and ifnull(party_type, '') = %(party_type)s and ifnull(party, '') != '' and (posting_date < %(from_date)s or ifnull(is_opening, 'No') = 'Yes') group by party""", { @@ -114,11 +117,11 @@ def get_opening_balances(filters): def get_balances_within_period(filters): gle = frappe.db.sql(""" - select party, sum(debit) as debit, sum(credit) as credit + select party, sum(debit) as debit, sum(credit) as credit from `tabGL Entry` - where company=%(company)s + where company=%(company)s and ifnull(party_type, '') = %(party_type)s and ifnull(party, '') != '' - and posting_date >= %(from_date)s and posting_date <= %(to_date)s + and posting_date >= %(from_date)s and posting_date <= %(to_date)s and ifnull(is_opening, 'No') = 'No' group by party""", { "company": filters.company, diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 94697be02f6..89c8467da26 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -569,7 +569,7 @@ def get_stock_and_account_balance(account=None, posting_date=None, company=None) warehouse_account = get_warehouse_account_map(company) - account_balance = get_balance_on(account, posting_date, in_account_currency=False) + account_balance = get_balance_on(account, posting_date, in_account_currency=False, ignore_account_permission=True) related_warehouses = [wh for wh, wh_details in warehouse_account.items() if wh_details.account == account and not wh_details.is_group] diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 56341ed1b1f..3e7f6833a0c 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -33,7 +33,7 @@ class Asset(AccountsController): self.make_asset_movement() if not self.booked_fixed_asset and is_cwip_accounting_enabled(self.asset_category): self.make_gl_entries() - + def before_cancel(self): self.cancel_auto_gen_movement() @@ -43,7 +43,7 @@ class Asset(AccountsController): self.set_status() delete_gl_entries(voucher_type='Asset', voucher_no=self.name) self.db_set('booked_fixed_asset', 0) - + def validate_asset_and_reference(self): if self.purchase_invoice or self.purchase_receipt: reference_doc = 'Purchase Invoice' if self.purchase_invoice else 'Purchase Receipt' @@ -51,8 +51,8 @@ class Asset(AccountsController): reference_doc = frappe.get_doc(reference_doc, reference_name) if reference_doc.get('company') != self.company: frappe.throw(_("Company of asset {0} and purchase document {1} doesn't matches.").format(self.name, reference_doc.get('name'))) - - + + if self.is_existing_asset and self.purchase_invoice: frappe.throw(_("Purchase Invoice cannot be made against an existing asset {0}").format(self.name)) @@ -135,7 +135,7 @@ class Asset(AccountsController): movement = frappe.get_doc('Asset Movement', movements[0].get('name')) movement.flags.ignore_validate = True movement.cancel() - + def make_asset_movement(self): reference_doctype = 'Purchase Receipt' if self.purchase_receipt else 'Purchase Invoice' reference_docname = self.purchase_receipt or self.purchase_invoice @@ -204,7 +204,7 @@ class Asset(AccountsController): if has_pro_rata and n==0: depreciation_amount, days, months = get_pro_rata_amt(d, depreciation_amount, self.available_for_use_date, d.depreciation_start_date) - + # For first depr schedule date will be the start date # so monthly schedule date is calculated by removing month difference between use date and start date monthly_schedule_date = add_months(d.depreciation_start_date, - months + 1) @@ -262,7 +262,7 @@ class Asset(AccountsController): else: date = add_months(monthly_schedule_date, r) amount = depreciation_amount / month_range - + self.append("schedules", { "schedule_date": date, "depreciation_amount": amount, @@ -517,15 +517,18 @@ def update_maintenance_status(): asset.set_status('Out of Order') def make_post_gl_entry(): - if not is_cwip_accounting_enabled(self.asset_category): - return - assets = frappe.db.sql_list(""" select name from `tabAsset` - where ifnull(booked_fixed_asset, 0) = 0 and available_for_use_date = %s""", nowdate()) + asset_categories = frappe.db.get_all('Asset Category', fields = ['name', 'enable_cwip_accounting']) - for asset in assets: - doc = frappe.get_doc('Asset', asset) - doc.make_gl_entries() + for asset_category in asset_categories: + if cint(asset_category.enable_cwip_accounting): + assets = frappe.db.sql_list(""" select name from `tabAsset` + where asset_category = %s and ifnull(booked_fixed_asset, 0) = 0 + and available_for_use_date = %s""", (asset_category.name, nowdate())) + + for asset in assets: + doc = frappe.get_doc('Asset', asset) + doc.make_gl_entries() def get_asset_naming_series(): meta = frappe.get_meta('Asset') @@ -607,13 +610,19 @@ def get_asset_account(account_name, asset=None, asset_category=None, company=Non if asset: account = get_asset_category_account(account_name, asset=asset, asset_category = asset_category, company = company) + + if not asset and not account: + account = get_asset_category_account(account_name, asset_category = asset_category, company = company) if not account: account = frappe.get_cached_value('Company', company, account_name) if not account: - frappe.throw(_("Set {0} in asset category {1} or company {2}") - .format(account_name.replace('_', ' ').title(), asset_category, company)) + if not asset_category: + frappe.throw(_("Set {0} in company {2}").format(account_name.replace('_', ' ').title(), company)) + else: + frappe.throw(_("Set {0} in asset category {1} or company {2}") + .format(account_name.replace('_', ' ').title(), asset_category, company)) return account @@ -652,10 +661,10 @@ def make_journal_entry(asset_name): def make_asset_movement(assets, purpose=None): import json from six import string_types - + if isinstance(assets, string_types): assets = json.loads(assets) - + if len(assets) == 0: frappe.throw(_('Atleast one asset has to be selected.')) diff --git a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js index 426caaad92d..8c737d066b9 100644 --- a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js +++ b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js @@ -26,5 +26,11 @@ frappe.query_reports["Fixed Asset Register"] = { fieldtype: "Link", options: "Finance Book" }, + { + fieldname:"date", + label: __("Date"), + fieldtype: "Date", + default: frappe.datetime.get_today() + }, ] }; diff --git a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py index f395499ad6b..57b68b4ed24 100644 --- a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py +++ b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import frappe from frappe import _ -from frappe.utils import cstr +from frappe.utils import cstr, today, flt def execute(filters=None): filters = frappe._dict(filters or {}) @@ -86,8 +86,8 @@ def get_columns(filters): "width": 90 }, { - "label": _("Current Value"), - "fieldname": "current_value", + "label": _("Asset Value"), + "fieldname": "asset_value", "options": "Currency", "width": 90 }, @@ -114,7 +114,7 @@ def get_data(filters): data = [] conditions = get_conditions(filters) - current_value_map = get_finance_book_value_map(filters.finance_book) + depreciation_amount_map = get_finance_book_value_map(filters.date, filters.finance_book) pr_supplier_map = get_purchase_receipt_supplier_map() pi_supplier_map = get_purchase_invoice_supplier_map() @@ -125,7 +125,9 @@ def get_data(filters): "available_for_use_date", "status", "purchase_invoice"]) for asset in assets_record: - if current_value_map.get(asset.name) is not None: + 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.name, "asset_name": asset.asset_name, @@ -138,19 +140,24 @@ def get_data(filters): "location": asset.location, "asset_category": asset.asset_category, "purchase_date": asset.purchase_date, - "current_value": current_value_map.get(asset.name) + "asset_value": asset_value } data.append(row) return data -def get_finance_book_value_map(finance_book=''): +def get_finance_book_value_map(date, finance_book=''): + if not date: + date = today() return frappe._dict(frappe.db.sql(''' Select - parent, value_after_depreciation - FROM `tabAsset Finance Book` + parent, SUM(depreciation_amount) + FROM `tabDepreciation Schedule` WHERE - parentfield='finance_books' - AND ifnull(finance_book, '')=%s''', cstr(finance_book))) + parentfield='schedules' + AND schedule_date<=%s + AND journal_entry IS NOT NULL + AND ifnull(finance_book, '')=%s + GROUP BY parent''', (date, cstr(finance_book)))) def get_purchase_receipt_supplier_map(): return frappe._dict(frappe.db.sql(''' Select diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js index c5fa98da09f..7b5e5c5cca0 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.js +++ b/erpnext/buying/doctype/purchase_order/purchase_order.js @@ -18,6 +18,7 @@ frappe.ui.form.on("Purchase Order", { return { filters: { "company": frm.doc.company, + "name": ['!=', frm.doc.supplier_warehouse], "is_group": 0 } } @@ -283,6 +284,8 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend( }) } + me.dialog.get_field('sub_con_rm_items').check_all_rows() + me.dialog.show() this.dialog.set_primary_action(__('Transfer'), function() { me.values = me.dialog.get_values(); diff --git a/erpnext/buying/doctype/purchase_order/regional/india.js b/erpnext/buying/doctype/purchase_order/regional/india.js new file mode 100644 index 00000000000..42d3995907f --- /dev/null +++ b/erpnext/buying/doctype/purchase_order/regional/india.js @@ -0,0 +1,3 @@ +{% include "erpnext/regional/india/taxes.js" %} + +erpnext.setup_auto_gst_taxation('Purchase Order'); \ No newline at end of file diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index 4506db64051..a0a1e8ed5c4 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -519,47 +519,62 @@ class TestPurchaseOrder(unittest.TestCase): def test_backflush_based_on_stock_entry(self): item_code = "_Test Subcontracted FG Item 1" make_subcontracted_item(item_code) + make_item('Sub Contracted Raw Material 1', { + 'is_stock_item': 1, + 'is_sub_contracted_item': 1 + }) update_backflush_based_on("Material Transferred for Subcontract") - po = create_purchase_order(item_code=item_code, qty=1, + + order_qty = 5 + po = create_purchase_order(item_code=item_code, qty=order_qty, is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC") - make_stock_entry(target="_Test Warehouse - _TC", qty=10, basic_rate=100) make_stock_entry(target="_Test Warehouse - _TC", item_code="_Test Item Home Desktop 100", qty=10, basic_rate=100) make_stock_entry(target="_Test Warehouse - _TC", item_code = "Test Extra Item 1", qty=100, basic_rate=100) make_stock_entry(target="_Test Warehouse - _TC", item_code = "Test Extra Item 2", qty=10, basic_rate=100) + make_stock_entry(target="_Test Warehouse - _TC", + item_code = "Sub Contracted Raw Material 1", qty=10, basic_rate=100) - rm_item = [ - {"item_code":item_code,"rm_item_code":"_Test Item","item_name":"_Test Item", - "qty":1,"warehouse":"_Test Warehouse - _TC","rate":100,"amount":100,"stock_uom":"Nos"}, + rm_items = [ + {"item_code":item_code,"rm_item_code":"Sub Contracted Raw Material 1","item_name":"_Test Item", + "qty":10,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos"}, {"item_code":item_code,"rm_item_code":"_Test Item Home Desktop 100","item_name":"_Test Item Home Desktop 100", - "qty":2,"warehouse":"_Test Warehouse - _TC","rate":100,"amount":200,"stock_uom":"Nos"}, + "qty":20,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos"}, {"item_code":item_code,"rm_item_code":"Test Extra Item 1","item_name":"Test Extra Item 1", - "qty":1,"warehouse":"_Test Warehouse - _TC","rate":100,"amount":200,"stock_uom":"Nos"}] + "qty":10,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos"}, + {'item_code': item_code, 'rm_item_code': 'Test Extra Item 2', 'stock_uom':'Nos', + 'qty': 10, 'warehouse': '_Test Warehouse - _TC', 'item_name':'Test Extra Item 2'}] - rm_item_string = json.dumps(rm_item) + rm_item_string = json.dumps(rm_items) se = frappe.get_doc(make_subcontract_transfer_entry(po.name, rm_item_string)) - se.append('items', { - 'item_code': "Test Extra Item 2", - "qty": 1, - "rate": 100, - "s_warehouse": "_Test Warehouse - _TC", - "t_warehouse": "_Test Warehouse 1 - _TC" - }) - se.set_missing_values() se.submit() pr = make_purchase_receipt(po.name) + + received_qty = 2 + # partial receipt + pr.get('items')[0].qty = received_qty pr.save() pr.submit() - se_items = sorted([d.item_code for d in se.get('items')]) - supplied_items = sorted([d.rm_item_code for d in pr.get('supplied_items')]) + transferred_items = sorted([d.item_code for d in se.get('items') if se.purchase_order == po.name]) + issued_items = sorted([d.rm_item_code for d in pr.get('supplied_items')]) + + self.assertEquals(transferred_items, issued_items) + self.assertEquals(pr.get('items')[0].rm_supp_cost, 2000) + + + transferred_rm_map = frappe._dict() + for item in rm_items: + transferred_rm_map[item.get('rm_item_code')] = item + + for item in pr.get('supplied_items'): + self.assertEqual(item.get('required_qty'), (transferred_rm_map[item.get('rm_item_code')].get('qty') / order_qty) * received_qty) - self.assertEquals(se_items, supplied_items) update_backflush_based_on("BOM") def test_advance_payment_entry_unlink_against_purchase_order(self): diff --git a/erpnext/buying/doctype/purchase_receipt_item_supplied/purchase_receipt_item_supplied.json b/erpnext/buying/doctype/purchase_receipt_item_supplied/purchase_receipt_item_supplied.json index 2e0fc94bc90..6f2fbe5c370 100644 --- a/erpnext/buying/doctype/purchase_receipt_item_supplied/purchase_receipt_item_supplied.json +++ b/erpnext/buying/doctype/purchase_receipt_item_supplied/purchase_receipt_item_supplied.json @@ -1,537 +1,168 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2013-02-22 01:27:42", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", + "creation": "2013-02-22 01:27:42", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "main_item_code", + "rm_item_code", + "description", + "batch_no", + "serial_no", + "col_break1", + "required_qty", + "consumed_qty", + "stock_uom", + "rate", + "amount", + "conversion_factor", + "current_stock", + "reference_name", + "bom_detail_no" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "main_item_code", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Item Code", - "length": 0, - "no_copy": 0, - "oldfieldname": "main_item_code", - "oldfieldtype": "Data", - "options": "Item", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "main_item_code", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Item Code", + "oldfieldname": "main_item_code", + "oldfieldtype": "Data", + "options": "Item", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "rm_item_code", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Raw Material Item Code", - "length": 0, - "no_copy": 0, - "oldfieldname": "rm_item_code", - "oldfieldtype": "Data", - "options": "Item", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "rm_item_code", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Raw Material Item Code", + "oldfieldname": "rm_item_code", + "oldfieldtype": "Data", + "options": "Item", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "description", - "fieldtype": "Text Editor", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 1, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Description", - "length": 0, - "no_copy": 0, - "oldfieldname": "description", - "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "300px", - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "fieldname": "description", + "fieldtype": "Text Editor", + "in_global_search": 1, + "in_list_view": 1, + "label": "Description", + "oldfieldname": "description", + "oldfieldtype": "Data", + "print_width": "300px", + "read_only": 1, "width": "300px" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "batch_no", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Batch No", - "length": 0, - "no_copy": 1, - "options": "Batch", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "batch_no", + "fieldtype": "Link", + "label": "Batch No", + "no_copy": 1, + "options": "Batch" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "serial_no", - "fieldtype": "Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Serial No", - "length": 0, - "no_copy": 1, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "serial_no", + "fieldtype": "Text", + "label": "Serial No", + "no_copy": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "col_break1", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "col_break1", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "required_qty", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Required Qty", - "length": 0, - "no_copy": 0, - "oldfieldname": "required_qty", - "oldfieldtype": "Currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "required_qty", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Required Qty", + "oldfieldname": "required_qty", + "oldfieldtype": "Currency", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "consumed_qty", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Consumed Qty", - "length": 0, - "no_copy": 0, - "oldfieldname": "consumed_qty", - "oldfieldtype": "Currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "consumed_qty", + "fieldtype": "Float", + "label": "Consumed Qty", + "oldfieldname": "consumed_qty", + "oldfieldtype": "Currency", + "read_only": 1, + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "stock_uom", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Stock Uom", - "length": 0, - "no_copy": 0, - "oldfieldname": "stock_uom", - "oldfieldtype": "Data", - "options": "UOM", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "stock_uom", + "fieldtype": "Link", + "label": "Stock Uom", + "oldfieldname": "stock_uom", + "oldfieldtype": "Data", + "options": "UOM", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "rate", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Rate", - "length": 0, - "no_copy": 0, - "oldfieldname": "rate", - "oldfieldtype": "Currency", - "options": "Company:company:default_currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "rate", + "fieldtype": "Currency", + "label": "Rate", + "oldfieldname": "rate", + "oldfieldtype": "Currency", + "options": "Company:company:default_currency", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Amount", - "length": 0, - "no_copy": 0, - "oldfieldname": "amount", - "oldfieldtype": "Currency", - "options": "Company:company:default_currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "amount", + "fieldtype": "Currency", + "label": "Amount", + "oldfieldname": "amount", + "oldfieldtype": "Currency", + "options": "Company:company:default_currency", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "conversion_factor", - "fieldtype": "Float", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Conversion Factor", - "length": 0, - "no_copy": 0, - "oldfieldname": "conversion_factor", - "oldfieldtype": "Currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "conversion_factor", + "fieldtype": "Float", + "hidden": 1, + "label": "Conversion Factor", + "oldfieldname": "conversion_factor", + "oldfieldtype": "Currency", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "current_stock", - "fieldtype": "Float", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Current Stock", - "length": 0, - "no_copy": 0, - "oldfieldname": "current_stock", - "oldfieldtype": "Currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "current_stock", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Current Stock", + "oldfieldname": "current_stock", + "oldfieldtype": "Currency", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "reference_name", - "fieldtype": "Data", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Reference Name", - "length": 0, - "no_copy": 0, - "oldfieldname": "reference_name", - "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "reference_name", + "fieldtype": "Data", + "hidden": 1, + "in_list_view": 1, + "label": "Reference Name", + "oldfieldname": "reference_name", + "oldfieldtype": "Data", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "bom_detail_no", - "fieldtype": "Data", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "BOM Detail No", - "length": 0, - "no_copy": 0, - "oldfieldname": "bom_detail_no", - "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldname": "bom_detail_no", + "fieldtype": "Data", + "hidden": 1, + "in_list_view": 1, + "label": "BOM Detail No", + "oldfieldname": "bom_detail_no", + "oldfieldtype": "Data", + "read_only": 1 } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 1, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2019-01-07 16:51:59.536291", - "modified_by": "Administrator", - "module": "Buying", - "name": "Purchase Receipt Item Supplied", - "owner": "wasim@webnotestech.com", - "permissions": [], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + ], + "idx": 1, + "istable": 1, + "modified": "2019-11-21 16:25:29.909112", + "modified_by": "Administrator", + "module": "Buying", + "name": "Purchase Receipt Item Supplied", + "owner": "wasim@webnotestech.com", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/change_log/v12/v12_3_0.md b/erpnext/change_log/v12/v12_3_0.md new file mode 100644 index 00000000000..6ac71df564f --- /dev/null +++ b/erpnext/change_log/v12/v12_3_0.md @@ -0,0 +1,33 @@ +# Version 12.3.0 Release Notes + +### Accounting + +1. Statewise GST taxation for India + - Added GST state in the tax category + - Added tax category in the address, sales/purchase tax template + - Based on the address system will fetch the tax template +2. Accounts Payable report based on payment terms +3. Trial Balance Report with filter "Party Name" +4. Fixed asset register report with date filters + +### CRM + +1. Appointment Scheduling + - Configure the appointment slots using Appointment Booking Settings + - Users can book the appointment through the portal based on slot availability + +### HR + +1. Refactored Employee Attendance Tool +2. Set allocated amount in employee advance as per total amount + +### Fixes + +1. Stock entry decimal issue while creating the GL entries +2. Item wise stock balance report +3. Valuation of subcontracting finished good item +4. Not able to create Instructor, Student entries +5. Pricing rule for a product discount +6. POS for serialized items +7. Not able to cancel share transfer entry +8. Ledger entries for compensatory off were not getting created diff --git a/erpnext/config/crm.py b/erpnext/config/crm.py index eba6c7a02a5..cf1021948a5 100644 --- a/erpnext/config/crm.py +++ b/erpnext/config/crm.py @@ -46,6 +46,16 @@ def get_data(): "name": "Contract", "description": _("Helps you keep tracks of Contracts based on Supplier, Customer and Employee"), }, + { + "type": "doctype", + "name": "Appointment", + "description" : _("Helps you manage appointments with your leads"), + }, + { + "type": "doctype", + "name": "Newsletter", + "label": _("Newsletter"), + } ] }, { @@ -165,6 +175,11 @@ def get_data(): "type": "doctype", "name": "SMS Settings", "description": _("Setup SMS gateway settings") + }, + { + "type": "doctype", + "label": _("Email Group"), + "name": "Email Group", } ] }, diff --git a/erpnext/config/stock.py b/erpnext/config/stock.py index 441a3ab4ec3..e24d7b88df1 100644 --- a/erpnext/config/stock.py +++ b/erpnext/config/stock.py @@ -241,6 +241,10 @@ def get_data(): "type": "doctype", "name": "Quality Inspection Template", }, + { + "type": "doctype", + "name": "Quick Stock Balance", + }, ] }, { diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index a912ef00d15..6150516ac8c 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -5,15 +5,17 @@ from __future__ import unicode_literals import frappe, erpnext import json from frappe import _, throw -from frappe.utils import today, flt, cint, fmt_money, formatdate, getdate, add_days, add_months, get_last_day, nowdate -from erpnext.stock.get_item_details import get_conversion_factor +from frappe.utils import (today, flt, cint, fmt_money, formatdate, + getdate, add_days, add_months, get_last_day, nowdate, get_link_to_form) +from erpnext.stock.get_item_details import get_conversion_factor, get_item_details from erpnext.setup.utils import get_exchange_rate from erpnext.accounts.utils import get_fiscal_years, validate_fiscal_year, get_account_currency from erpnext.utilities.transaction_base import TransactionBase from erpnext.buying.utils import update_last_purchase_rate from erpnext.controllers.sales_and_purchase_return import validate_return from erpnext.accounts.party import get_party_account_currency, validate_party_frozen_disabled -from erpnext.accounts.doctype.pricing_rule.utils import validate_pricing_rules +from erpnext.accounts.doctype.pricing_rule.utils import (apply_pricing_rule_on_transaction, + apply_pricing_rule_for_free_items, get_applied_pricing_rules) from erpnext.exceptions import InvalidCurrency from six import text_type from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions @@ -59,7 +61,6 @@ class AccountsController(TransactionBase): _('{0} is blocked so this transaction cannot proceed'.format(supplier_name)), raise_exception=1) def validate(self): - if not self.get('is_return'): self.validate_qty_is_not_zero() @@ -98,10 +99,22 @@ class AccountsController(TransactionBase): if self.is_return: self.validate_qty() + else: + self.validate_deferred_start_and_end_date() validate_regional(self) if self.doctype != 'Material Request': - validate_pricing_rules(self) + apply_pricing_rule_on_transaction(self) + + def validate_deferred_start_and_end_date(self): + for d in self.items: + if d.get("enable_deferred_revenue") or d.get("enable_deferred_expense"): + if not (d.service_start_date and d.service_end_date): + frappe.throw(_("Row #{0}: Service Start and End Date is required for deferred accounting").format(d.idx)) + elif getdate(d.service_start_date) > getdate(d.service_end_date): + frappe.throw(_("Row #{0}: Service Start Date cannot be greater than Service End Date").format(d.idx)) + elif getdate(self.posting_date) > getdate(d.service_end_date): + frappe.throw(_("Row #{0}: Service End Date cannot be before Invoice Posting Date").format(d.idx)) def validate_invoice_documents_schedule(self): self.validate_payment_schedule_dates() @@ -232,7 +245,6 @@ class AccountsController(TransactionBase): def set_missing_item_details(self, for_validate=False): """set missing item values""" - from erpnext.stock.get_item_details import get_item_details from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos if hasattr(self, "items"): @@ -244,7 +256,6 @@ class AccountsController(TransactionBase): document_type = "{} Item".format(self.doctype) parent_dict.update({"document_type": document_type}) - self.set('pricing_rules', []) # party_name field used for customer in quotation if self.doctype == "Quotation" and self.quotation_to == "Customer" and parent_dict.get("party_name"): parent_dict.update({"customer": parent_dict.get("party_name")}) @@ -264,7 +275,7 @@ class AccountsController(TransactionBase): if self.get("is_subcontracted"): args["is_subcontracted"] = self.is_subcontracted - ret = get_item_details(args, self, overwrite_warehouse=False) + ret = get_item_details(args, self, for_validate=True, overwrite_warehouse=False) for fieldname, value in ret.items(): if item.meta.get_field(fieldname) and value is not None: @@ -285,24 +296,42 @@ class AccountsController(TransactionBase): if self.doctype in ["Purchase Invoice", "Sales Invoice"] and item.meta.get_field('is_fixed_asset'): item.set('is_fixed_asset', ret.get('is_fixed_asset', 0)) - if ret.get("pricing_rules") and not ret.get("validate_applied_rule", 0): - # if user changed the discount percentage then set user's discount percentage ? - item.set("pricing_rules", ret.get("pricing_rules")) - item.set("discount_percentage", ret.get("discount_percentage")) - item.set("discount_amount", ret.get("discount_amount")) - if ret.get("pricing_rule_for") == "Rate": - item.set("price_list_rate", ret.get("price_list_rate")) - - if item.get("price_list_rate"): - item.rate = flt(item.price_list_rate * - (1.0 - (flt(item.discount_percentage) / 100.0)), item.precision("rate")) - - if item.get('discount_amount'): - item.rate = item.price_list_rate - item.discount_amount + if ret.get("pricing_rules"): + self.apply_pricing_rule_on_items(item, ret) if self.doctype == "Purchase Invoice": self.set_expense_account(for_validate) + def apply_pricing_rule_on_items(self, item, pricing_rule_args): + if not pricing_rule_args.get("validate_applied_rule", 0): + # if user changed the discount percentage then set user's discount percentage ? + if pricing_rule_args.get("price_or_product_discount") == 'Price': + item.set("pricing_rules", pricing_rule_args.get("pricing_rules")) + item.set("discount_percentage", pricing_rule_args.get("discount_percentage")) + item.set("discount_amount", pricing_rule_args.get("discount_amount")) + if pricing_rule_args.get("pricing_rule_for") == "Rate": + item.set("price_list_rate", pricing_rule_args.get("price_list_rate")) + + if item.get("price_list_rate"): + item.rate = flt(item.price_list_rate * + (1.0 - (flt(item.discount_percentage) / 100.0)), item.precision("rate")) + + if item.get('discount_amount'): + item.rate = item.price_list_rate - item.discount_amount + + elif pricing_rule_args.get('free_item_data'): + apply_pricing_rule_for_free_items(self, pricing_rule_args.get('free_item_data')) + + elif pricing_rule_args.get("validate_applied_rule"): + for pricing_rule in get_applied_pricing_rules(item): + pricing_rule_doc = frappe.get_cached_doc("Pricing Rule", pricing_rule) + for field in ['discount_percentage', 'discount_amount', 'rate']: + if item.get(field) < pricing_rule_doc.get(field): + title = get_link_to_form("Pricing Rule", pricing_rule) + + frappe.msgprint(_("Row {0}: user has not applied the rule {1} on the item {2}") + .format(item.idx, frappe.bold(title), frappe.bold(item.item_code))) + def set_taxes(self): if not self.meta.get_field("taxes"): return @@ -397,9 +426,10 @@ class AccountsController(TransactionBase): return gl_dict def validate_qty_is_not_zero(self): - for item in self.items: - if not item.qty: - frappe.throw(_("Item quantity can not be zero")) + if self.doctype != "Purchase Receipt": + for item in self.items: + if not item.qty: + frappe.throw(_("Item quantity can not be zero")) def validate_account_currency(self, account, account_currency=None): valid_currency = [self.company_currency] diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index d12643af820..75b896bb134 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -221,7 +221,7 @@ class BuyingController(StockController): "backflush_raw_materials_of_subcontract_based_on") if (self.doctype == 'Purchase Receipt' and backflush_raw_materials_based_on != 'BOM'): - self.update_raw_materials_supplied_based_on_stock_entries(raw_material_table) + self.update_raw_materials_supplied_based_on_stock_entries() else: for item in self.get("items"): if self.doctype in ["Purchase Receipt", "Purchase Invoice"]: @@ -241,41 +241,96 @@ class BuyingController(StockController): if self.is_subcontracted == "No" and self.get("supplied_items"): self.set('supplied_items', []) - def update_raw_materials_supplied_based_on_stock_entries(self, raw_material_table): - self.set(raw_material_table, []) - purchase_orders = [d.purchase_order for d in self.items] - if purchase_orders: - items = get_subcontracted_raw_materials_from_se(purchase_orders) - backflushed_raw_materials = get_backflushed_subcontracted_raw_materials_from_se(purchase_orders, self.name) + def update_raw_materials_supplied_based_on_stock_entries(self): + self.set('supplied_items', []) - for d in items: - qty = d.qty - backflushed_raw_materials.get(d.item_code, 0) - rm = self.append(raw_material_table, {}) - rm.rm_item_code = d.item_code - rm.item_name = d.item_name - rm.main_item_code = d.main_item_code - rm.description = d.description - rm.stock_uom = d.stock_uom - rm.required_qty = qty - rm.consumed_qty = qty - rm.serial_no = d.serial_no - rm.batch_no = d.batch_no + purchase_orders = set([d.purchase_order for d in self.items]) - # get raw materials rate - from erpnext.stock.utils import get_incoming_rate - rm.rate = get_incoming_rate({ - "item_code": d.item_code, - "warehouse": self.supplier_warehouse, - "posting_date": self.posting_date, - "posting_time": self.posting_time, - "qty": -1 * qty, - "serial_no": rm.serial_no - }) - if not rm.rate: - rm.rate = get_valuation_rate(d.item_code, self.supplier_warehouse, - self.doctype, self.name, currency=self.company_currency, company = self.company) + # qty of raw materials backflushed (for each item per purchase order) + backflushed_raw_materials_map = get_backflushed_subcontracted_raw_materials(purchase_orders) - rm.amount = qty * flt(rm.rate) + # qty of "finished good" item yet to be received + qty_to_be_received_map = get_qty_to_be_received(purchase_orders) + + for item in self.get('items'): + # reset raw_material cost + item.rm_supp_cost = 0 + + # qty of raw materials transferred to the supplier + transferred_raw_materials = get_subcontracted_raw_materials_from_se(item.purchase_order, item.item_code) + + non_stock_items = get_non_stock_items(item.purchase_order, item.item_code) + + item_key = '{}{}'.format(item.item_code, item.purchase_order) + + fg_yet_to_be_received = qty_to_be_received_map.get(item_key) + + transferred_batch_qty_map = get_transferred_batch_qty_map(item.purchase_order, item.item_code) + backflushed_batch_qty_map = get_backflushed_batch_qty_map(item.purchase_order, item.item_code) + + for raw_material in transferred_raw_materials + non_stock_items: + rm_item_key = '{}{}'.format(raw_material.rm_item_code, item.purchase_order) + raw_material_data = backflushed_raw_materials_map.get(rm_item_key, {}) + + consumed_qty = raw_material_data.get('qty', 0) + consumed_serial_nos = raw_material_data.get('serial_nos', '') + consumed_batch_nos = raw_material_data.get('batch_nos', '') + + transferred_qty = raw_material.qty + + rm_qty_to_be_consumed = transferred_qty - consumed_qty + + # backflush all remaining transferred qty in the last Purchase Receipt + if fg_yet_to_be_received == item.qty: + qty = rm_qty_to_be_consumed + else: + qty = (rm_qty_to_be_consumed / fg_yet_to_be_received) * item.qty + + if frappe.get_cached_value('UOM', raw_material.stock_uom, 'must_be_whole_number'): + qty = frappe.utils.ceil(qty) + + if qty > rm_qty_to_be_consumed: + qty = rm_qty_to_be_consumed + + if not qty: continue + + if raw_material.serial_nos: + set_serial_nos(raw_material, consumed_serial_nos, qty) + + if raw_material.batch_nos: + batches_qty = get_batches_with_qty(raw_material.rm_item_code, raw_material.main_item_code, + qty, transferred_batch_qty_map, backflushed_batch_qty_map) + for batch_data in batches_qty: + qty = batch_data['qty'] + raw_material.batch_no = batch_data['batch'] + self.append_raw_material_to_be_backflushed(item, raw_material, qty) + else: + self.append_raw_material_to_be_backflushed(item, raw_material, qty) + + def append_raw_material_to_be_backflushed(self, fg_item_doc, raw_material_data, qty): + rm = self.append('supplied_items', {}) + rm.update(raw_material_data) + + rm.required_qty = qty + rm.consumed_qty = qty + + if not raw_material_data.get('non_stock_item'): + from erpnext.stock.utils import get_incoming_rate + rm.rate = get_incoming_rate({ + "item_code": raw_material_data.rm_item_code, + "warehouse": self.supplier_warehouse, + "posting_date": self.posting_date, + "posting_time": self.posting_time, + "qty": -1 * qty, + "serial_no": rm.serial_no + }) + + if not rm.rate: + rm.rate = get_valuation_rate(raw_material_data.item_code, self.supplier_warehouse, + self.doctype, self.name, currency=self.company_currency, company=self.company) + + rm.amount = qty * flt(rm.rate) + fg_item_doc.rm_supp_cost += rm.amount def update_raw_materials_supplied_based_on_bom(self, item, raw_material_table): exploded_item = 1 @@ -387,9 +442,11 @@ class BuyingController(StockController): item_codes = list(set(item.item_code for item in self.get("items"))) if item_codes: - self._sub_contracted_items = [r[0] for r in frappe.db.sql("""select name - from `tabItem` where name in (%s) and is_sub_contracted_item=1""" % \ - (", ".join((["%s"]*len(item_codes))),), item_codes)] + items = frappe.get_all('Item', filters={ + 'name': ['in', item_codes], + 'is_sub_contracted_item': 1 + }) + self._sub_contracted_items = [item.name for item in items] return self._sub_contracted_items @@ -722,28 +779,72 @@ def get_items_from_bom(item_code, bom, exploded_item=1): return bom_items -def get_subcontracted_raw_materials_from_se(purchase_orders): - return frappe.db.sql(""" - select - sed.item_name, sed.item_code, sum(sed.qty) as qty, sed.description, - sed.stock_uom, sed.subcontracted_item as main_item_code, sed.serial_no, sed.batch_no - from `tabStock Entry` se,`tabStock Entry Detail` sed - where - se.name = sed.parent and se.docstatus=1 and se.purpose='Send to Subcontractor' - and se.purchase_order in (%s) and ifnull(sed.t_warehouse, '') != '' - group by sed.item_code, sed.t_warehouse - """ % (','.join(['%s'] * len(purchase_orders))), tuple(purchase_orders), as_dict=1) +def get_subcontracted_raw_materials_from_se(purchase_order, fg_item): + common_query = """ + SELECT + sed.item_code AS rm_item_code, + SUM(sed.qty) AS qty, + sed.description, + sed.stock_uom, + sed.subcontracted_item AS main_item_code, + {serial_no_concat_syntax} AS serial_nos, + {batch_no_concat_syntax} AS batch_nos + FROM `tabStock Entry` se,`tabStock Entry Detail` sed + WHERE + se.name = sed.parent + AND se.docstatus=1 + AND se.purpose='Send to Subcontractor' + AND se.purchase_order = %s + AND IFNULL(sed.t_warehouse, '') != '' + AND sed.subcontracted_item = %s + GROUP BY sed.item_code, sed.subcontracted_item + """ + raw_materials = frappe.db.multisql({ + 'mariadb': common_query.format( + serial_no_concat_syntax="GROUP_CONCAT(sed.serial_no)", + batch_no_concat_syntax="GROUP_CONCAT(sed.batch_no)" + ), + 'postgres': common_query.format( + serial_no_concat_syntax="STRING_AGG(sed.serial_no, ',')", + batch_no_concat_syntax="STRING_AGG(sed.batch_no, ',')" + ) + }, (purchase_order, fg_item), as_dict=1) -def get_backflushed_subcontracted_raw_materials_from_se(purchase_orders, purchase_receipt): - return frappe._dict(frappe.db.sql(""" - select - prsi.rm_item_code as item_code, sum(prsi.consumed_qty) as qty - from `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pri, `tabPurchase Receipt Item Supplied` prsi - where - pr.name = pri.parent and pr.name = prsi.parent and pri.purchase_order in (%s) - and pri.item_code = prsi.main_item_code and pr.name != '%s' and pr.docstatus = 1 - group by prsi.rm_item_code - """ % (','.join(['%s'] * len(purchase_orders)), purchase_receipt), tuple(purchase_orders))) + return raw_materials + +def get_backflushed_subcontracted_raw_materials(purchase_orders): + common_query = """ + SELECT + CONCAT(prsi.rm_item_code, pri.purchase_order) AS item_key, + SUM(prsi.consumed_qty) AS qty, + {serial_no_concat_syntax} AS serial_nos, + {batch_no_concat_syntax} AS batch_nos + FROM `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pri, `tabPurchase Receipt Item Supplied` prsi + WHERE + pr.name = pri.parent + AND pr.name = prsi.parent + AND pri.purchase_order IN %s + AND pri.item_code = prsi.main_item_code + AND pr.docstatus = 1 + GROUP BY prsi.rm_item_code, pri.purchase_order + """ + + backflushed_raw_materials = frappe.db.multisql({ + 'mariadb': common_query.format( + serial_no_concat_syntax="GROUP_CONCAT(prsi.serial_no)", + batch_no_concat_syntax="GROUP_CONCAT(prsi.batch_no)" + ), + 'postgres': common_query.format( + serial_no_concat_syntax="STRING_AGG(prsi.serial_no, ',')", + batch_no_concat_syntax="STRING_AGG(prsi.batch_no, ',')" + ) + }, (purchase_orders, ), as_dict=1) + + backflushed_raw_materials_map = frappe._dict() + for item in backflushed_raw_materials: + backflushed_raw_materials_map.setdefault(item.item_key, item) + + return backflushed_raw_materials_map def get_asset_item_details(asset_items): asset_items_data = {} @@ -776,3 +877,125 @@ def validate_item_type(doc, fieldname, message): error_message = _("Following item {0} is not marked as {1} item. You can enable them as {1} item from its Item master".format(items, message)) frappe.throw(error_message) + +def get_qty_to_be_received(purchase_orders): + return frappe._dict(frappe.db.sql(""" + SELECT CONCAT(poi.`item_code`, poi.`parent`) AS item_key, + SUM(poi.`qty`) - SUM(poi.`received_qty`) AS qty_to_be_received + FROM `tabPurchase Order Item` poi + WHERE + poi.`parent` in %s + GROUP BY poi.`item_code`, poi.`parent` + HAVING SUM(poi.`qty`) > SUM(poi.`received_qty`) + """, (purchase_orders))) + +def get_non_stock_items(purchase_order, fg_item_code): + return frappe.db.sql(""" + SELECT + pois.main_item_code, + pois.rm_item_code, + item.description, + pois.required_qty AS qty, + pois.rate, + 1 as non_stock_item, + pois.stock_uom + FROM `tabPurchase Order Item Supplied` pois, `tabItem` item + WHERE + pois.`rm_item_code` = item.`name` + AND item.is_stock_item = 0 + AND pois.`parent` = %s + AND pois.`main_item_code` = %s + """, (purchase_order, fg_item_code), as_dict=1) + + +def set_serial_nos(raw_material, consumed_serial_nos, qty): + serial_nos = set(get_serial_nos(raw_material.serial_nos)) - \ + set(get_serial_nos(consumed_serial_nos)) + if serial_nos and qty <= len(serial_nos): + raw_material.serial_no = '\n'.join(list(serial_nos)[0:frappe.utils.cint(qty)]) + +def get_transferred_batch_qty_map(purchase_order, fg_item): + # returns + # { + # (item_code, fg_code): { + # batch1: 10, # qty + # batch2: 16 + # }, + # } + transferred_batch_qty_map = {} + transferred_batches = frappe.db.sql(""" + SELECT + sed.batch_no, + SUM(sed.qty) AS qty, + sed.item_code + FROM `tabStock Entry` se,`tabStock Entry Detail` sed + WHERE + se.name = sed.parent + AND se.docstatus=1 + AND se.purpose='Send to Subcontractor' + AND se.purchase_order = %s + AND sed.subcontracted_item = %s + AND sed.batch_no IS NOT NULL + GROUP BY + sed.batch_no, + sed.item_code + """, (purchase_order, fg_item), as_dict=1) + + for batch_data in transferred_batches: + transferred_batch_qty_map.setdefault((batch_data.item_code, fg_item), {}) + transferred_batch_qty_map[(batch_data.item_code, fg_item)][batch_data.batch_no] = batch_data.qty + + return transferred_batch_qty_map + +def get_backflushed_batch_qty_map(purchase_order, fg_item): + # returns + # { + # (item_code, fg_code): { + # batch1: 10, # qty + # batch2: 16 + # }, + # } + backflushed_batch_qty_map = {} + backflushed_batches = frappe.db.sql(""" + SELECT + pris.batch_no, + SUM(pris.consumed_qty) AS qty, + pris.rm_item_code AS item_code + FROM `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pri, `tabPurchase Receipt Item Supplied` pris + WHERE + pr.name = pri.parent + AND pri.parent = pris.parent + AND pri.purchase_order = %s + AND pri.item_code = pris.main_item_code + AND pr.docstatus = 1 + AND pris.main_item_code = %s + AND pris.batch_no IS NOT NULL + GROUP BY + pris.rm_item_code, pris.batch_no + """, (purchase_order, fg_item), as_dict=1) + + for batch_data in backflushed_batches: + backflushed_batch_qty_map.setdefault((batch_data.item_code, fg_item), {}) + backflushed_batch_qty_map[(batch_data.item_code, fg_item)][batch_data.batch_no] = batch_data.qty + + return backflushed_batch_qty_map + +def get_batches_with_qty(item_code, fg_item, required_qty, transferred_batch_qty_map, backflushed_batch_qty_map): + # Returns available batches to be backflushed based on requirements + transferred_batches = transferred_batch_qty_map.get((item_code, fg_item), {}) + backflushed_batches = backflushed_batch_qty_map.get((item_code, fg_item), {}) + + available_batches = [] + + for (batch, transferred_qty) in transferred_batches.items(): + backflushed_qty = backflushed_batches.get(batch, 0) + available_qty = transferred_qty - backflushed_qty + + if available_qty >= required_qty: + available_batches.append({'batch': batch, 'qty': required_qty}) + break + else: + available_batches.append({'batch': batch, 'qty': available_qty}) + required_qty -= available_qty + + return available_batches \ No newline at end of file diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index d2db9d005a7..66232d7ff1b 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -552,7 +552,7 @@ class calculate_taxes_and_totals(object): if item.price_list_rate: if item.pricing_rules and not self.doc.ignore_pricing_rule: for d in item.pricing_rules.split(','): - pricing_rule = frappe.get_doc('Pricing Rule', d) + pricing_rule = frappe.get_cached_doc('Pricing Rule', d) if (pricing_rule.margin_type == 'Amount' and pricing_rule.currency == self.doc.currency)\ or (pricing_rule.margin_type == 'Percentage'): diff --git a/erpnext/crm/doctype/appointment/__init__.py b/erpnext/crm/doctype/appointment/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/crm/doctype/appointment/appointment.js b/erpnext/crm/doctype/appointment/appointment.js new file mode 100644 index 00000000000..8888b569c46 --- /dev/null +++ b/erpnext/crm/doctype/appointment/appointment.js @@ -0,0 +1,17 @@ +// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Appointment', { + refresh: function(frm) { + if(frm.doc.lead){ + frm.add_custom_button(frm.doc.lead,()=>{ + frappe.set_route("Form", "Lead", frm.doc.lead); + }); + } + if(frm.doc.calendar_event){ + frm.add_custom_button(__(frm.doc.calendar_event),()=>{ + frappe.set_route("Form", "Event", frm.doc.calendar_event); + }); + } + } +}); diff --git a/erpnext/crm/doctype/appointment/appointment.json b/erpnext/crm/doctype/appointment/appointment.json new file mode 100644 index 00000000000..32df8ec4295 --- /dev/null +++ b/erpnext/crm/doctype/appointment/appointment.json @@ -0,0 +1,153 @@ +{ + "autoname": "format:APMT-{customer_name}-{####}", + "creation": "2019-08-27 10:48:27.926283", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "scheduled_time", + "status", + "customer_details_section", + "customer_name", + "customer_phone_number", + "customer_skype", + "customer_email", + "col_br_2", + "customer_details", + "linked_docs_section", + "lead", + "col_br_3", + "calendar_event" + ], + "fields": [ + { + "fieldname": "customer_details_section", + "fieldtype": "Section Break", + "label": "Customer Details" + }, + { + "fieldname": "customer_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Name", + "reqd": 1 + }, + { + "fieldname": "customer_phone_number", + "fieldtype": "Data", + "label": "Phone Number" + }, + { + "fieldname": "customer_skype", + "fieldtype": "Data", + "label": "Skype ID" + }, + { + "fieldname": "customer_details", + "fieldtype": "Long Text", + "label": "Details" + }, + { + "fieldname": "scheduled_time", + "fieldtype": "Datetime", + "in_list_view": 1, + "label": "Scheduled Time", + "reqd": 1 + }, + { + "fieldname": "status", + "fieldtype": "Select", + "label": "Status", + "options": "Open\nUnverified\nClosed", + "reqd": 1 + }, + { + "fieldname": "lead", + "fieldtype": "Link", + "label": "Lead", + "options": "Lead" + }, + { + "fieldname": "calendar_event", + "fieldtype": "Link", + "label": "Calendar Event", + "options": "Event" + }, + { + "fieldname": "col_br_2", + "fieldtype": "Column Break" + }, + { + "fieldname": "customer_email", + "fieldtype": "Data", + "label": "Email", + "reqd": 1 + }, + { + "fieldname": "linked_docs_section", + "fieldtype": "Section Break", + "label": "Linked Documents" + }, + { + "fieldname": "col_br_3", + "fieldtype": "Column Break" + } + ], + "modified": "2019-10-14 15:23:54.630731", + "modified_by": "Administrator", + "module": "CRM", + "name": "Appointment", + "name_case": "UPPER CASE", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Guest", + "share": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Sales Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Sales User", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py new file mode 100644 index 00000000000..b6c4c4707de --- /dev/null +++ b/erpnext/crm/doctype/appointment/appointment.py @@ -0,0 +1,223 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals + +import urllib +from collections import Counter +from datetime import timedelta + +import frappe +from frappe import _ +from frappe.model.document import Document +from frappe.utils import get_url +from frappe.utils.verified_command import verify_request, get_signed_params + + +class Appointment(Document): + + def find_lead_by_email(self): + lead_list = frappe.get_list( + 'Lead', filters={'email_id': self.customer_email}, ignore_permissions=True) + if lead_list: + return lead_list[0].name + return None + + def before_insert(self): + number_of_appointments_in_same_slot = frappe.db.count( + 'Appointment', filters={'scheduled_time': self.scheduled_time}) + number_of_agents = frappe.db.get_single_value('Appointment Booking Settings', 'number_of_agents') + if not number_of_agents == 0: + if (number_of_appointments_in_same_slot >= number_of_agents): + frappe.throw('Time slot is not available') + # Link lead + if not self.lead: + self.lead = self.find_lead_by_email() + + def after_insert(self): + if self.lead: + # Create Calendar event + self.auto_assign() + self.create_calendar_event() + else: + # Set status to unverified + self.status = 'Unverified' + # Send email to confirm + self.send_confirmation_email() + + def send_confirmation_email(self): + verify_url = self._get_verify_url() + template = 'confirm_appointment' + args = { + "link":verify_url, + "site_url":frappe.utils.get_url(), + "full_name":self.customer_name, + } + frappe.sendmail(recipients=[self.customer_email], + template=template, + args=args, + subject=_('Appointment Confirmation')) + if frappe.session.user == "Guest": + frappe.msgprint( + 'Please check your email to confirm the appointment') + else : + frappe.msgprint( + 'Appointment was created. But no lead was found. Please check the email to confirm') + + def on_change(self): + # Sync Calendar + if not self.calendar_event: + return + cal_event = frappe.get_doc('Event', self.calendar_event) + cal_event.starts_on = self.scheduled_time + cal_event.save(ignore_permissions=True) + + + def set_verified(self, email): + if not email == self.customer_email: + frappe.throw('Email verification failed.') + # Create new lead + self.create_lead_and_link() + # Remove unverified status + self.status = 'Open' + # Create calender event + self.auto_assign() + self.create_calendar_event() + self.save(ignore_permissions=True) + frappe.db.commit() + + def create_lead_and_link(self): + # Return if already linked + if self.lead: + return + lead = frappe.get_doc({ + 'doctype': 'Lead', + 'lead_name': self.customer_name, + 'email_id': self.customer_email, + 'notes': self.customer_details, + 'phone': self.customer_phone_number, + }) + lead.insert(ignore_permissions=True) + # Link lead + self.lead = lead.name + + def auto_assign(self): + from frappe.desk.form.assign_to import add as add_assignemnt + existing_assignee = self.get_assignee_from_latest_opportunity() + if existing_assignee: + # If the latest opportunity is assigned to someone + # Assign the appointment to the same + add_assignemnt({ + 'doctype': self.doctype, + 'name': self.name, + 'assign_to': existing_assignee + }) + return + if self._assign: + return + available_agents = _get_agents_sorted_by_asc_workload( + self.scheduled_time.date()) + for agent in available_agents: + if(_check_agent_availability(agent, self.scheduled_time)): + agent = agent[0] + add_assignemnt({ + 'doctype': self.doctype, + 'name': self.name, + 'assign_to': agent + }) + break + + def get_assignee_from_latest_opportunity(self): + if not self.lead: + return None + if not frappe.db.exists('Lead', self.lead): + return None + opporutnities = frappe.get_list( + 'Opportunity', + filters={ + 'party_name': self.lead, + }, + ignore_permissions=True, + order_by='creation desc') + if not opporutnities: + return None + latest_opportunity = frappe.get_doc('Opportunity', opporutnities[0].name ) + assignee = latest_opportunity._assign + if not assignee: + return None + assignee = frappe.parse_json(assignee)[0] + return assignee + + def create_calendar_event(self): + if self.calendar_event: + return + appointment_event = frappe.get_doc({ + 'doctype': 'Event', + 'subject': ' '.join(['Appointment with', self.customer_name]), + 'starts_on': self.scheduled_time, + 'status': 'Open', + 'type': 'Public', + 'send_reminder': frappe.db.get_single_value('Appointment Booking Settings', 'email_reminders'), + 'event_participants': [dict(reference_doctype='Lead', reference_docname=self.lead)] + }) + employee = _get_employee_from_user(self._assign) + if employee: + appointment_event.append('event_participants', dict( + reference_doctype='Employee', + reference_docname=employee.name)) + appointment_event.insert(ignore_permissions=True) + self.calendar_event = appointment_event.name + self.save(ignore_permissions=True) + + def _get_verify_url(self): + verify_route = '/book_appointment/verify' + params = { + 'email': self.customer_email, + 'appointment': self.name + } + return get_url(verify_route + '?' + get_signed_params(params)) + + +def _get_agents_sorted_by_asc_workload(date): + appointments = frappe.db.get_list('Appointment', fields='*') + agent_list = _get_agent_list_as_strings() + if not appointments: + return agent_list + appointment_counter = Counter(agent_list) + for appointment in appointments: + assigned_to = frappe.parse_json(appointment._assign) + if not assigned_to: + continue + if (assigned_to[0] in agent_list) and appointment.scheduled_time.date() == date: + appointment_counter[assigned_to[0]] += 1 + sorted_agent_list = appointment_counter.most_common() + sorted_agent_list.reverse() + return sorted_agent_list + + +def _get_agent_list_as_strings(): + agent_list_as_strings = [] + agent_list = frappe.get_doc('Appointment Booking Settings').agent_list + for agent in agent_list: + agent_list_as_strings.append(agent.user) + return agent_list_as_strings + + +def _check_agent_availability(agent_email, scheduled_time): + appointemnts_at_scheduled_time = frappe.get_list( + 'Appointment', filters={'scheduled_time': scheduled_time}) + for appointment in appointemnts_at_scheduled_time: + if appointment._assign == agent_email: + return False + return True + + +def _get_employee_from_user(user): + employee_docname = frappe.db.exists( + {'doctype': 'Employee', 'user_id': user}) + if employee_docname: + # frappe.db.exists returns a tuple of a tuple + return frappe.get_doc('Employee', employee_docname[0][0]) + return None + diff --git a/erpnext/crm/doctype/appointment/test_appointment.py b/erpnext/crm/doctype/appointment/test_appointment.py new file mode 100644 index 00000000000..50c98c59de6 --- /dev/null +++ b/erpnext/crm/doctype/appointment/test_appointment.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +import unittest +import datetime + + +def create_test_lead(): + test_lead = frappe.db.exists({'doctype': 'Lead', 'lead_name': 'Test Lead'}) + if test_lead: + return frappe.get_doc('Lead', test_lead[0][0]) + test_lead = frappe.get_doc({ + 'doctype': 'Lead', + 'lead_name': 'Test Lead', + 'email_id': 'test@example.com' + }) + test_lead.insert(ignore_permissions=True) + return test_lead + + +def create_test_appointments(): + test_appointment = frappe.db.exists( + {'doctype': 'Appointment', 'scheduled_time':datetime.datetime.now(),'email':'test@example.com'}) + if test_appointment: + return frappe.get_doc('Appointment', test_appointment[0][0]) + test_appointment = frappe.get_doc({ + 'doctype': 'Appointment', + 'email': 'test@example.com', + 'status': 'Open', + 'customer_name': 'Test Lead', + 'customer_phone_number': '666', + 'customer_skype': 'test', + 'customer_email': 'test@example.com', + 'scheduled_time': datetime.datetime.now() + }) + test_appointment.insert() + return test_appointment + + +class TestAppointment(unittest.TestCase): + test_appointment = test_lead = None + + def setUp(self): + self.test_lead = create_test_lead() + self.test_appointment = create_test_appointments() + + def test_calendar_event_created(self): + cal_event = frappe.get_doc( + 'Event', self.test_appointment.calendar_event) + self.assertEqual(cal_event.starts_on, + self.test_appointment.scheduled_time) + + def test_lead_linked(self): + lead = frappe.get_doc('Lead', self.test_lead.name) + self.assertIsNotNone(lead) diff --git a/erpnext/crm/doctype/appointment_booking_settings/__init__.py b/erpnext/crm/doctype/appointment_booking_settings/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.js b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.js new file mode 100644 index 00000000000..99b82148d2e --- /dev/null +++ b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.js @@ -0,0 +1,10 @@ +frappe.ui.form.on('Appointment Booking Settings', 'validate',check_times); +function check_times(frm) { + $.each(frm.doc.availability_of_slots || [], function (i, d) { + let from_time = Date.parse('01/01/2019 ' + d.from_time); + let to_time = Date.parse('01/01/2019 ' + d.to_time); + if (from_time > to_time) { + frappe.throw(__(`In row ${i + 1} of Appointment Booking Slots : "To Time" must be later than "From Time"`)); + } + }); +} \ No newline at end of file diff --git a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json new file mode 100644 index 00000000000..4b26e4901bd --- /dev/null +++ b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json @@ -0,0 +1,151 @@ +{ + "creation": "2019-08-27 10:56:48.309824", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "enable_scheduling", + "agent_detail_section", + "availability_of_slots", + "number_of_agents", + "agent_list", + "holiday_list", + "appointment_details_section", + "appointment_duration", + "email_reminders", + "advance_booking_days", + "success_details", + "success_redirect_url" + ], + "fields": [ + { + "fieldname": "availability_of_slots", + "fieldtype": "Table", + "label": "Availability Of Slots", + "options": "Appointment Booking Slots", + "reqd": 1 + }, + { + "default": "1", + "fieldname": "number_of_agents", + "fieldtype": "Int", + "hidden": 1, + "in_list_view": 1, + "label": "Number of Concurrent Appointments", + "read_only": 1, + "reqd": 1 + }, + { + "fieldname": "holiday_list", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Holiday List", + "options": "Holiday List", + "reqd": 1 + }, + { + "default": "60", + "fieldname": "appointment_duration", + "fieldtype": "Int", + "label": "Appointment Duration (In Minutes)", + "reqd": 1 + }, + { + "default": "0", + "description": "Notify customer and agent via email on the day of the appointment.", + "fieldname": "email_reminders", + "fieldtype": "Check", + "label": "Notify Via Email" + }, + { + "default": "7", + "fieldname": "advance_booking_days", + "fieldtype": "Int", + "label": "Number of days appointments can be booked in advance", + "reqd": 1 + }, + { + "fieldname": "agent_list", + "fieldtype": "Table MultiSelect", + "label": "Agents", + "options": "Assignment Rule User", + "reqd": 1 + }, + { + "default": "0", + "fieldname": "enable_scheduling", + "fieldtype": "Check", + "label": "Enable Appointment Scheduling", + "reqd": 1 + }, + { + "fieldname": "agent_detail_section", + "fieldtype": "Section Break", + "label": "Agent Details" + }, + { + "fieldname": "appointment_details_section", + "fieldtype": "Section Break", + "label": "Appointment Details" + }, + { + "fieldname": "success_details", + "fieldtype": "Section Break", + "label": "Success Settings" + }, + { + "description": "Leave blank for home.\nThis is relative to site URL, for example \"about\" will redirect to \"https://yoursitename.com/about\"", + "fieldname": "success_redirect_url", + "fieldtype": "Data", + "label": "Success Redirect URL" + } + ], + "issingle": 1, + "modified": "2019-11-26 12:14:17.669366", + "modified_by": "Administrator", + "module": "CRM", + "name": "Appointment Booking Settings", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "email": 1, + "print": 1, + "read": 1, + "role": "Guest", + "share": 1 + }, + { + "create": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "HR Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "Sales Manager", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py new file mode 100644 index 00000000000..27f14b1dbd8 --- /dev/null +++ b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe import _ +import datetime +from frappe.model.document import Document + + +class AppointmentBookingSettings(Document): + agent_list = [] #Hack + min_date = '01/01/1970 ' + format_string = "%d/%m/%Y %H:%M:%S" + + def validate(self): + self.validate_availability_of_slots() + + def save(self): + self.number_of_agents = len(self.agent_list) + super(AppointmentBookingSettings, self).save() + + def validate_availability_of_slots(self): + for record in self.availability_of_slots: + from_time = datetime.datetime.strptime( + self.min_date+record.from_time, self.format_string) + to_time = datetime.datetime.strptime( + self.min_date+record.to_time, self.format_string) + timedelta = to_time-from_time + self.validate_from_and_to_time(from_time, to_time) + self.duration_is_divisible(from_time, to_time) + + def validate_from_and_to_time(self, from_time, to_time): + if from_time > to_time: + err_msg = _('From Time cannot be later than To Time for {0}').format(record.day_of_week) + frappe.throw(_(err_msg)) + + def duration_is_divisible(self, from_time, to_time): + timedelta = to_time - from_time + if timedelta.total_seconds() % (self.appointment_duration * 60): + frappe.throw( + _('The difference between from time and To Time must be a multiple of Appointment')) diff --git a/erpnext/crm/doctype/appointment_booking_settings/test_appointment_booking_settings.py b/erpnext/crm/doctype/appointment_booking_settings/test_appointment_booking_settings.py new file mode 100644 index 00000000000..3dc3c399712 --- /dev/null +++ b/erpnext/crm/doctype/appointment_booking_settings/test_appointment_booking_settings.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestAppointmentBookingSettings(unittest.TestCase): + pass diff --git a/erpnext/crm/doctype/appointment_booking_slots/__init__.py b/erpnext/crm/doctype/appointment_booking_slots/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/crm/doctype/appointment_booking_slots/appointment_booking_slots.json b/erpnext/crm/doctype/appointment_booking_slots/appointment_booking_slots.json new file mode 100644 index 00000000000..ddf87386295 --- /dev/null +++ b/erpnext/crm/doctype/appointment_booking_slots/appointment_booking_slots.json @@ -0,0 +1,46 @@ +{ + "creation": "2019-11-19 10:49:49.494927", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "day_of_week", + "from_time", + "to_time" + ], + "fields": [ + { + "fieldname": "day_of_week", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Day Of Week", + "options": "Sunday\nMonday\nTuesday\nWednesday\nThursday\nFriday\nSaturday", + "reqd": 1 + }, + { + "fieldname": "from_time", + "fieldtype": "Time", + "in_list_view": 1, + "label": "From Time ", + "reqd": 1 + }, + { + "fieldname": "to_time", + "fieldtype": "Time", + "in_list_view": 1, + "label": "To Time", + "reqd": 1 + } + ], + "istable": 1, + "modified": "2019-11-19 10:49:49.494927", + "modified_by": "Administrator", + "module": "CRM", + "name": "Appointment Booking Slots", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/crm/doctype/appointment_booking_slots/appointment_booking_slots.py b/erpnext/crm/doctype/appointment_booking_slots/appointment_booking_slots.py new file mode 100644 index 00000000000..3cadbc95590 --- /dev/null +++ b/erpnext/crm/doctype/appointment_booking_slots/appointment_booking_slots.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019, 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 AppointmentBookingSlots(Document): + pass diff --git a/erpnext/crm/doctype/availability_of_slots/__init__.py b/erpnext/crm/doctype/availability_of_slots/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/crm/doctype/availability_of_slots/availability_of_slots.json b/erpnext/crm/doctype/availability_of_slots/availability_of_slots.json new file mode 100644 index 00000000000..b54af8dba4f --- /dev/null +++ b/erpnext/crm/doctype/availability_of_slots/availability_of_slots.json @@ -0,0 +1,46 @@ +{ + "creation": "2019-09-10 15:02:05.779434", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "day_of_week", + "from_time", + "to_time" + ], + "fields": [ + { + "fieldname": "day_of_week", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Day Of Week", + "options": "Sunday\nMonday\nTuesday\nWednesday\nThursday\nFriday\nSaturday", + "reqd": 1 + }, + { + "fieldname": "from_time", + "fieldtype": "Time", + "in_list_view": 1, + "label": "From Time", + "reqd": 1 + }, + { + "fieldname": "to_time", + "fieldtype": "Time", + "in_list_view": 1, + "label": "To Time", + "reqd": 1 + } + ], + "istable": 1, + "modified": "2019-09-10 15:05:20.406855", + "modified_by": "Administrator", + "module": "CRM", + "name": "Availability Of Slots", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/crm/doctype/availability_of_slots/availability_of_slots.py b/erpnext/crm/doctype/availability_of_slots/availability_of_slots.py new file mode 100644 index 00000000000..8258471eed1 --- /dev/null +++ b/erpnext/crm/doctype/availability_of_slots/availability_of_slots.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019, 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 AvailabilityOfSlots(Document): + pass diff --git a/erpnext/crm/doctype/email_campaign/email_campaign.py b/erpnext/crm/doctype/email_campaign/email_campaign.py index 3050d05a7c8..00a4bd1a322 100644 --- a/erpnext/crm/doctype/email_campaign/email_campaign.py +++ b/erpnext/crm/doctype/email_campaign/email_campaign.py @@ -41,7 +41,8 @@ class EmailCampaign(Document): email_campaign_exists = frappe.db.exists("Email Campaign", { "campaign_name": self.campaign_name, "recipient": self.recipient, - "status": ("in", ["In Progress", "Scheduled"]) + "status": ("in", ["In Progress", "Scheduled"]), + "name": ("!=", self.name) }) if email_campaign_exists: frappe.throw(_("The Campaign '{0}' already exists for the {1} '{2}'").format(self.campaign_name, self.email_campaign_for, self.recipient)) @@ -78,7 +79,7 @@ def send_mail(entry, email_campaign): comm = make( doctype = "Email Campaign", name = email_campaign.name, - subject = email_template.get("subject"), + subject = frappe.render_template(email_template.get("subject"), context), content = frappe.render_template(email_template.get("response"), context), sender = sender, recipients = recipient, diff --git a/erpnext/crm/doctype/lead/lead.json b/erpnext/crm/doctype/lead/lead.json index 3c22dc71999..eb68c679ba5 100644 --- a/erpnext/crm/doctype/lead/lead.json +++ b/erpnext/crm/doctype/lead/lead.json @@ -1,1436 +1,372 @@ { - "allow_copy": 0, "allow_events_in_timeline": 1, - "allow_guest_to_view": 0, "allow_import": 1, - "allow_rename": 0, "autoname": "naming_series:", - "beta": 0, "creation": "2013-04-10 11:45:37", - "custom": 0, - "docstatus": 0, "doctype": "DocType", "document_type": "Document", - "editable_grid": 0, + "engine": "InnoDB", + "field_order": [ + "organization_lead", + "lead_details", + "naming_series", + "lead_name", + "company_name", + "email_id", + "col_break123", + "lead_owner", + "status", + "gender", + "source", + "customer", + "campaign_name", + "image", + "section_break_12", + "contact_by", + "column_break_14", + "contact_date", + "ends_on", + "notes_section", + "notes", + "contact_info", + "address_desc", + "address_html", + "column_break2", + "contact_html", + "phone", + "salutation", + "mobile_no", + "fax", + "website", + "territory", + "more_info", + "type", + "market_segment", + "industry", + "request_type", + "column_break3", + "company", + "unsubscribed", + "blog_subscriber" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "fetch_if_empty": 0, + "default": "0", "fieldname": "organization_lead", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Lead is an Organization", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 1, - "translatable": 0, - "unique": 0 + "set_only_once": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "lead_details", "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "", - "length": 0, - "no_copy": 0, - "options": "fa fa-user", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "fa fa-user" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", - "fetch_if_empty": 0, "fieldname": "naming_series", "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Series", - "length": 0, "no_copy": 1, "oldfieldname": "naming_series", "oldfieldtype": "Select", "options": "CRM-LEAD-.YYYY.-", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 1, - "translatable": 0, - "unique": 0 + "set_only_once": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "eval:!doc.organization_lead", - "fetch_if_empty": 0, "fieldname": "lead_name", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, "in_global_search": 1, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Person Name", - "length": 0, - "no_copy": 0, "oldfieldname": "lead_name", "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "search_index": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "company_name", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "Organization Name", - "length": 0, - "no_copy": 0, "oldfieldname": "company_name", - "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "oldfieldtype": "Data" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "email_id", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Email Address", - "length": 0, - "no_copy": 0, "oldfieldname": "email_id", "oldfieldtype": "Data", "options": "Email", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "search_index": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "col_break123", "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "50%" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "__user", - "fetch_if_empty": 0, "fieldname": "lead_owner", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "Lead Owner", - "length": 0, - "no_copy": 0, "oldfieldname": "lead_owner", "oldfieldtype": "Link", "options": "User", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "search_index": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "Lead", - "fetch_if_empty": 0, "fieldname": "status", "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, "in_standard_filter": 1, "label": "Status", - "length": 0, "no_copy": 1, "oldfieldname": "status", "oldfieldtype": "Select", "options": "Lead\nOpen\nReplied\nOpportunity\nQuotation\nLost Quotation\nInterested\nConverted\nDo Not Contact", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, "reqd": 1, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "search_index": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "eval:!doc.organization_lead", - "fetch_if_empty": 0, "fieldname": "gender", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Gender", - "length": 0, - "no_copy": 0, - "options": "Gender", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Gender" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "fetch_if_empty": 0, "fieldname": "source", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Source", - "length": 0, - "no_copy": 0, "oldfieldname": "source", "oldfieldtype": "Select", - "options": "Lead Source", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Lead Source" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "eval:doc.source == 'Existing Customer'", - "fetch_if_empty": 0, "fieldname": "customer", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "From Customer", - "length": 0, "no_copy": 1, "oldfieldname": "customer", "oldfieldtype": "Link", - "options": "Customer", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Customer" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "eval: doc.source==\"Campaign\"", - "description": "", - "fetch_if_empty": 0, "fieldname": "campaign_name", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Campaign Name", - "length": 0, - "no_copy": 0, "oldfieldname": "campaign_name", "oldfieldtype": "Link", - "options": "Campaign", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Campaign" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "image", "fieldtype": "Attach Image", "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Image", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "print_hide": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "section_break_12", "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Follow Up", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Follow Up" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "contact_by", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Next Contact By", - "length": 0, - "no_copy": 0, "oldfieldname": "contact_by", "oldfieldtype": "Link", "options": "User", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "100px" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "column_break_14", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, "bold": 1, - "collapsible": 0, - "columns": 0, - "description": "", - "fetch_if_empty": 0, "fieldname": "contact_date", "fieldtype": "Datetime", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Next Contact Date", - "length": 0, "no_copy": 1, "oldfieldname": "contact_date", "oldfieldtype": "Date", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "100px" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, "bold": 1, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "ends_on", "fieldtype": "Datetime", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Ends On", - "length": 0, - "no_copy": 1, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "no_copy": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, "collapsible": 1, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "notes_section", "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Notes", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Notes" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "notes", "fieldtype": "Text Editor", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Notes", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Notes" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, "collapsible": 1, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "contact_info", "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Address & Contact", - "length": 0, - "no_copy": 0, "oldfieldtype": "Column Break", - "options": "fa fa-map-marker", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "fa fa-map-marker" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "eval:doc.__islocal", - "fetch_if_empty": 0, "fieldname": "address_desc", "fieldtype": "HTML", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Address Desc", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "print_hide": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "address_html", "fieldtype": "HTML", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Address HTML", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "column_break2", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "eval:doc.organization_lead", - "fetch_if_empty": 0, "fieldname": "contact_html", "fieldtype": "HTML", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Contact HTML", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "eval:!doc.organization_lead", - "fetch_if_empty": 0, "fieldname": "phone", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Phone", - "length": 0, - "no_copy": 0, "oldfieldname": "contact_no", - "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "oldfieldtype": "Data" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "eval:!doc.organization_lead", - "fetch_if_empty": 0, "fieldname": "salutation", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Salutation", - "length": 0, - "no_copy": 0, - "options": "Salutation", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Salutation" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "eval:!doc.organization_lead", - "fetch_if_empty": 0, "fieldname": "mobile_no", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Mobile No.", - "length": 0, - "no_copy": 0, "oldfieldname": "mobile_no", - "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "oldfieldtype": "Data" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "eval:!doc.organization_lead", - "fetch_if_empty": 0, "fieldname": "fax", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Fax", - "length": 0, - "no_copy": 0, "oldfieldname": "fax", - "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "oldfieldtype": "Data" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "website", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Website", - "length": 0, - "no_copy": 0, "oldfieldname": "website", - "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "oldfieldtype": "Data" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "", - "fetch_if_empty": 0, "fieldname": "territory", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Territory", - "length": 0, - "no_copy": 0, "oldfieldname": "territory", "oldfieldtype": "Link", "options": "Territory", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "print_hide": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, "collapsible": 1, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "more_info", "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "More Information", - "length": 0, - "no_copy": 0, "oldfieldtype": "Section Break", - "options": "fa fa-file-text", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "fa fa-file-text" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "type", "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Lead Type", - "length": 0, - "no_copy": 0, "oldfieldname": "type", "oldfieldtype": "Select", - "options": "\nClient\nChannel Partner\nConsultant", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "\nClient\nChannel Partner\nConsultant" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "market_segment", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Market Segment", - "length": 0, - "no_copy": 0, "oldfieldname": "market_segment", "oldfieldtype": "Select", - "options": "Market Segment", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Market Segment" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "industry", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Industry", - "length": 0, - "no_copy": 0, "oldfieldname": "industry", "oldfieldtype": "Link", - "options": "Industry Type", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Industry Type" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "request_type", "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Request Type", - "length": 0, - "no_copy": 0, "oldfieldname": "request_type", "oldfieldtype": "Select", - "options": "\nProduct Enquiry\nRequest for Information\nSuggestions\nOther", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "\nProduct Enquiry\nRequest for Information\nSuggestions\nOther" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "column_break3", "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, "oldfieldtype": "Column Break", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "50%" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "company", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Company", - "length": 0, - "no_copy": 0, "oldfieldname": "company", "oldfieldtype": "Link", "options": "Company", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "remember_last_selected_value": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, + "default": "0", "fieldname": "unsubscribed", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Unsubscribed", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Unsubscribed" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, + "default": "0", "fieldname": "blog_subscriber", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Blog Subscriber", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Blog Subscriber" } ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, "icon": "fa fa-user", "idx": 5, "image_field": "image", - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2019-06-18 03:22:57.283628", + "modified": "2019-09-19 12:49:02.536647", "modified_by": "Administrator", "module": "CRM", "name": "Lead", @@ -1438,128 +374,69 @@ "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, "permlevel": 1, - "print": 0, "read": 1, "report": 1, - "role": "All", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 + "role": "All" }, { - "amend": 0, - "cancel": 0, "create": 1, - "delete": 0, "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "Sales User", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 }, { - "amend": 0, - "cancel": 0, "create": 1, "delete": 1, "email": 1, "export": 1, - "if_owner": 0, "import": 1, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "Sales Manager", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 }, { - "amend": 0, - "cancel": 0, "create": 1, - "delete": 0, "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "System Manager", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, "permlevel": 1, - "print": 0, "read": 1, "report": 1, - "role": "Sales Manager", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 + "role": "Sales Manager" }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, "permlevel": 1, - "print": 0, "read": 1, "report": 1, - "role": "Sales User", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 + "role": "Sales User" + }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Guest", + "share": 1 } ], - "quick_entry": 0, - "read_only": 0, "search_fields": "lead_name,lead_owner,status", "show_name_in_global_search": 1, "sort_field": "modified", "sort_order": "DESC", - "title_field": "lead_name", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 + "title_field": "lead_name" } \ No newline at end of file diff --git a/erpnext/crm/doctype/opportunity/opportunity.py b/erpnext/crm/doctype/opportunity/opportunity.py index 99486fa2066..4e0eebd0de9 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.py +++ b/erpnext/crm/doctype/opportunity/opportunity.py @@ -130,10 +130,11 @@ class Opportunity(TransactionBase): def has_lost_quotation(self): lost_quotation = frappe.db.sql(""" - select q.name - from `tabQuotation` q, `tabQuotation Item` qi - where q.name = qi.parent and q.docstatus=1 - and qi.prevdoc_docname =%s and q.status = 'Lost' + select name + from `tabQuotation` + where docstatus=1 + and opportunity =%s + and status = 'Lost' """, self.name) if lost_quotation: if self.has_active_quotation(): diff --git a/erpnext/education/doctype/instructor/instructor.js b/erpnext/education/doctype/instructor/instructor.js index f9c7a2a13da..71e044bb70a 100644 --- a/erpnext/education/doctype/instructor/instructor.js +++ b/erpnext/education/doctype/instructor/instructor.js @@ -4,11 +4,11 @@ cur_frm.add_fetch("employee", "image", "image"); frappe.ui.form.on("Instructor", { employee: function(frm) { if(!frm.doc.employee) return; - frappe.db.get_value('Employee', {name: frm.doc.employee}, 'company', (company) => { + frappe.db.get_value('Employee', {name: frm.doc.employee}, 'company', (d) => { frm.set_query("department", function() { return { "filters": { - "company": company, + "company": d.company, } }; }); @@ -16,7 +16,7 @@ frappe.ui.form.on("Instructor", { frm.set_query("department", "instructor_log", function() { return { "filters": { - "company": company, + "company": d.company, } }; }); diff --git a/erpnext/education/doctype/student/student.py b/erpnext/education/doctype/student/student.py index 9af5e22913a..76825cec1b2 100644 --- a/erpnext/education/doctype/student/student.py +++ b/erpnext/education/doctype/student/student.py @@ -40,7 +40,7 @@ class Student(Document): frappe.throw(_("Student {0} exist against student applicant {1}").format(student[0][0], self.student_applicant)) def after_insert(self): - if not frappe.get_single('Education Settings').user_creation_skip: + if not frappe.get_single('Education Settings').get('user_creation_skip'): self.create_student_user() def create_student_user(self): diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 9e74bfd2906..7f268d6ba14 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -182,6 +182,7 @@ standard_portal_menu_items = [ {"title": _("Admission"), "route": "/admissions", "reference_doctype": "Student Admission", "role": "Student"}, {"title": _("Certification"), "route": "/certification", "reference_doctype": "Certification Application", "role": "Non Profit Portal User"}, {"title": _("Material Request"), "route": "/material-requests", "reference_doctype": "Material Request", "role": "Customer"}, + {"title": _("Appointment Booking"), "route": "/book_appointment"}, ] default_roles = [ @@ -248,10 +249,10 @@ doc_events = { "on_trash": "erpnext.regional.check_deletion_permission" }, 'Address': { - 'validate': ['erpnext.regional.india.utils.validate_gstin_for_india', 'erpnext.regional.italy.utils.set_state_code'] + 'validate': ['erpnext.regional.india.utils.validate_gstin_for_india', 'erpnext.regional.italy.utils.set_state_code', 'erpnext.regional.india.utils.update_gst_category'] }, - ('Sales Invoice', 'Purchase Invoice', 'Delivery Note'): { - 'validate': 'erpnext.regional.india.utils.set_place_of_supply' + ('Sales Invoice', 'Sales Order', 'Delivery Note', 'Purchase Invoice', 'Purchase Order', 'Purchase Receipt'): { + 'validate': ['erpnext.regional.india.utils.set_place_of_supply'] }, "Contact": { "on_trash": "erpnext.support.doctype.issue.issue.update_issue", @@ -301,7 +302,8 @@ scheduler_events = { "erpnext.quality_management.doctype.quality_review.quality_review.review", "erpnext.support.doctype.service_level_agreement.service_level_agreement.check_agreement_status", "erpnext.crm.doctype.email_campaign.email_campaign.send_email_to_leads_or_contacts", - "erpnext.crm.doctype.email_campaign.email_campaign.set_email_campaign_status" + "erpnext.crm.doctype.email_campaign.email_campaign.set_email_campaign_status", + "erpnext.selling.doctype.quotation.quotation.set_expired_status" ], "daily_long": [ "erpnext.setup.doctype.email_digest.email_digest.send", diff --git a/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.js b/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.js index 1baa1e04e32..57838423d71 100644 --- a/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.js +++ b/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.js @@ -19,4 +19,4 @@ frappe.ui.form.on('Compensatory Leave Request', { frm.set_df_property('half_day_date', 'reqd', false); } } -}); +}); \ No newline at end of file diff --git a/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py b/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py index bc4a1b4034b..7a9727f18c4 100644 --- a/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py +++ b/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py @@ -5,9 +5,10 @@ from __future__ import unicode_literals import frappe from frappe import _ -from frappe.utils import date_diff, add_days, getdate +from frappe.utils import date_diff, add_days, getdate, cint from frappe.model.document import Document -from erpnext.hr.utils import validate_dates, validate_overlap, get_leave_period, get_holidays_for_employee +from erpnext.hr.utils import validate_dates, validate_overlap, get_leave_period, \ + get_holidays_for_employee, create_additional_leave_ledger_entry class CompensatoryLeaveRequest(Document): @@ -25,16 +26,14 @@ class CompensatoryLeaveRequest(Document): frappe.throw(_("Leave Type is madatory")) def validate_attendance(self): - query = """select attendance_date, status - from `tabAttendance` where - attendance_date between %(work_from_date)s and %(work_end_date)s - and docstatus=1 and status = 'Present' and employee=%(employee)s""" + attendance = frappe.get_all('Attendance', + filters={ + 'attendance_date': ['between', (self.work_from_date, self.work_end_date)], + 'status': 'Present', + 'docstatus': 1, + 'employee': self.employee + }, fields=['attendance_date', 'status']) - attendance = frappe.db.sql(query, { - "work_from_date": self.work_from_date, - "work_end_date": self.work_end_date, - "employee": self.employee - }, as_dict=True) if len(attendance) < date_diff(self.work_end_date, self.work_from_date) + 1: frappe.throw(_("You are not present all day(s) between compensatory leave request days")) @@ -50,13 +49,19 @@ class CompensatoryLeaveRequest(Document): date_difference -= 0.5 leave_period = get_leave_period(self.work_from_date, self.work_end_date, company) if leave_period: - leave_allocation = self.exists_allocation_for_period(leave_period) + leave_allocation = self.get_existing_allocation_for_period(leave_period) if leave_allocation: leave_allocation.new_leaves_allocated += date_difference - leave_allocation.submit() + leave_allocation.validate() + leave_allocation.db_set("new_leaves_allocated", leave_allocation.total_leaves_allocated) + leave_allocation.db_set("total_leaves_allocated", leave_allocation.total_leaves_allocated) + + # generate additional ledger entry for the new compensatory leaves off + create_additional_leave_ledger_entry(leave_allocation, date_difference, add_days(self.work_end_date, 1)) + else: leave_allocation = self.create_leave_allocation(leave_period, date_difference) - self.db_set("leave_allocation", leave_allocation.name) + self.leave_allocation=leave_allocation.name else: frappe.throw(_("There is no leave period in between {0} and {1}").format(self.work_from_date, self.work_end_date)) @@ -68,11 +73,16 @@ class CompensatoryLeaveRequest(Document): leave_allocation = frappe.get_doc("Leave Allocation", self.leave_allocation) if leave_allocation: leave_allocation.new_leaves_allocated -= date_difference - if leave_allocation.total_leaves_allocated - date_difference <= 0: - leave_allocation.total_leaves_allocated = 0 - leave_allocation.submit() + if leave_allocation.new_leaves_allocated - date_difference <= 0: + leave_allocation.new_leaves_allocated = 0 + leave_allocation.validate() + leave_allocation.db_set("new_leaves_allocated", leave_allocation.total_leaves_allocated) + leave_allocation.db_set("total_leaves_allocated", leave_allocation.total_leaves_allocated) - def exists_allocation_for_period(self, leave_period): + # create reverse entry on cancelation + create_additional_leave_ledger_entry(leave_allocation, date_difference * -1, add_days(self.work_end_date, 1)) + + def get_existing_allocation_for_period(self, leave_period): leave_allocation = frappe.db.sql(""" select name from `tabLeave Allocation` @@ -95,17 +105,18 @@ class CompensatoryLeaveRequest(Document): def create_leave_allocation(self, leave_period, date_difference): is_carry_forward = frappe.db.get_value("Leave Type", self.leave_type, "is_carry_forward") - allocation = frappe.new_doc("Leave Allocation") - allocation.employee = self.employee - allocation.employee_name = self.employee_name - allocation.leave_type = self.leave_type - allocation.from_date = add_days(self.work_end_date, 1) - allocation.to_date = leave_period[0].to_date - allocation.new_leaves_allocated = date_difference - allocation.total_leaves_allocated = date_difference - allocation.description = self.reason - if is_carry_forward == 1: - allocation.carry_forward = True - allocation.save(ignore_permissions = True) + allocation = frappe.get_doc(dict( + doctype="Leave Allocation", + employee=self.employee, + employee_name=self.employee_name, + leave_type=self.leave_type, + from_date=add_days(self.work_end_date, 1), + to_date=leave_period[0].to_date, + carry_forward=cint(is_carry_forward), + new_leaves_allocated=date_difference, + total_leaves_allocated=date_difference, + description=self.reason + )) + allocation.insert(ignore_permissions=True) allocation.submit() - return allocation + return allocation \ No newline at end of file diff --git a/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.py b/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.py index f2ca1f4f5f0..1615ab30f1d 100644 --- a/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.py +++ b/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.py @@ -5,37 +5,128 @@ from __future__ import unicode_literals import frappe import unittest +from frappe.utils import today, add_months, add_days +from erpnext.hr.doctype.attendance_request.test_attendance_request import get_employee +from erpnext.hr.doctype.leave_period.test_leave_period import create_leave_period +from erpnext.hr.doctype.leave_application.leave_application import get_leave_balance_on -# class TestCompensatoryLeaveRequest(unittest.TestCase): -# def get_compensatory_leave_request(self): -# return frappe.get_doc('Compensatory Leave Request', dict( -# employee = employee, -# work_from_date = today, -# work_to_date = today, -# reason = 'test' -# )).insert() -# -# def test_creation_of_leave_allocation(self): -# employee = get_employee() -# today = get_today() -# -# compensatory_leave_request = self.get_compensatory_leave_request(today) -# -# before = get_leave_balance(employee, compensatory_leave_request.leave_type) -# -# compensatory_leave_request.submit() -# -# self.assertEqual(get_leave_balance(employee, compensatory_leave_request.leave_type), before + 1) -# -# def test_max_compensatory_leave(self): -# employee = get_employee() -# today = get_today() -# -# compensatory_leave_request = self.get_compensatory_leave_request() -# -# frappe.db.set_value('Leave Type', compensatory_leave_request.leave_type, 'max_leaves_allowed', 0) -# -# self.assertRaises(MaxLeavesLimitCrossed, compensatory_leave_request.submit) -# -# frappe.db.set_value('Leave Type', compensatory_leave_request.leave_type, 'max_leaves_allowed', 10) -# +class TestCompensatoryLeaveRequest(unittest.TestCase): + def setUp(self): + frappe.db.sql(''' delete from `tabCompensatory Leave Request`''') + frappe.db.sql(''' delete from `tabLeave Ledger Entry`''') + frappe.db.sql(''' delete from `tabLeave Allocation`''') + frappe.db.sql(''' delete from `tabAttendance` where attendance_date in {0} '''.format((today(), add_days(today(), -1)))) #nosec + create_leave_period(add_months(today(), -3), add_months(today(), 3), "_Test Company") + create_holiday_list() + + employee = get_employee() + employee.holiday_list = "_Test Compensatory Leave" + employee.save() + + def test_leave_balance_on_submit(self): + ''' check creation of leave allocation on submission of compensatory leave request ''' + employee = get_employee() + mark_attendance(employee) + compensatory_leave_request = get_compensatory_leave_request(employee.name) + + before = get_leave_balance_on(employee.name, compensatory_leave_request.leave_type, today()) + compensatory_leave_request.submit() + + self.assertEqual(get_leave_balance_on(employee.name, compensatory_leave_request.leave_type, add_days(today(), 1)), before + 1) + + def test_leave_allocation_update_on_submit(self): + employee = get_employee() + mark_attendance(employee, date=add_days(today(), -1)) + compensatory_leave_request = get_compensatory_leave_request(employee.name, leave_date=add_days(today(), -1)) + compensatory_leave_request.submit() + + # leave allocation creation on submit + leaves_allocated = frappe.db.get_value('Leave Allocation', { + 'name': compensatory_leave_request.leave_allocation + }, ['total_leaves_allocated']) + self.assertEqual(leaves_allocated, 1) + + mark_attendance(employee) + compensatory_leave_request = get_compensatory_leave_request(employee.name) + compensatory_leave_request.submit() + + # leave allocation updates on submission of second compensatory leave request + leaves_allocated = frappe.db.get_value('Leave Allocation', { + 'name': compensatory_leave_request.leave_allocation + }, ['total_leaves_allocated']) + self.assertEqual(leaves_allocated, 2) + + def test_creation_of_leave_ledger_entry_on_submit(self): + ''' check creation of leave ledger entry on submission of leave request ''' + employee = get_employee() + mark_attendance(employee) + compensatory_leave_request = get_compensatory_leave_request(employee.name) + compensatory_leave_request.submit() + + filters = dict(transaction_name=compensatory_leave_request.leave_allocation) + leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=filters) + + self.assertEquals(len(leave_ledger_entry), 1) + self.assertEquals(leave_ledger_entry[0].employee, compensatory_leave_request.employee) + self.assertEquals(leave_ledger_entry[0].leave_type, compensatory_leave_request.leave_type) + self.assertEquals(leave_ledger_entry[0].leaves, 1) + + # check reverse leave ledger entry on cancellation + compensatory_leave_request.cancel() + leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=filters, order_by = 'creation desc') + + self.assertEquals(len(leave_ledger_entry), 2) + self.assertEquals(leave_ledger_entry[0].employee, compensatory_leave_request.employee) + self.assertEquals(leave_ledger_entry[0].leave_type, compensatory_leave_request.leave_type) + self.assertEquals(leave_ledger_entry[0].leaves, -1) + +def get_compensatory_leave_request(employee, leave_date=today()): + prev_comp_leave_req = frappe.db.get_value('Compensatory Leave Request', + dict(leave_type='Compensatory Off', + work_from_date=leave_date, + work_end_date=leave_date, + employee=employee), 'name') + if prev_comp_leave_req: + return frappe.get_doc('Compensatory Leave Request', prev_comp_leave_req) + + return frappe.get_doc(dict( + doctype='Compensatory Leave Request', + employee=employee, + leave_type='Compensatory Off', + work_from_date=leave_date, + work_end_date=leave_date, + reason='test' + )).insert() + +def mark_attendance(employee, date=today(), status='Present'): + if not frappe.db.exists(dict(doctype='Attendance', employee=employee.name, attendance_date=date, status='Present')): + attendance = frappe.get_doc({ + "doctype": "Attendance", + "employee": employee.name, + "attendance_date": date, + "status": status + }) + attendance.save() + attendance.submit() + +def create_holiday_list(): + if frappe.db.exists("Holiday List", "_Test Compensatory Leave"): + return + + holiday_list = frappe.get_doc({ + "doctype": "Holiday List", + "from_date": add_months(today(), -3), + "to_date": add_months(today(), 3), + "holidays": [ + { + "description": "Test Holiday", + "holiday_date": today() + }, + { + "description": "Test Holiday 1", + "holiday_date": add_days(today(), -1) + } + ], + "holiday_list_name": "_Test Compensatory Leave" + }) + holiday_list.save() \ No newline at end of file diff --git a/erpnext/hr/doctype/employee/employee.py b/erpnext/hr/doctype/employee/employee.py index 703ec06f83b..2f88e1e3631 100755 --- a/erpnext/hr/doctype/employee/employee.py +++ b/erpnext/hr/doctype/employee/employee.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import frappe -from frappe.utils import getdate, validate_email_address, today, add_years, format_datetime +from frappe.utils import getdate, validate_email_address, today, add_years, format_datetime, cstr from frappe.model.naming import set_name_by_naming_series from frappe import throw, _, scrub from frappe.permissions import add_user_permission, remove_user_permission, \ @@ -218,8 +218,8 @@ class Employee(NestedSet): def reset_employee_emails_cache(self): prev_doc = self.get_doc_before_save() or {} - cell_number = self.get('cell_number') - prev_number = prev_doc.get('cell_number') + cell_number = cstr(self.get('cell_number')) + prev_number = cstr(prev_doc.get('cell_number')) if (cell_number != prev_number or self.get('user_id') != prev_doc.get('user_id')): frappe.cache().hdel('employees_with_number', cell_number) diff --git a/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.py b/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.py index 32fcee1abe4..16c1a32b9b5 100644 --- a/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.py +++ b/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.py @@ -6,6 +6,7 @@ from __future__ import unicode_literals import frappe import json from frappe.model.document import Document +from frappe.utils import getdate class EmployeeAttendanceTool(Document): @@ -43,17 +44,26 @@ def get_employees(date, department = None, branch = None, company = None): @frappe.whitelist() def mark_employee_attendance(employee_list, status, date, leave_type=None, company=None): + employee_list = json.loads(employee_list) for employee in employee_list: - attendance = frappe.new_doc("Attendance") - attendance.employee = employee['employee'] - attendance.employee_name = employee['employee_name'] - attendance.attendance_date = date - attendance.status = status + if status == "On Leave" and leave_type: - attendance.leave_type = leave_type - if company: - attendance.company = company + leave_type = leave_type else: - attendance.company = frappe.db.get_value("Employee", employee['employee'], "Company") + leave_type = None + + if not company: + company = frappe.db.get_value("Employee", employee['employee'], "Company") + + attendance=frappe.get_doc(dict( + doctype='Attendance', + employee=employee.get('employee'), + employee_name=employee.get('employee_name'), + attendance_date=getdate(date), + status=status, + leave_type=leave_type, + company=company + )) + attendance.insert() attendance.submit() diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.js b/erpnext/hr/doctype/expense_claim/expense_claim.js index 6d3a28e5e24..0d37c10e9cc 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.js +++ b/erpnext/hr/doctype/expense_claim/expense_claim.js @@ -208,6 +208,24 @@ frappe.ui.form.on("Expense Claim", { frm.refresh_fields(); }, + grand_total: function(frm) { + frm.trigger("update_employee_advance_claimed_amount"); + }, + + update_employee_advance_claimed_amount: function(frm) { + let amount_to_be_allocated = frm.doc.grand_total; + $.each(frm.doc.advances || [], function(i, advance){ + if (amount_to_be_allocated >= advance.unclaimed_amount){ + frm.doc.advances[i].allocated_amount = frm.doc.advances[i].unclaimed_amount; + amount_to_be_allocated -= advance.allocated_amount; + } else{ + frm.doc.advances[i].allocated_amount = amount_to_be_allocated; + amount_to_be_allocated = 0; + } + frm.refresh_field("advances"); + }); + }, + make_payment_entry: function(frm) { var method = "erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry"; if(frm.doc.__onload && frm.doc.__onload.make_payment_via_journal_entry) { @@ -300,7 +318,7 @@ frappe.ui.form.on("Expense Claim", { row.advance_account = d.advance_account; row.advance_paid = d.paid_amount; row.unclaimed_amount = flt(d.paid_amount) - flt(d.claimed_amount); - row.allocated_amount = flt(d.paid_amount) - flt(d.claimed_amount); + row.allocated_amount = 0; }); refresh_field("advances"); } diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.py b/erpnext/hr/doctype/expense_claim/expense_claim.py index 59391505fa0..ab9a2b22102 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.py +++ b/erpnext/hr/doctype/expense_claim/expense_claim.py @@ -43,9 +43,9 @@ class ExpenseClaim(AccountsController): }[cstr(self.docstatus or 0)] paid_amount = flt(self.total_amount_reimbursed) + flt(self.total_advance_amount) - precision = self.precision("total_sanctioned_amount") + precision = self.precision("grand_total") if (self.is_paid or (flt(self.total_sanctioned_amount) > 0 - and flt(self.total_sanctioned_amount, precision) == flt(paid_amount, precision))) \ + and flt(self.grand_total, precision) == flt(paid_amount, precision))) \ and self.docstatus == 1 and self.approval_status == 'Approved': self.status = "Paid" elif flt(self.total_sanctioned_amount) > 0 and self.docstatus == 1 and self.approval_status == 'Approved': diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.py b/erpnext/hr/doctype/leave_allocation/leave_allocation.py index 874ae7a1bc2..d13bb4577cd 100755 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.py +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.py @@ -69,10 +69,14 @@ class LeaveAllocation(Document): def validate_allocation_overlap(self): leave_allocation = frappe.db.sql(""" - select name from `tabLeave Allocation` - where employee=%s and leave_type=%s and docstatus=1 - and to_date >= %s and from_date <= %s""", - (self.employee, self.leave_type, self.from_date, self.to_date)) + SELECT + name + FROM `tabLeave Allocation` + WHERE + employee=%s AND leave_type=%s + AND name <> %s AND docstatus=1 + AND to_date >= %s AND from_date <= %s""", + (self.employee, self.leave_type, self.name, self.from_date, self.to_date)) if leave_allocation: frappe.msgprint(_("{0} already allocated for Employee {1} for period {2} to {3}") diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py index 0e6630541c4..65fcbf7a999 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.py +++ b/erpnext/hr/doctype/leave_application/leave_application.py @@ -549,10 +549,10 @@ def get_leaves_for_period(employee, leave_type, from_date, to_date): leave_days += leave_entry.leaves elif inclusive_period and leave_entry.transaction_type == 'Leave Allocation' \ - and not skip_expiry_leaves(leave_entry, to_date): + and leave_entry.is_expired and not skip_expiry_leaves(leave_entry, to_date): leave_days += leave_entry.leaves - else: + elif leave_entry.transaction_type == 'Leave Application': if leave_entry.from_date < getdate(from_date): leave_entry.from_date = from_date if leave_entry.to_date > getdate(to_date): @@ -579,14 +579,15 @@ def skip_expiry_leaves(leave_entry, date): def get_leave_entries(employee, leave_type, from_date, to_date): ''' Returns leave entries between from_date and to_date ''' return frappe.db.sql(""" - select employee, leave_type, from_date, to_date, leaves, transaction_type, is_carry_forward, transaction_name - from `tabLeave Ledger Entry` - where employee=%(employee)s and leave_type=%(leave_type)s - and docstatus=1 - and leaves<0 - and (from_date between %(from_date)s and %(to_date)s - or to_date between %(from_date)s and %(to_date)s - or (from_date < %(from_date)s and to_date > %(to_date)s)) + SELECT + employee, leave_type, from_date, to_date, leaves, transaction_name, transaction_type, + is_carry_forward, is_expired + FROM `tabLeave Ledger Entry` + WHERE employee=%(employee)s AND leave_type=%(leave_type)s + AND docstatus=1 AND leaves<0 + AND (from_date between %(from_date)s AND %(to_date)s + OR to_date between %(from_date)s AND %(to_date)s + OR (from_date < %(from_date)s AND to_date > %(to_date)s)) """, { "from_date": from_date, "to_date": to_date, @@ -773,4 +774,4 @@ def get_leave_approver(employee): leave_approver = frappe.db.get_value('Department Approver', {'parent': department, 'parentfield': 'leave_approvers', 'idx': 1}, 'approver') - return leave_approver + return leave_approver \ No newline at end of file diff --git a/erpnext/hr/doctype/leave_period/test_leave_period.py b/erpnext/hr/doctype/leave_period/test_leave_period.py index 850a08dd536..1762cf917a2 100644 --- a/erpnext/hr/doctype/leave_period/test_leave_period.py +++ b/erpnext/hr/doctype/leave_period/test_leave_period.py @@ -43,10 +43,18 @@ class TestLeavePeriod(unittest.TestCase): leave_period.grant_leave_allocation(employee=employee_doc_name) self.assertEqual(get_leave_balance_on(employee_doc_name, leave_type, today()), 20) -def create_leave_period(from_date, to_date): +def create_leave_period(from_date, to_date, company=None): + leave_period = frappe.db.get_value('Leave Period', + dict(company=company or erpnext.get_default_company(), + from_date=from_date, + to_date=to_date, + is_active=1), 'name') + if leave_period: + return frappe.get_doc("Leave Period", leave_period) + leave_period = frappe.get_doc({ "doctype": "Leave Period", - "company": erpnext.get_default_company(), + "company": company or erpnext.get_default_company(), "from_date": from_date, "to_date": to_date, "is_active": 1 diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py index 1464a779368..9b0ca4e9a1a 100644 --- a/erpnext/hr/utils.py +++ b/erpnext/hr/utils.py @@ -321,11 +321,11 @@ def allocate_earned_leaves(): if new_allocation == allocation.total_leaves_allocated: continue allocation.db_set("total_leaves_allocated", new_allocation, update_modified=False) - create_earned_leave_ledger_entry(allocation, earned_leaves, today) + create_additional_leave_ledger_entry(allocation, earned_leaves, today) -def create_earned_leave_ledger_entry(allocation, earned_leaves, date): - ''' Create leave ledger entry based on the earned leave frequency ''' - allocation.new_leaves_allocated = earned_leaves +def create_additional_leave_ledger_entry(allocation, leaves, date): + ''' Create leave ledger entry for leave types ''' + allocation.new_leaves_allocated = leaves allocation.from_date = date allocation.unused_leaves = 0 allocation.create_leave_ledger_entry() @@ -389,6 +389,7 @@ def get_sal_slip_total_benefit_given(employee, payroll_period, component=False): def get_holidays_for_employee(employee, start_date, end_date): holiday_list = get_holiday_list_for_employee(employee) + holidays = frappe.db.sql_list('''select holiday_date from `tabHoliday` where parent=%(holiday_list)s @@ -437,4 +438,4 @@ def get_previous_claimed_amount(employee, payroll_period, non_pro_rata=False, co }, as_dict=True) if sum_of_claimed_amount and flt(sum_of_claimed_amount[0].total_amount) > 0: total_claimed_amount = sum_of_claimed_amount[0].total_amount - return total_claimed_amount + return total_claimed_amount \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/blanket_order/blanket_order.json b/erpnext/manufacturing/doctype/blanket_order/blanket_order.json index 260e0b8a736..0330e5c85c9 100644 --- a/erpnext/manufacturing/doctype/blanket_order/blanket_order.json +++ b/erpnext/manufacturing/doctype/blanket_order/blanket_order.json @@ -89,7 +89,8 @@ "fieldtype": "Link", "label": "Company", "options": "Company", - "reqd": 1 + "reqd": 1, + "search_index": 1 }, { "fieldname": "section_break_12", @@ -129,7 +130,7 @@ } ], "is_submittable": 1, - "modified": "2019-10-16 13:38:32.302316", + "modified": "2019-11-18 19:37:37.151686", "modified_by": "Administrator", "module": "Manufacturing", "name": "Blanket Order", diff --git a/erpnext/manufacturing/doctype/blanket_order_item/blanket_order_item.json b/erpnext/manufacturing/doctype/blanket_order_item/blanket_order_item.json index 099eed4aece..977ad547f55 100644 --- a/erpnext/manufacturing/doctype/blanket_order_item/blanket_order_item.json +++ b/erpnext/manufacturing/doctype/blanket_order_item/blanket_order_item.json @@ -1,298 +1,78 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2018-05-24 07:20:04.255236", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "creation": "2018-05-24 07:20:04.255236", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "item_code", + "item_name", + "column_break_3", + "qty", + "rate", + "ordered_qty", + "section_break_7", + "terms_and_conditions" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "item_code", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Item Code", - "length": 0, - "no_copy": 0, - "options": "Item", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "item_code", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Item Code", + "options": "Item", + "reqd": 1, + "search_index": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "item_code.item_name", - "fieldname": "item_name", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Item Name", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fetch_from": "item_code.item_name", + "fieldname": "item_name", + "fieldtype": "Data", + "label": "Item Name" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_3", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "qty", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Quantity", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "qty", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Quantity" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "rate", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Rate", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "rate", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Rate", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "ordered_qty", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Ordered Quantity", - "length": 0, - "no_copy": 1, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "ordered_qty", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Ordered Quantity", + "no_copy": 1, + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_7", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "section_break_7", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "terms_and_conditions", - "fieldtype": "Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Terms and Conditions", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldname": "terms_and_conditions", + "fieldtype": "Text", + "label": "Terms and Conditions" } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2018-06-14 07:04:14.050836", - "modified_by": "Administrator", - "module": "Manufacturing", - "name": "Blanket Order Item", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0 + ], + "istable": 1, + "modified": "2019-11-18 19:37:46.245878", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "Blanket Order Item", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js index b9591d6054b..8283fd7e6fc 100644 --- a/erpnext/manufacturing/doctype/bom/bom.js +++ b/erpnext/manufacturing/doctype/bom/bom.js @@ -5,6 +5,12 @@ frappe.provide("erpnext.bom"); frappe.ui.form.on("BOM", { setup: function(frm) { + frm.custom_make_buttons = { + 'BOM': 'Duplicate BOM', + 'Work Order': 'Work Order', + 'Quality Inspection': 'Quality Inspection' + }; + frm.set_query("bom_no", "items", function() { return { filters: { @@ -85,9 +91,21 @@ frappe.ui.form.on("BOM", { } if(frm.doc.docstatus!=0) { - frm.add_custom_button(__("Duplicate"), function() { + frm.add_custom_button(__("Duplicate BOM"), function() { frm.copy_doc(); - }); + }, __("Create")); + + frm.add_custom_button(__("Work Order"), function() { + frm.trigger("make_work_order"); + }, __("Create")); + + if (frm.doc.inspection_required) { + frm.add_custom_button(__("Quality Inspection"), function() { + frm.trigger("make_quality_inspection"); + }, __("Create")); + } + + frm.page.set_inner_btn_group_as_primary(__('Create')); } if(frm.doc.items && frm.doc.allow_alternative_item) { @@ -109,6 +127,41 @@ frappe.ui.form.on("BOM", { } }, + make_work_order: function(frm) { + const fields = [{ + fieldtype: 'Float', + label: __('Qty To Manufacture'), + fieldname: 'qty', + reqd: 1, + default: 1 + }]; + + frappe.prompt(fields, data => { + frappe.call({ + method: "erpnext.manufacturing.doctype.work_order.work_order.make_work_order", + args: { + item: frm.doc.item, + qty: data.qty || 0.0, + project: frm.doc.project + }, + freeze: true, + callback: function(r) { + if(r.message) { + var doc = frappe.model.sync(r.message)[0]; + frappe.set_route("Form", doc.doctype, doc.name); + } + } + }); + }, __("Enter Value"), __("Create")); + }, + + make_quality_inspection: function(frm) { + frappe.model.open_mapped_doc({ + method: "erpnext.stock.doctype.quality_inspection.quality_inspection.make_quality_inspection", + frm: frm + }) + }, + update_cost: function(frm) { return frappe.call({ doc: frm.doc, diff --git a/erpnext/manufacturing/doctype/bom/bom.json b/erpnext/manufacturing/doctype/bom/bom.json index a0faeb5fb55..63f4f977c59 100644 --- a/erpnext/manufacturing/doctype/bom/bom.json +++ b/erpnext/manufacturing/doctype/bom/bom.json @@ -3,33 +3,36 @@ "creation": "2013-01-22 15:11:38", "doctype": "DocType", "document_type": "Setup", + "engine": "InnoDB", "field_order": [ "item", - "item_name", - "image", - "uom", "quantity", + "set_rate_of_sub_assembly_item_based_on_bom", "cb0", "is_active", "is_default", - "with_operations", - "inspection_required", "allow_alternative_item", - "allow_same_item_multiple_times", - "set_rate_of_sub_assembly_item_based_on_bom", - "quality_inspection_template", + "image", + "item_name", + "uom", "currency_detail", "company", - "transfer_material_against", + "project", "conversion_rate", "column_break_12", "currency", "rm_cost_as_per", "buying_price_list", - "operations_section", + "section_break_21", + "with_operations", + "column_break_23", + "transfer_material_against", "routing", + "operations_section", "operations", "materials_section", + "inspection_required", + "quality_inspection_template", "items", "scrap_section", "scrap_items", @@ -41,14 +44,9 @@ "base_operating_cost", "base_raw_material_cost", "base_scrap_material_cost", - "total_cost_of_bom", - "total_cost", "column_break_26", + "total_cost", "base_total_cost", - "more_info_section", - "project", - "amended_from", - "col_break23", "section_break_25", "description", "column_break_27", @@ -57,12 +55,14 @@ "website_section", "show_in_website", "route", + "column_break_52", "website_image", "thumbnail", "sb_web_spec", - "web_long_description", "show_items", - "show_operations" + "show_operations", + "web_long_description", + "amended_from" ], "fields": [ { @@ -152,7 +152,7 @@ "default": "0", "fieldname": "inspection_required", "fieldtype": "Check", - "label": "Inspection Required" + "label": "Quality Inspection Required" }, { "default": "0", @@ -160,12 +160,6 @@ "fieldtype": "Check", "label": "Allow Alternative Item" }, - { - "default": "0", - "fieldname": "allow_same_item_multiple_times", - "fieldtype": "Check", - "label": "Allow Same Item Multiple Times" - }, { "allow_on_submit": 1, "default": "1", @@ -193,6 +187,7 @@ "reqd": 1 }, { + "default": "Work Order", "fieldname": "transfer_material_against", "fieldtype": "Select", "label": "Transfer Material Against", @@ -235,10 +230,10 @@ { "fieldname": "operations_section", "fieldtype": "Section Break", - "label": "Operations", "oldfieldtype": "Section Break" }, { + "depends_on": "with_operations", "fieldname": "routing", "fieldtype": "Link", "label": "Routing", @@ -335,10 +330,6 @@ "options": "Company:company:default_currency", "read_only": 1 }, - { - "fieldname": "total_cost_of_bom", - "fieldtype": "Section Break" - }, { "fieldname": "total_cost", "fieldtype": "Currency", @@ -359,10 +350,6 @@ "print_hide": 1, "read_only": 1 }, - { - "fieldname": "more_info_section", - "fieldtype": "Section Break" - }, { "fieldname": "project", "fieldtype": "Link", @@ -381,10 +368,6 @@ "print_hide": 1, "read_only": 1 }, - { - "fieldname": "col_break23", - "fieldtype": "Column Break" - }, { "fieldname": "section_break_25", "fieldtype": "Section Break" @@ -481,13 +464,26 @@ "fieldname": "show_operations", "fieldtype": "Check", "label": "Show Operations" + }, + { + "fieldname": "section_break_21", + "fieldtype": "Section Break", + "label": "Operations" + }, + { + "fieldname": "column_break_23", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_52", + "fieldtype": "Column Break" } ], "icon": "fa fa-sitemap", "idx": 1, "image_field": "image", "is_submittable": 1, - "modified": "2019-07-30 17:00:09.665068", + "modified": "2019-11-22 14:35:12.142150", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM", diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index db79d7feda4..723445ad528 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -65,6 +65,7 @@ class BOM(WebsiteGenerator): context.parents = [{'name': 'boms', 'title': _('All BOMs') }] def on_update(self): + frappe.cache().hdel('bom_children', self.name) self.check_recursion() self.update_stock_qty() self.update_exploded_items() @@ -96,6 +97,7 @@ class BOM(WebsiteGenerator): def get_routing(self): if self.routing: + self.set("operations", []) for d in frappe.get_all("BOM Operation", fields = ["*"], filters = {'parenttype': 'Routing', 'parent': self.routing}): child = self.append('operations', d) @@ -289,7 +291,7 @@ class BOM(WebsiteGenerator): if not valuation_rate: valuation_rate = frappe.db.get_value("Item", args['item_code'], "valuation_rate") - return valuation_rate + return flt(valuation_rate) def manage_default_bom(self): """ Uncheck others if current one is selected as default or @@ -362,15 +364,9 @@ class BOM(WebsiteGenerator): def validate_materials(self): """ Validate raw material entries """ - def get_duplicates(lst): - seen = set() - seen_add = seen.add - for item in lst: - if item.item_code in seen or seen_add(item.item_code): - yield item - if not self.get('items'): frappe.throw(_("Raw Materials cannot be blank.")) + check_list = [] for m in self.get('items'): if m.bom_no: @@ -379,16 +375,6 @@ class BOM(WebsiteGenerator): frappe.throw(_("Quantity required for Item {0} in row {1}").format(m.item_code, m.idx)) check_list.append(m) - if not self.allow_same_item_multiple_times: - duplicate_items = list(get_duplicates(check_list)) - if duplicate_items: - li = [] - for i in duplicate_items: - li.append("{0} on row {1}".format(i.item_code, i.idx)) - duplicate_list = '
' + '
'.join(li) - - frappe.throw(_("Same item has been entered multiple times. {0}").format(duplicate_list)) - def check_recursion(self, bom_list=[]): """ Check whether recursion occurs in any bom""" bom_list = self.traverse_tree() diff --git a/erpnext/manufacturing/doctype/bom/bom_dashboard.py b/erpnext/manufacturing/doctype/bom/bom_dashboard.py index 803ece7c789..060cd53ef19 100644 --- a/erpnext/manufacturing/doctype/bom/bom_dashboard.py +++ b/erpnext/manufacturing/doctype/bom/bom_dashboard.py @@ -17,11 +17,13 @@ def get_data(): }, { 'label': _('Manufacture'), - 'items': ['BOM', 'Work Order', 'Job Card', 'Production Plan'] + 'items': ['BOM', 'Work Order', 'Job Card'] }, { - 'label': _('Purchase'), + 'label': _('Subcontract'), 'items': ['Purchase Order', 'Purchase Receipt', 'Purchase Invoice'] } - ] + ], + 'disable_create_buttons': ["Item", "Purchase Order", "Purchase Receipt", + "Purchase Invoice", "Job Card", "Stock Entry"] } diff --git a/erpnext/manufacturing/doctype/bom_item/bom_item.json b/erpnext/manufacturing/doctype/bom_item/bom_item.json index febf315988c..f094be4c647 100644 --- a/erpnext/manufacturing/doctype/bom_item/bom_item.json +++ b/erpnext/manufacturing/doctype/bom_item/bom_item.json @@ -1,1053 +1,273 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, "creation": "2013-02-22 01:27:49", - "custom": 0, - "docstatus": 0, "doctype": "DocType", "document_type": "Setup", "editable_grid": 1, + "field_order": [ + "item_code", + "item_name", + "operation", + "column_break_3", + "bom_no", + "source_warehouse", + "allow_alternative_item", + "section_break_5", + "description", + "col_break1", + "image", + "image_view", + "quantity_and_rate", + "qty", + "uom", + "col_break2", + "stock_qty", + "stock_uom", + "conversion_factor", + "rate_amount_section", + "rate", + "base_rate", + "column_break_21", + "amount", + "base_amount", + "section_break_18", + "scrap", + "qty_consumed_per_unit", + "section_break_27", + "include_item_in_manufacturing", + "original_item" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, "columns": 3, "fieldname": "item_code", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, "in_filter": 1, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "Item Code", - "length": 0, - "no_copy": 0, "oldfieldname": "item_code", "oldfieldtype": "Link", "options": "Item", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, "reqd": 1, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "search_index": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, "columns": 3, "fieldname": "item_name", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Item Name", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Item Name" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "operation", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Item operation", - "length": 0, - "no_copy": 0, - "options": "Operation", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Operation" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "column_break_3", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "bom_no", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, "in_filter": 1, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "BOM No", - "length": 0, - "no_copy": 0, "oldfieldname": "bom_no", "oldfieldtype": "Link", "options": "BOM", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, "print_width": "150px", - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "150px" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "source_warehouse", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Source Warehouse", - "length": 0, - "no_copy": 0, - "options": "Warehouse", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Warehouse" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, "collapsible": 1, - "columns": 0, "fieldname": "section_break_5", "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Description", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Description" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "description", "fieldtype": "Text Editor", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Item Description", - "length": 0, - "no_copy": 0, "oldfieldname": "description", "oldfieldtype": "Text", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, "print_width": "250px", - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "250px" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "col_break1", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "image", "fieldtype": "Attach", "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Image", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "print_hide": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "image_view", "fieldtype": "Image", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Image View", - "length": 0, - "no_copy": 0, - "options": "image", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "image" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "quantity_and_rate", "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Quantity and Rate", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Quantity and Rate" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, "columns": 2, "fieldname": "qty", "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "Qty", - "length": 0, - "no_copy": 0, "oldfieldname": "qty", "oldfieldtype": "Currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, "columns": 1, "fieldname": "uom", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "UOM", - "length": 0, - "no_copy": 0, "options": "UOM", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "col_break2", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "stock_qty", "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Stock Qty", - "length": 0, - "no_copy": 0, "oldfieldname": "stock_qty", "oldfieldtype": "Currency", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "stock_uom", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Stock UOM", - "length": 0, - "no_copy": 0, "oldfieldname": "stock_uom", "oldfieldtype": "Data", "options": "UOM", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "conversion_factor", "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Conversion Factor", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Conversion Factor" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "rate_amount_section", "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Rate & Amount", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Rate & Amount" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "", "fieldname": "rate", "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "Rate", - "length": 0, - "no_copy": 0, "options": "currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "base_rate", "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Basic Rate (Company Currency)", - "length": 0, - "no_copy": 0, "options": "Company:company:default_currency", - "permlevel": 0, - "precision": "", "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "column_break_21", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "amount", "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "Amount", - "length": 0, - "no_copy": 0, "oldfieldname": "amount_as_per_mar", "oldfieldtype": "Currency", "options": "currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, "print_width": "150px", "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "150px" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "base_amount", "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Amount (Company Currency)", - "length": 0, - "no_copy": 0, "options": "Company:company:default_currency", - "permlevel": 0, - "precision": "", "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "section_break_18", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Section Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, "columns": 1, "fieldname": "scrap", "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Scrap %", - "length": 0, - "no_copy": 0, "oldfieldname": "scrap", "oldfieldtype": "Currency", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "print_hide": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "qty_consumed_per_unit", "fieldtype": "Float", "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Qty Consumed Per Unit", - "length": 0, - "no_copy": 0, "oldfieldname": "qty_consumed_per_unit", "oldfieldtype": "Float", - "permlevel": 0, "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "section_break_27", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Section Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, + "default": "0", "fieldname": "allow_alternative_item", "fieldtype": "Check", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Allow Alternative Item", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Allow Alternative Item" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, + "default": "0", "fetch_from": "item_code.include_item_in_manufacturing", "fieldname": "include_item_in_manufacturing", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Include Item In Manufacturing", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Include Item In Manufacturing" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "original_item", "fieldtype": "Link", "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Original Item", - "length": 0, - "no_copy": 0, "options": "Item", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "read_only": 1 } ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, "idx": 1, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, "istable": 1, - "max_attachments": 0, - "modified": "2019-02-21 19:19:54.872459", + "modified": "2019-11-22 11:38:52.087303", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM Item", "owner": "Administrator", "permissions": [], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 + "sort_order": "DESC" } \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py index 2ca4d16a07c..31a9fdb28ab 100644 --- a/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py +++ b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py @@ -9,6 +9,7 @@ from frappe import _ from six import string_types from erpnext.manufacturing.doctype.bom.bom import get_boms_in_bottom_up_order from frappe.model.document import Document +import click class BOMUpdateTool(Document): def replace_bom(self): @@ -17,7 +18,8 @@ class BOMUpdateTool(Document): frappe.cache().delete_key('bom_children') bom_list = self.get_parent_boms(self.new_bom) updated_bom = [] - + with click.progressbar(bom_list) as bom_list: + pass for bom in bom_list: try: bom_obj = frappe.get_cached_doc('BOM', bom) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 5d2696933bc..25c385fb1e5 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -529,7 +529,6 @@ def get_material_request_items(row, sales_order, required_qty = ceil(required_qty) if required_qty > 0: - print(row) return { 'item_code': row.item_code, 'item_name': row.item_name, diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index 107c79b89bc..1951c271548 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -581,6 +581,8 @@ erpnext.work_order = { description: __('Max: {0}', [max]), default: max }, data => { + max += (max * (frm.doc.__onload.overproduction_percentage || 0.0)) / 100; + if (data.qty > max) { frappe.msgprint(__('Quantity must not be more than {0}', [max])); reject(); diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 089cb8014d2..1cb69012083 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -37,7 +37,7 @@ class WorkOrder(Document): ms = frappe.get_doc("Manufacturing Settings") self.set_onload("material_consumption", ms.material_consumption) self.set_onload("backflush_raw_materials_based_on", ms.backflush_raw_materials_based_on) - + self.set_onload("overproduction_percentage", ms.overproduction_percentage_for_work_order) def validate(self): self.validate_production_item() @@ -609,6 +609,23 @@ def get_item_details(item, project = None): return res +@frappe.whitelist() +def make_work_order(item, qty=0, project=None): + if not frappe.has_permission("Work Order", "write"): + frappe.throw(_("Not permitted"), frappe.PermissionError) + + item_details = get_item_details(item, project) + + wo_doc = frappe.new_doc("Work Order") + wo_doc.production_item = item + wo_doc.update(item_details) + + if flt(qty) > 0: + wo_doc.qty = flt(qty) + wo_doc.get_items_and_operations_from_bom() + + return wo_doc + @frappe.whitelist() def check_if_scrap_warehouse_mandatory(bom_no): res = {"set_scrap_wh_mandatory": False } diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 9e4dc12e653..eb686122355 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -645,4 +645,6 @@ erpnext.patches.v12_0.replace_accounting_with_accounts_in_home_settings erpnext.patches.v12_0.set_payment_entry_status erpnext.patches.v12_0.update_owner_fields_in_acc_dimension_custom_fields erpnext.patches.v12_0.set_default_for_add_taxes_from_item_tax_template -erpnext.patches.v12_0.remove_denied_leaves_from_leave_ledger \ No newline at end of file +erpnext.patches.v12_0.remove_denied_leaves_from_leave_ledger +erpnext.patches.v12_0.update_price_or_product_discount +erpnext.patches.v12_0.add_export_type_field_in_party_master diff --git a/erpnext/patches/v11_0/rename_bom_wo_fields.py b/erpnext/patches/v11_0/rename_bom_wo_fields.py index c8106a6bd59..b4a740fabbf 100644 --- a/erpnext/patches/v11_0/rename_bom_wo_fields.py +++ b/erpnext/patches/v11_0/rename_bom_wo_fields.py @@ -15,13 +15,6 @@ def execute(): rename_field(doctype, "allow_transfer_for_manufacture", "include_item_in_manufacturing") - if frappe.db.has_column('BOM', 'allow_same_item_multiple_times'): - frappe.db.sql(""" UPDATE tabBOM - SET - allow_same_item_multiple_times = 0 - WHERE - trim(coalesce(allow_same_item_multiple_times, '')) = '' """) - for doctype in ['BOM', 'Work Order']: frappe.reload_doc('manufacturing', 'doctype', frappe.scrub(doctype)) diff --git a/erpnext/patches/v12_0/add_export_type_field_in_party_master.py b/erpnext/patches/v12_0/add_export_type_field_in_party_master.py new file mode 100644 index 00000000000..c565b7ecd87 --- /dev/null +++ b/erpnext/patches/v12_0/add_export_type_field_in_party_master.py @@ -0,0 +1,40 @@ +from __future__ import unicode_literals +import frappe +from erpnext.regional.india.setup import make_custom_fields + +def execute(): + + company = frappe.get_all('Company', filters = {'country': 'India'}) + if not company: + return + + make_custom_fields() + + frappe.reload_doctype('Tax Category') + frappe.reload_doctype('Sales Taxes and Charges Template') + frappe.reload_doctype('Purchase Taxes and Charges Template') + + # Create tax category with inter state field checked + tax_category = frappe.db.get_value('Tax Category', {'name': 'OUT OF STATE'}, 'name') + + if not tax_category: + inter_state_category = frappe.get_doc({ + 'doctype': 'Tax Category', + 'title': 'OUT OF STATE', + 'is_inter_state': 1 + }).insert() + + tax_category = inter_state_category.name + + for doctype in ('Sales Taxes and Charges Template', 'Purchase Taxes and Charges Template'): + template = frappe.db.get_value(doctype, {'is_inter_state': 1, 'disabled': 0}, ['name']) + if template: + frappe.db.set_value(doctype, template, 'tax_category', tax_category) + + frappe.db.sql(""" + DELETE FROM `tabCustom Field` + WHERE fieldname = 'is_inter_state' + AND dt IN ('Sales Taxes and Charges Template', 'Purchase Taxes and Charges Template') + """) + + diff --git a/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py b/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py index f25b9eaf521..e47344bd92c 100644 --- a/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py +++ b/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py @@ -62,12 +62,12 @@ def execute(): ] for dt in doctypes: - for d in frappe.db.sql("""select name, parent, item_code, item_tax_rate from `tab{0} Item` + for d in frappe.db.sql("""select name, parenttype, parent, item_code, item_tax_rate from `tab{0} Item` where ifnull(item_tax_rate, '') not in ('', '{{}}') and item_tax_template is NULL""".format(dt), as_dict=1): item_tax_map = json.loads(d.item_tax_rate) item_tax_template_name = get_item_tax_template(item_tax_templates, - item_tax_map, d.item_code, d.parent) + item_tax_map, d.item_code, d.parenttype, d.parent) frappe.db.set_value(dt + " Item", d.name, "item_tax_template", item_tax_template_name) frappe.db.auto_commit_on_many_writes = False @@ -77,7 +77,7 @@ def execute(): settings.determine_address_tax_category_from = "Billing Address" settings.save() -def get_item_tax_template(item_tax_templates, item_tax_map, item_code, parent=None): +def get_item_tax_template(item_tax_templates, item_tax_map, item_code, parenttype=None, parent=None): # search for previously created item tax template by comparing tax maps for template, item_tax_template_map in iteritems(item_tax_templates): if item_tax_map == item_tax_template_map: @@ -88,23 +88,44 @@ def get_item_tax_template(item_tax_templates, item_tax_map, item_code, parent=No item_tax_template.title = make_autoname("Item Tax Template-.####") for tax_type, tax_rate in iteritems(item_tax_map): - if not frappe.db.exists("Account", tax_type): + account_details = frappe.db.get_value("Account", tax_type, ['name', 'account_type'], as_dict=1) + if account_details: + if account_details.account_type not in ('Tax', 'Chargeable', 'Income Account', 'Expense Account', 'Expenses Included In Valuation'): + frappe.db.set_value('Account', account_details.name, 'account_type', 'Chargeable') + else: parts = tax_type.strip().split(" - ") account_name = " - ".join(parts[:-1]) - company = frappe.db.get_value("Company", filters={"abbr": parts[-1]}) + company = get_company(parts[-1], parenttype, parent) parent_account = frappe.db.get_value("Account", filters={"account_type": "Tax", "root_type": "Liability", "is_group": 0, "company": company}, fieldname="parent_account") - - frappe.get_doc({ - "doctype": "Account", + filters = { "account_name": account_name, - "company": company, - "account_type": "Tax", - "parent_account": parent_account - }).insert() + "company": company, + "account_type": "Tax", + "parent_account": parent_account + } + tax_type = frappe.db.get_value("Account", filters) + if not tax_type: + account = frappe.new_doc("Account") + account.update(filters) + account.insert() + tax_type = account.name item_tax_template.append("taxes", {"tax_type": tax_type, "tax_rate": tax_rate}) item_tax_templates.setdefault(item_tax_template.title, {}) item_tax_templates[item_tax_template.title][tax_type] = tax_rate item_tax_template.save() return item_tax_template.name + +def get_company(company_abbr, parenttype=None, parent=None): + if parenttype and parent: + company = frappe.get_cached_value(parenttype, parent, 'company') + else: + company = frappe.db.get_value("Company", filters={"abbr": company_abbr}) + + if not company: + companies = frappe.get_all('Company') + if len(companies) == 1: + company = companies[0].name + + return company diff --git a/erpnext/patches/v12_0/set_gst_category.py b/erpnext/patches/v12_0/set_gst_category.py index 54bc5b3c74f..55bbdee7edf 100644 --- a/erpnext/patches/v12_0/set_gst_category.py +++ b/erpnext/patches/v12_0/set_gst_category.py @@ -7,6 +7,8 @@ def execute(): if not company: return + frappe.reload_doc('accounts', 'doctype', 'Tax Category') + make_custom_fields() for doctype in ['Sales Invoice', 'Purchase Invoice']: diff --git a/erpnext/patches/v12_0/update_price_or_product_discount.py b/erpnext/patches/v12_0/update_price_or_product_discount.py new file mode 100644 index 00000000000..3a8cd43e302 --- /dev/null +++ b/erpnext/patches/v12_0/update_price_or_product_discount.py @@ -0,0 +1,8 @@ +from __future__ import unicode_literals +import frappe + +def execute(): + frappe.reload_doc("accounts", "doctype", "pricing_rule") + + frappe.db.sql(""" UPDATE `tabPricing Rule` SET price_or_product_discount = 'Price' + WHERE ifnull(price_or_product_discount,'') = '' """) diff --git a/erpnext/portal/product_configurator/utils.py b/erpnext/portal/product_configurator/utils.py index 61c50e5fe01..336b9319c1d 100644 --- a/erpnext/portal/product_configurator/utils.py +++ b/erpnext/portal/product_configurator/utils.py @@ -302,6 +302,8 @@ def get_items(filters=None, search=None): if isinstance(filters, dict): filters = [['Item', fieldname, '=', value] for fieldname, value in filters.items()] + enabled_items_filter = get_conditions({ 'disabled': 0 }, 'and') + show_in_website_condition = '' if products_settings.hide_variants: show_in_website_condition = get_conditions({'show_in_website': 1 }, 'and') @@ -313,19 +315,32 @@ def get_items(filters=None, search=None): search_condition = '' if search: + # Default fields to search from + default_fields = {'name', 'item_name', 'description', 'item_group'} + + # Get meta search fields + meta = frappe.get_meta("Item") + meta_fields = set(meta.get_search_fields()) + + # Join the meta fields and default fields set + search_fields = default_fields.union(meta_fields) + try: + if frappe.db.count('Item', cache=True) > 50000: + search_fields.remove('description') + except KeyError: + pass + + # Build or filters for query search = '%{}%'.format(search) - or_filters = [ - ['name', 'like', search], - ['item_name', 'like', search], - ['description', 'like', search], - ['item_group', 'like', search] - ] + or_filters = [[field, 'like', search] for field in search_fields] + search_condition = get_conditions(or_filters, 'or') filter_condition = get_conditions(filters, 'and') where_conditions = ' and '.join( - [condition for condition in [show_in_website_condition, search_condition, filter_condition] if condition] + [condition for condition in [enabled_items_filter, show_in_website_condition, \ + search_condition, filter_condition] if condition] ) left_joins = [] diff --git a/erpnext/projects/doctype/project/project.js b/erpnext/projects/doctype/project/project.js index 25c97d1fb84..3570a0f2be4 100644 --- a/erpnext/projects/doctype/project/project.js +++ b/erpnext/projects/doctype/project/project.js @@ -4,20 +4,16 @@ frappe.ui.form.on("Project", { setup(frm) { frm.make_methods = { 'Timesheet': () => { - let doctype = 'Timesheet'; - frappe.model.with_doctype(doctype, () => { - let new_doc = frappe.model.get_new_doc(doctype); - - // add a new row and set the project - let time_log = frappe.model.get_new_doc('Timesheet Detail'); - time_log.project = frm.doc.name; - time_log.parent = new_doc.name; - time_log.parentfield = 'time_logs'; - time_log.parenttype = 'Timesheet'; - new_doc.time_logs = [time_log]; - - frappe.ui.form.make_quick_entry(doctype, null, null, new_doc); - }); + open_form(frm, "Timesheet", "Timesheet Detail", "time_logs"); + }, + 'Purchase Order': () => { + open_form(frm, "Purchase Order", "Purchase Order Item", "items"); + }, + 'Purchase Receipt': () => { + open_form(frm, "Purchase Receipt", "Purchase Receipt Item", "items"); + }, + 'Purchase Invoice': () => { + open_form(frm, "Purchase Invoice", "Purchase Invoice Item", "items"); }, }; }, @@ -80,7 +76,7 @@ frappe.ui.form.on("Project", { frm.events.set_status(frm, 'Cancelled'); }, __('Set Status')); } - + if (frappe.model.can_read("Task")) { frm.add_custom_button(__("Gantt Chart"), function () { frappe.route_options = { @@ -123,3 +119,20 @@ frappe.ui.form.on("Project", { }, }); + +function open_form(frm, doctype, child_doctype, parentfield) { + frappe.model.with_doctype(doctype, () => { + let new_doc = frappe.model.get_new_doc(doctype); + + // add a new row and set the project + let new_child_doc = frappe.model.get_new_doc(child_doctype); + new_child_doc.project = frm.doc.name; + new_child_doc.parent = new_doc.name; + new_child_doc.parentfield = parentfield; + new_child_doc.parenttype = doctype; + new_doc[parentfield] = [new_child_doc]; + + frappe.ui.form.make_quick_entry(doctype, null, null, new_doc); + }); + +} \ No newline at end of file diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py index 7083d694f80..45f26814a65 100755 --- a/erpnext/projects/doctype/task/task.py +++ b/erpnext/projects/doctype/task/task.py @@ -47,11 +47,11 @@ class Task(NestedSet): if not self.project or frappe.flags.in_test: return - expected_end_date = getdate(frappe.db.get_value("Project", self.project, "expected_end_date")) + expected_end_date = frappe.db.get_value("Project", self.project, "expected_end_date") if expected_end_date: - validate_project_dates(expected_end_date, self, "exp_start_date", "exp_end_date", "Expected") - validate_project_dates(expected_end_date, self, "act_start_date", "act_end_date", "Actual") + validate_project_dates(getdate(expected_end_date), self, "exp_start_date", "exp_end_date", "Expected") + validate_project_dates(getdate(expected_end_date), self, "act_start_date", "act_end_date", "Actual") def validate_status(self): if self.status!=self.get_db_value("status") and self.status == "Completed": @@ -278,4 +278,4 @@ def validate_project_dates(project_end_date, task, task_start, task_end, actual_ frappe.throw(_("Task's {0} Start Date cannot be after Project's End Date.").format(actual_or_expected_date)) if task.get(task_end) and date_diff(project_end_date, getdate(task.get(task_end))) < 0: - frappe.throw(_("Task's {0} End Date cannot be after Project's End Date.").format(actual_or_expected_date)) \ No newline at end of file + frappe.throw(_("Task's {0} End Date cannot be after Project's End Date.").format(actual_or_expected_date)) diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py index c4481c9aa0e..e90821689bd 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.py +++ b/erpnext/projects/doctype/timesheet/timesheet.py @@ -188,7 +188,8 @@ class Timesheet(Document): }, as_dict=True) # check internal overlap for time_log in self.time_logs: - if not (time_log.from_time or time_log.to_time): continue + if not (time_log.from_time and time_log.to_time + and args.from_time and args.to_time): continue if (fieldname != 'workstation' or args.get(fieldname) == time_log.get(fieldname)) and \ args.idx != time_log.idx and ((args.from_time > time_log.from_time and args.from_time < time_log.to_time) or diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js index 02c30587f67..926227b24c8 100644 --- a/erpnext/public/js/controllers/buying.js +++ b/erpnext/public/js/controllers/buying.js @@ -30,7 +30,7 @@ erpnext.buying.BuyingController = erpnext.TransactionController.extend({ && frappe.meta.has_field(this.frm.doc.doctype, "disable_rounded_total")) { var df = frappe.meta.get_docfield(this.frm.doc.doctype, "disable_rounded_total"); - var disable = df.default || cint(frappe.sys_defaults.disable_rounded_total); + var disable = cint(df.default) || cint(frappe.sys_defaults.disable_rounded_total); this.frm.set_value("disable_rounded_total", disable); } diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index d26c1f8d532..c735f840769 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -500,6 +500,9 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ () => { var d = locals[cdt][cdn]; me.add_taxes_from_item_tax_template(d.item_tax_rate); + if (d.free_item_data) { + me.apply_product_discount(d.free_item_data); + } }, () => me.frm.script_manager.trigger("price_list_rate", cdt, cdn), () => me.toggle_conversion_factor(item), @@ -516,7 +519,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ } }, () => me.conversion_factor(doc, cdt, cdn, true), - () => me.validate_pricing_rule(item) + () => me.remove_pricing_rule(item) ]); } } @@ -1174,7 +1177,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ callback: function(r) { if (!r.exc && r.message) { r.message.forEach(row_item => { - me.validate_pricing_rule(row_item); + me.remove_pricing_rule(row_item); }); me._set_values_for_item_list(r.message); me.calculate_taxes_and_totals(); @@ -1283,6 +1286,8 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ _set_values_for_item_list: function(children) { var me = this; var price_list_rate_changed = false; + var items_rule_dict = {}; + for(var i=0, l=children.length; i { + if (in_list(data.apply_rule_on_other_items, d[data.apply_rule_on])) { + for(var k in data) { + if (in_list(fields, k)) { + frappe.model.set_value(d.doctype, d.name, k, data[k]); + } + } + } + }); + } + }, + + apply_product_discount: function(free_item_data) { + const items = this.frm.doc.items.filter(d => (d.item_code == free_item_data.item_code + && d.is_free_item)) || []; + + if (!items.length) { + let row_to_modify = frappe.model.add_child(this.frm.doc, + this.frm.doc.doctype + ' Item', 'items'); + + for (let key in free_item_data) { + row_to_modify[key] = free_item_data[key]; + } + } + }, + apply_price_list: function(item, reset_plc_conversion) { // We need to reset plc_conversion_rate sometimes because the call to // `erpnext.stock.get_item_details.apply_price_list` is sensitive to its value @@ -1348,33 +1396,11 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ }); }, - validate_pricing_rule: function(item) { + remove_pricing_rule: function(item) { let me = this; const fields = ["discount_percentage", "discount_amount", "pricing_rules"]; - if (item.pricing_rules && item.apply_rule_on_other_items) { - frappe.call({ - method: "erpnext.accounts.doctype.pricing_rule.utils.validate_pricing_rule_for_different_cond", - args: { - doc: me.frm.doc - }, - callback: function(r) { - if (r.message) { - r.message.items.forEach(d => { - me.frm.doc.items.forEach(row => { - if(d.name == row.name) { - fields.forEach(f => { - row[f] = d[f]; - }); - } - }); - }); - - me.trigger_price_list_rate(); - } - } - }); - } else if(item.remove_free_item) { + if(item.remove_free_item) { var items = []; me.frm.doc.items.forEach(d => { diff --git a/erpnext/public/js/financial_statements.js b/erpnext/public/js/financial_statements.js index 63e057c39d1..dead309a5d0 100644 --- a/erpnext/public/js/financial_statements.js +++ b/erpnext/public/js/financial_statements.js @@ -4,7 +4,7 @@ erpnext.financial_statements = { "filters": get_filters(), "formatter": function(value, row, column, data, default_formatter) { if (column.fieldname=="account") { - value = data.account_name; + value = data.account_name || value; column.link_onclick = "erpnext.financial_statements.open_general_ledger(" + JSON.stringify(data) + ")"; diff --git a/erpnext/public/js/queries.js b/erpnext/public/js/queries.js index 84d2113c067..560a5617da5 100644 --- a/erpnext/public/js/queries.js +++ b/erpnext/public/js/queries.js @@ -65,7 +65,7 @@ $.extend(erpnext.queries, { frappe.throw(__("Please set {0}", [__(frappe.meta.get_label(doc.doctype, frappe.dynamic_link.fieldname, doc.name))])); } - console.log(frappe.dynamic_link) + return { query: 'frappe.contacts.doctype.address.address.address_query', filters: { diff --git a/erpnext/public/js/utils/party.js b/erpnext/public/js/utils/party.js index a8d3888ba0f..99c1b8ae8f3 100644 --- a/erpnext/public/js/utils/party.js +++ b/erpnext/public/js/utils/party.js @@ -7,6 +7,21 @@ erpnext.utils.get_party_details = function(frm, method, args, callback) { if(!method) { method = "erpnext.accounts.party.get_party_details"; } + + if (args) { + if (in_list(['Sales Invoice', 'Sales Order', 'Delivery Note'], frm.doc.doctype)) { + if (frm.doc.company_address && (!args.company_address)) { + args.company_address = frm.doc.company_address; + } + } + + if (in_list(['Purchase Invoice', 'Purchase Order', 'Purchase Receipt'], frm.doc.doctype)) { + if (frm.doc.shipping_address && (!args.shipping_address)) { + args.shipping_address = frm.doc.shipping_address; + } + } + } + if(!args) { if((frm.doctype != "Purchase Order" && frm.doc.customer) || (frm.doc.party_name && in_list(['Quotation', 'Opportunity'], frm.doc.doctype))) { @@ -30,6 +45,35 @@ erpnext.utils.get_party_details = function(frm, method, args, callback) { }; } + if (in_list(['Sales Invoice', 'Sales Order', 'Delivery Note'], frm.doc.doctype)) { + if (!args) { + args = { + party: frm.doc.customer || frm.doc.party_name, + party_type: 'Customer' + } + } + if (frm.doc.company_address && (!args.company_address)) { + args.company_address = frm.doc.company_address; + } + + if (frm.doc.shipping_address_name &&(!args.shipping_address_name)) { + args.shipping_address_name = frm.doc.shipping_address_name; + } + } + + if (in_list(['Purchase Invoice', 'Purchase Order', 'Purchase Receipt'], frm.doc.doctype)) { + if (!args) { + args = { + party: frm.doc.supplier, + party_type: 'Supplier' + } + } + + if (frm.doc.shipping_address && (!args.shipping_address)) { + args.shipping_address = frm.doc.shipping_address; + } + } + if (args) { args.posting_date = frm.doc.posting_date || frm.doc.transaction_date; } diff --git a/erpnext/quality_management/doctype/quality_action_resolution/quality_action_resolution.json b/erpnext/quality_management/doctype/quality_action_resolution/quality_action_resolution.json index 74370cc3efe..a4e6aed86a0 100644 --- a/erpnext/quality_management/doctype/quality_action_resolution/quality_action_resolution.json +++ b/erpnext/quality_management/doctype/quality_action_resolution/quality_action_resolution.json @@ -13,7 +13,7 @@ "fieldname": "problem", "fieldtype": "Long Text", "in_list_view": 1, - "label": "Problem" + "label": "Review" }, { "fieldname": "sb_00", diff --git a/erpnext/quality_management/doctype/quality_procedure_process/quality_procedure_process.json b/erpnext/quality_management/doctype/quality_procedure_process/quality_procedure_process.json index f5c0fbc2523..0a67fa505ee 100644 --- a/erpnext/quality_management/doctype/quality_procedure_process/quality_procedure_process.json +++ b/erpnext/quality_management/doctype/quality_procedure_process/quality_procedure_process.json @@ -18,7 +18,7 @@ "fieldname": "procedure", "fieldtype": "Link", "in_list_view": 1, - "label": "Procedure", + "label": "Child Procedure", "options": "Quality Procedure" } ], diff --git a/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py b/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py index fef73d9a0a8..fa6fb706e9b 100644 --- a/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py +++ b/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py @@ -64,7 +64,8 @@ class TestGSTR3BReport(unittest.TestCase): self.assertEqual(output["inter_sup"]["unreg_details"][0]["iamt"], 18), self.assertEqual(output["sup_details"]["osup_nil_exmp"]["txval"], 100), self.assertEqual(output["inward_sup"]["isup_details"][0]["inter"], 250) - self.assertEqual(output["itc_elg"]["itc_avl"][4]["iamt"], 45) + self.assertEqual(output["itc_elg"]["itc_avl"][4]["samt"], 22.50) + self.assertEqual(output["itc_elg"]["itc_avl"][4]["camt"], 22.50) def make_sales_invoice(): si = create_sales_invoice(company="_Test Company GST", @@ -158,10 +159,18 @@ def create_purchase_invoices(): pi.append("taxes", { "charge_type": "On Net Total", - "account_head": "IGST - _GST", + "account_head": "CGST - _GST", "cost_center": "Main - _GST", - "description": "IGST @ 18.0", - "rate": 18 + "description": "CGST @ 9.0", + "rate": 9 + }) + + pi.append("taxes", { + "charge_type": "On Net Total", + "account_head": "SGST - _GST", + "cost_center": "Main - _GST", + "description": "SGST @ 9.0", + "rate": 9 }) pi.submit() diff --git a/erpnext/regional/india/__init__.py b/erpnext/regional/india/__init__.py index 46c874b2524..0ed98b74eef 100644 --- a/erpnext/regional/india/__init__.py +++ b/erpnext/regional/india/__init__.py @@ -1,4 +1,5 @@ from __future__ import unicode_literals +from six import iteritems states = [ '', @@ -79,4 +80,6 @@ state_numbers = { "Uttar Pradesh": "09", "Uttarakhand": "05", "West Bengal": "19", -} \ No newline at end of file +} + +number_state_mapping = {v: k for k, v in iteritems(state_numbers)} \ No newline at end of file diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index 756c17dc3b3..14fdba013c7 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -107,7 +107,12 @@ def make_custom_fields(update=True): dict(fieldname='gst_category', label='GST Category', fieldtype='Select', insert_after='gst_section', print_hide=1, options='\nRegistered Regular\nRegistered Composition\nUnregistered\nSEZ\nOverseas\nUIN Holders', - fetch_from='supplier.gst_category', fetch_if_empty=1) + fetch_from='supplier.gst_category', fetch_if_empty=1), + dict(fieldname='export_type', label='Export Type', + fieldtype='Select', insert_after='gst_category', print_hide=1, + depends_on='eval:in_list(["SEZ", "Overseas"], doc.gst_category)', + options='\nWith Payment of Tax\nWithout Payment of Tax', fetch_from='supplier.export_type', + fetch_if_empty=1), ] sales_invoice_gst_category = [ @@ -116,20 +121,21 @@ def make_custom_fields(update=True): dict(fieldname='gst_category', label='GST Category', fieldtype='Select', insert_after='gst_section', print_hide=1, options='\nRegistered Regular\nRegistered Composition\nUnregistered\nSEZ\nOverseas\nConsumer\nDeemed Export\nUIN Holders', - fetch_from='customer.gst_category', fetch_if_empty=1) + fetch_from='customer.gst_category', fetch_if_empty=1), + dict(fieldname='export_type', label='Export Type', + fieldtype='Select', insert_after='gst_category', print_hide=1, + depends_on='eval:in_list(["SEZ", "Overseas", "Deemed Export"], doc.gst_category)', + options='\nWith Payment of Tax\nWithout Payment of Tax', fetch_from='customer.export_type', + fetch_if_empty=1), ] invoice_gst_fields = [ dict(fieldname='invoice_copy', label='Invoice Copy', - fieldtype='Select', insert_after='gst_category', print_hide=1, allow_on_submit=1, + fieldtype='Select', insert_after='export_type', print_hide=1, allow_on_submit=1, options='Original for Recipient\nDuplicate for Transporter\nDuplicate for Supplier\nTriplicate for Supplier'), dict(fieldname='reverse_charge', label='Reverse Charge', fieldtype='Select', insert_after='invoice_copy', print_hide=1, options='Y\nN', default='N'), - dict(fieldname='export_type', label='Export Type', - fieldtype='Select', insert_after='reverse_charge', print_hide=1, - depends_on='eval:in_list(["SEZ", "Overseas", "Deemed Export"], doc.gst_category)', - options='\nWith Payment of Tax\nWithout Payment of Tax'), dict(fieldname='ecommerce_gstin', label='E-commerce GSTIN', fieldtype='Data', insert_after='export_type', print_hide=1), dict(fieldname='gst_col_break', fieldtype='Column Break', insert_after='ecommerce_gstin'), @@ -142,13 +148,13 @@ def make_custom_fields(update=True): purchase_invoice_gst_fields = [ dict(fieldname='supplier_gstin', label='Supplier GSTIN', fieldtype='Data', insert_after='supplier_address', - fetch_from='supplier_address.gstin', print_hide=1), + fetch_from='supplier_address.gstin', print_hide=1, read_only=1), dict(fieldname='company_gstin', label='Company GSTIN', fieldtype='Data', insert_after='shipping_address_display', - fetch_from='shipping_address.gstin', print_hide=1), + fetch_from='shipping_address.gstin', print_hide=1, read_only=1), dict(fieldname='place_of_supply', label='Place of Supply', fieldtype='Data', insert_after='shipping_address', - print_hide=1, read_only=0), + print_hide=1, read_only=1), ] purchase_invoice_itc_fields = [ @@ -167,17 +173,17 @@ def make_custom_fields(update=True): sales_invoice_gst_fields = [ dict(fieldname='billing_address_gstin', label='Billing Address GSTIN', - fieldtype='Data', insert_after='customer_address', + fieldtype='Data', insert_after='customer_address', read_only=1, fetch_from='customer_address.gstin', print_hide=1), dict(fieldname='customer_gstin', label='Customer GSTIN', fieldtype='Data', insert_after='shipping_address_name', fetch_from='shipping_address_name.gstin', print_hide=1), dict(fieldname='place_of_supply', label='Place of Supply', fieldtype='Data', insert_after='customer_gstin', - print_hide=1, read_only=0), + print_hide=1, read_only=1), dict(fieldname='company_gstin', label='Company GSTIN', fieldtype='Data', insert_after='company_address', - fetch_from='company_address.gstin', print_hide=1), + fetch_from='company_address.gstin', print_hide=1, read_only=1), ] sales_invoice_shipping_fields = [ @@ -194,7 +200,11 @@ def make_custom_fields(update=True): inter_state_gst_field = [ dict(fieldname='is_inter_state', label='Is Inter State', - fieldtype='Check', insert_after='disabled', print_hide=1) + fieldtype='Check', insert_after='disabled', print_hide=1), + dict(fieldname='tax_category_column_break', fieldtype='Column Break', + insert_after='is_inter_state'), + dict(fieldname='gst_state', label='Source State', fieldtype='Select', + options='\n'.join(states), insert_after='company') ] ewaybill_fields = [ @@ -374,8 +384,7 @@ def make_custom_fields(update=True): 'Sales Invoice': sales_invoice_gst_category + invoice_gst_fields + sales_invoice_shipping_fields + sales_invoice_gst_fields + si_ewaybill_fields, 'Delivery Note': sales_invoice_gst_fields + ewaybill_fields + sales_invoice_shipping_fields, 'Sales Order': sales_invoice_gst_fields, - 'Sales Taxes and Charges Template': inter_state_gst_field, - 'Purchase Taxes and Charges Template': inter_state_gst_field, + 'Tax Category': inter_state_gst_field, 'Item': [ dict(fieldname='gst_hsn_code', label='HSN/SAC', fieldtype='Link', options='GST HSN Code', insert_after='item_group'), @@ -459,6 +468,15 @@ def make_custom_fields(update=True): 'insert_after': 'gst_transporter_id', 'options': 'Registered Regular\nRegistered Composition\nUnregistered\nSEZ\nOverseas\nUIN Holders', 'default': 'Unregistered' + }, + { + 'fieldname': 'export_type', + 'label': 'Export Type', + 'fieldtype': 'Select', + 'insert_after': 'gst_category', + 'default': 'Without Payment of Tax', + 'depends_on':'eval:in_list(["SEZ", "Overseas"], doc.gst_category)', + 'options': '\nWith Payment of Tax\nWithout Payment of Tax' } ], 'Customer': [ @@ -469,6 +487,15 @@ def make_custom_fields(update=True): 'insert_after': 'customer_type', 'options': 'Registered Regular\nRegistered Composition\nUnregistered\nSEZ\nOverseas\nConsumer\nDeemed Export\nUIN Holders', 'default': 'Unregistered' + }, + { + 'fieldname': 'export_type', + 'label': 'Export Type', + 'fieldtype': 'Select', + 'insert_after': 'gst_category', + 'default': 'Without Payment of Tax', + 'depends_on':'eval:in_list(["SEZ", "Overseas", "Deemed Export"], doc.gst_category)', + 'options': '\nWith Payment of Tax\nWithout Payment of Tax' } ] } diff --git a/erpnext/regional/india/taxes.js b/erpnext/regional/india/taxes.js new file mode 100644 index 00000000000..1e59032db10 --- /dev/null +++ b/erpnext/regional/india/taxes.js @@ -0,0 +1,41 @@ +erpnext.setup_auto_gst_taxation = (doctype) => { + frappe.ui.form.on(doctype, { + company_address: function(frm) { + frm.trigger('get_tax_template'); + }, + shipping_address: function(frm) { + frm.trigger('get_tax_template'); + }, + tax_category: function(frm) { + frm.trigger('get_tax_template'); + }, + get_tax_template: function(frm) { + let party_details = { + 'shipping_address': frm.doc.shipping_address || '', + 'shipping_address_name': frm.doc.shipping_address_name || '', + 'customer_address': frm.doc.customer_address || '', + 'customer': frm.doc.customer, + 'supplier': frm.doc.supplier, + 'supplier_gstin': frm.doc.supplier_gstin, + 'company_gstin': frm.doc.company_gstin, + 'tax_category': frm.doc.tax_category + }; + + frappe.call({ + method: 'erpnext.regional.india.utils.get_regional_address_details', + args: { + party_details: JSON.stringify(party_details), + doctype: frm.doc.doctype, + company: frm.doc.company, + return_taxes: 1 + }, + callback: function(r) { + if(r.message) { + frm.set_value('taxes_and_charges', r.message.taxes_and_charges); + } + } + }); + } + }); +}; + diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index aae07797a15..0f9156a6b4c 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -7,6 +7,8 @@ from erpnext.controllers.taxes_and_totals import get_itemised_tax, get_itemised_ from erpnext.controllers.accounts_controller import get_taxes_and_charges from erpnext.hr.utils import get_salary_assignment from erpnext.hr.doctype.salary_structure.salary_structure import make_salary_slip +from erpnext.regional.india import number_state_mapping +from six import string_types def validate_gstin_for_india(doc, method): if hasattr(doc, 'gst_state') and doc.gst_state: @@ -46,6 +48,14 @@ def validate_gstin_for_india(doc, method): frappe.throw(_("Invalid GSTIN! First 2 digits of GSTIN should match with State number {0}.") .format(doc.gst_state_number)) +def update_gst_category(doc, method): + for link in doc.links: + if link.link_doctype in ['Customer', 'Supplier']: + if doc.get('gstin'): + frappe.db.sql(""" + UPDATE `tab{0}` SET gst_category = %s WHERE name = %s AND gst_category = 'Unregistered' + """.format(link.link_doctype), ("Registered Regular", link.link_name)) #nosec + def set_gst_state_and_state_number(doc): if not doc.gst_state: if not doc.state: @@ -122,44 +132,108 @@ def test_method(): '''test function''' return 'overridden' -def get_place_of_supply(out, doctype): +def get_place_of_supply(party_details, doctype): if not frappe.get_meta('Address').has_field('gst_state'): return - if doctype in ("Sales Invoice", "Delivery Note"): - address_name = out.shipping_address_name or out.customer_address - elif doctype == "Purchase Invoice": - address_name = out.shipping_address or out.supplier_address + if doctype in ("Sales Invoice", "Delivery Note", "Sales Order"): + address_name = party_details.shipping_address_name or party_details.customer_address + elif doctype in ("Purchase Invoice", "Purchase Order", "Purchase Receipt"): + address_name = party_details.shipping_address or party_details.supplier_address if address_name: address = frappe.db.get_value("Address", address_name, ["gst_state", "gst_state_number"], as_dict=1) if address and address.gst_state and address.gst_state_number: return cstr(address.gst_state_number) + "-" + cstr(address.gst_state) -def get_regional_address_details(out, doctype, company): - out.place_of_supply = get_place_of_supply(out, doctype) +@frappe.whitelist() +def get_regional_address_details(party_details, doctype, company, return_taxes=None): - if not out.place_of_supply: return + if isinstance(party_details, string_types): + party_details = json.loads(party_details) + party_details = frappe._dict(party_details) - if doctype in ("Sales Invoice", "Delivery Note"): + party_details.place_of_supply = get_place_of_supply(party_details, doctype) + if doctype in ("Sales Invoice", "Delivery Note", "Sales Order"): master_doctype = "Sales Taxes and Charges Template" - if not out.company_gstin: - return - elif doctype == "Purchase Invoice": - master_doctype = "Purchase Taxes and Charges Template" - if not out.supplier_gstin: + + get_tax_template_for_sez(party_details, master_doctype, company, 'Customer') + get_tax_template_based_on_category(master_doctype, company, party_details) + + if party_details.get('taxes_and_charges') and return_taxes: + return party_details + + if not party_details.company_gstin: return - if ((doctype in ("Sales Invoice", "Delivery Note") and out.company_gstin - and out.company_gstin[:2] != out.place_of_supply[:2]) or (doctype == "Purchase Invoice" - and out.supplier_gstin and out.supplier_gstin[:2] != out.place_of_supply[:2])): - default_tax = frappe.db.get_value(master_doctype, {"company": company, "is_inter_state":1, "disabled":0}) + elif doctype in ("Purchase Invoice", "Purchase Order", "Purchase Receipt"): + master_doctype = "Purchase Taxes and Charges Template" + + get_tax_template_for_sez(party_details, master_doctype, company, 'Supplier') + get_tax_template_based_on_category(master_doctype, company, party_details) + + if party_details.get('taxes_and_charges') and return_taxes: + return party_details + + if not party_details.supplier_gstin: + return + + if not party_details.place_of_supply: return + + if not party_details.company_gstin: return + + if ((doctype in ("Sales Invoice", "Delivery Note", "Sales Order") and party_details.company_gstin + and party_details.company_gstin[:2] != party_details.place_of_supply[:2]) or (doctype in ("Purchase Invoice", + "Purchase Order", "Purchase Receipt") and party_details.supplier_gstin and party_details.supplier_gstin[:2] != party_details.place_of_supply[:2])): + default_tax = get_tax_template(master_doctype, company, 1, party_details.company_gstin[:2]) else: - default_tax = frappe.db.get_value(master_doctype, {"company": company, "disabled":0, "is_default": 1}) + default_tax = get_tax_template(master_doctype, company, 0, party_details.company_gstin[:2]) if not default_tax: return - out["taxes_and_charges"] = default_tax - out.taxes = get_taxes_and_charges(master_doctype, default_tax) + party_details["taxes_and_charges"] = default_tax + party_details.taxes = get_taxes_and_charges(master_doctype, default_tax) + + if return_taxes: + return party_details + +def get_tax_template_based_on_category(master_doctype, company, party_details): + if not party_details.get('tax_category'): + return + + default_tax = frappe.db.get_value(master_doctype, {'company': company, 'tax_category': party_details.get('tax_category')}, + 'name') + + if default_tax: + party_details["taxes_and_charges"] = default_tax + party_details.taxes = get_taxes_and_charges(master_doctype, default_tax) + +def get_tax_template(master_doctype, company, is_inter_state, state_code): + tax_categories = frappe.get_all('Tax Category', fields = ['name', 'is_inter_state', 'gst_state'], + filters = {'is_inter_state': is_inter_state}) + + default_tax = '' + + for tax_category in tax_categories: + if tax_category.gst_state == number_state_mapping[state_code] or \ + (not default_tax and not tax_category.gst_state): + default_tax = frappe.db.get_value(master_doctype, + {'disabled': 0, 'tax_category': tax_category.name}, 'name') + + return default_tax + +def get_tax_template_for_sez(party_details, master_doctype, company, party_type): + + gst_details = frappe.db.get_value(party_type, {'name': party_details.get(frappe.scrub(party_type))}, + ['gst_category', 'export_type'], as_dict=1) + + if gst_details: + if gst_details.gst_category == 'SEZ' and gst_details.export_type == 'With Payment of Tax': + default_tax = frappe.db.get_value(master_doctype, {"company": company, "is_inter_state":1, "disabled":0, + "gst_state": number_state_mapping[party_details.company_gstin[:2]]}) + + party_details["taxes_and_charges"] = default_tax + party_details.taxes = get_taxes_and_charges(master_doctype, default_tax) + def calculate_annual_eligible_hra_exemption(doc): basic_component = frappe.get_cached_value('Company', doc.company, "basic_component") @@ -555,7 +629,7 @@ def get_gst_accounts(company, account_wise=False): filters={"parent": "GST Settings", "company": company}, fields=["cgst_account", "sgst_account", "igst_account", "cess_account"]) - if not gst_settings_accounts: + if not gst_settings_accounts and not frappe.flags.in_test: frappe.throw(_("Please set GST Accounts in GST Settings")) for d in gst_settings_accounts: diff --git a/erpnext/regional/italy/e-invoice.xml b/erpnext/regional/italy/e-invoice.xml index 049a7eba61a..69b8e3e488d 100644 --- a/erpnext/regional/italy/e-invoice.xml +++ b/erpnext/regional/italy/e-invoice.xml @@ -205,7 +205,9 @@ {%- endif %} {{ format_float(data.taxable_amount, item_meta.get_field("tax_amount").precision) }} {{ format_float(data.tax_amount, item_meta.get_field("tax_amount").precision) }} - {{ doc.vat_collectability.split("-")[0] }} + {%- if data.vat_collectability %} + {{ doc.vat_collectability.split("-")[0] }} + {%- endif %} {%- if data.tax_exemption_law %} {{ data.tax_exemption_law }} {%- endif %} diff --git a/erpnext/regional/report/gstr_1/gstr_1.js b/erpnext/regional/report/gstr_1/gstr_1.js index 96827682807..80ef3718aa0 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.js +++ b/erpnext/regional/report/gstr_1/gstr_1.js @@ -55,14 +55,25 @@ frappe.query_reports["GSTR-1"] = { report.page.add_inner_button(__("Download as Json"), function () { var filters = report.get_values(); - const args = { - cmd: 'erpnext.regional.report.gstr_1.gstr_1.get_json', - data: report.data, - report_name: report.report_name, - filters: filters - }; - - open_url_post(frappe.request.url, args); + frappe.call({ + method: 'erpnext.regional.report.gstr_1.gstr_1.get_json', + args: { + data: report.data, + report_name: report.report_name, + filters: filters + }, + callback: function(r) { + if (r.message) { + const args = { + cmd: 'erpnext.regional.report.gstr_1.gstr_1.download_json_file', + data: r.message.data, + report_name: r.message.report_name, + report_type: r.message.report_type + }; + open_url_post(frappe.request.url, args); + } + } + }); }); } } diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py index 090616b0777..4f9cc7ff7a0 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.py +++ b/erpnext/regional/report/gstr_1/gstr_1.py @@ -532,16 +532,9 @@ class Gstr1Report(object): self.columns = self.invoice_columns + self.tax_columns + self.other_columns @frappe.whitelist() -def get_json(): - data = frappe._dict(frappe.local.form_dict) - - del data["cmd"] - if "csrf_token" in data: - del data["csrf_token"] - - filters = json.loads(data["filters"]) - report_data = json.loads(data["data"]) - report_name = data["report_name"] +def get_json(filters, report_name, data): + filters = json.loads(filters) + report_data = json.loads(data) gstin = get_company_gstin_number(filters["company"]) fp = "%02d%s" % (getdate(filters["to_date"]).month, getdate(filters["to_date"]).year) @@ -575,7 +568,11 @@ def get_json(): out = get_export_json(res) gst_json["exp"] = out - download_json_file(report_name, filters["type_of_business"], gst_json) + return { + 'report_name': report_name, + 'report_type': filters['type_of_business'], + 'data': gst_json + } def get_b2b_json(res, gstin): inv_type, out = {"Registered Regular": "R", "Deemed Export": "DE", "URD": "URD", "SEZ": "SEZ"}, [] @@ -722,11 +719,15 @@ def get_company_gstin_number(company): if gstin: return gstin[0]["gstin"] else: - frappe.throw(_("Please set valid GSTIN No. in Company Address")) + frappe.throw(_("Please set valid GSTIN No. in Company Address for company {0}".format( + frappe.bold(company) + ))) -def download_json_file(filename, report_type, data): +@frappe.whitelist() +def download_json_file(): ''' download json content in a file ''' - frappe.response['filename'] = frappe.scrub("{0} {1}".format(filename, report_type)) + '.json' - frappe.response['filecontent'] = json.dumps(data) + data = frappe._dict(frappe.local.form_dict) + frappe.response['filename'] = frappe.scrub("{0} {1}".format(data['report_name'], data['report_type'])) + '.json' + frappe.response['filecontent'] = data['data'] frappe.response['content_type'] = 'application/json' frappe.response['type'] = 'download' diff --git a/erpnext/regional/report/gstr_2/gstr_2.py b/erpnext/regional/report/gstr_2/gstr_2.py index a3622690078..f326fe07cac 100644 --- a/erpnext/regional/report/gstr_2/gstr_2.py +++ b/erpnext/regional/report/gstr_2/gstr_2.py @@ -44,12 +44,16 @@ class Gstr2Report(Gstr1Report): for inv, items_based_on_rate in self.items_based_on_tax_rate.items(): invoice_details = self.invoices.get(inv) for rate, items in items_based_on_rate.items(): - row, taxable_value = self.get_row_data_for_invoice(inv, invoice_details, rate, items) - tax_amount = taxable_value * rate / 100 - if inv in self.igst_invoices: - row += [tax_amount, 0, 0] + if inv not in self.igst_invoices: + rate = rate / 2 + row, taxable_value = self.get_row_data_for_invoice(inv, invoice_details, rate, items) + tax_amount = taxable_value * rate / 100 + row += [0, tax_amount, tax_amount] else: - row += [0, tax_amount / 2, tax_amount / 2] + row, taxable_value = self.get_row_data_for_invoice(inv, invoice_details, rate, items) + tax_amount = taxable_value * rate / 100 + row += [tax_amount, 0, 0] + row += [ self.invoice_cess.get(inv), diff --git a/erpnext/selling/doctype/customer/customer.js b/erpnext/selling/doctype/customer/customer.js index 458a56c9e79..68f75a03495 100644 --- a/erpnext/selling/doctype/customer/customer.js +++ b/erpnext/selling/doctype/customer/customer.js @@ -5,13 +5,13 @@ frappe.ui.form.on("Customer", { setup: function(frm) { frm.make_methods = { - 'Quotation': () => erpnext.utils.create_new_doc('Quotation', { - 'quotation_to': frm.doc.doctype, - 'party_name': frm.doc.name + 'Quotation': () => frappe.model.open_mapped_doc({ + method: 'erpnext.selling.doctype.customer.customer.make_quotation', + frm: cur_frm }), - 'Opportunity': () => erpnext.utils.create_new_doc('Opportunity', { - 'opportunity_from': frm.doc.doctype, - 'party_name': frm.doc.name + 'Opportunity': () => frappe.model.open_mapped_doc({ + method: 'erpnext.selling.doctype.customer.customer.make_opportunity', + frm: cur_frm }) } @@ -49,9 +49,9 @@ frappe.ui.form.on("Customer", { }) frm.set_query('customer_primary_address', function(doc) { return { - query: "erpnext.selling.doctype.customer.customer.get_customer_primary_address", filters: { - 'customer': doc.name + 'link_doctype': 'Customer', + 'link_name': doc.name } } }) diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index a8e3ce4eae7..6ee02190bad 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -12,6 +12,7 @@ from erpnext.utilities.transaction_base import TransactionBase from erpnext.accounts.party import validate_party_accounts, get_dashboard_info, get_timeline_data # keep this from frappe.contacts.address_and_contact import load_address_and_contact, delete_contact_and_address from frappe.model.rename_doc import update_linked_doctypes +from frappe.model.mapper import get_mapped_doc class Customer(TransactionBase): def get_feed(self): @@ -204,6 +205,66 @@ class Customer(TransactionBase): else: frappe.msgprint(_("Multiple Loyalty Program found for the Customer. Please select manually.")) +@frappe.whitelist() +def make_quotation(source_name, target_doc=None): + + def set_missing_values(source, target): + _set_missing_values(source, target) + + target_doc = get_mapped_doc("Customer", source_name, + {"Customer": { + "doctype": "Quotation", + "field_map": { + "name":"party_name" + } + }}, target_doc, set_missing_values) + + target_doc.quotation_to = "Customer" + target_doc.run_method("set_missing_values") + target_doc.run_method("set_other_charges") + target_doc.run_method("calculate_taxes_and_totals") + + price_list = frappe.get_value("Customer", source_name, "default_price_list") + if price_list: + target_doc.selling_price_list = price_list + + return target_doc + +@frappe.whitelist() +def make_opportunity(source_name, target_doc=None): + def set_missing_values(source, target): + _set_missing_values(source, target) + + target_doc = get_mapped_doc("Customer", source_name, + {"Customer": { + "doctype": "Opportunity", + "field_map": { + "name": "party_name", + "doctype": "opportunity_from", + } + }}, target_doc, set_missing_values) + + return target_doc + +def _set_missing_values(source, target): + address = frappe.get_all('Dynamic Link', { + 'link_doctype': source.doctype, + 'link_name': source.name, + 'parenttype': 'Address', + }, ['parent'], limit=1) + + contact = frappe.get_all('Dynamic Link', { + 'link_doctype': source.doctype, + 'link_name': source.name, + 'parenttype': 'Contact', + }, ['parent'], limit=1) + + if address: + target.customer_address = address[0].parent + + if contact: + target.contact_person = contact[0].parent + @frappe.whitelist() def get_loyalty_programs(doc): ''' returns applicable loyalty programs for a customer ''' @@ -397,15 +458,3 @@ def get_customer_primary_contact(doctype, txt, searchfield, start, page_len, fil 'customer': customer, 'txt': '%%%s%%' % txt }) - -def get_customer_primary_address(doctype, txt, searchfield, start, page_len, filters): - customer = frappe.db.escape(filters.get('customer')) - return frappe.db.sql(""" - select `tabAddress`.name from `tabAddress`, `tabDynamic Link` - where `tabAddress`.name = `tabDynamic Link`.parent and `tabDynamic Link`.link_name = %(customer)s - and `tabDynamic Link`.link_doctype = 'Customer' - and `tabAddress`.name like %(txt)s - """, { - 'customer': customer, - 'txt': '%%%s%%' % txt - }) diff --git a/erpnext/selling/doctype/quotation/quotation.json b/erpnext/selling/doctype/quotation/quotation.json index ea047155244..64ad1b5de95 100644 --- a/erpnext/selling/doctype/quotation/quotation.json +++ b/erpnext/selling/doctype/quotation/quotation.json @@ -1,3509 +1,1027 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 1, - "allow_rename": 0, - "autoname": "naming_series:", - "beta": 0, - "creation": "2013-05-24 19:29:08", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Document", - "editable_grid": 1, - "fields": [ - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "customer_section", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "", - "length": 0, - "no_copy": 0, - "options": "fa fa-user", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "{customer_name}", - "fieldname": "title", - "fieldtype": "Data", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Title", - "length": 0, - "no_copy": 1, - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", - "fieldname": "naming_series", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Series", - "length": 0, - "no_copy": 1, - "oldfieldname": "naming_series", - "oldfieldtype": "Select", - "options": "SAL-QTN-.YYYY.-", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 1, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Customer", - "fetch_if_empty": 0, - "fieldname": "quotation_to", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 1, - "label": "Quotation To", - "length": 0, - "no_copy": 0, - "oldfieldname": "quotation_to", - "oldfieldtype": "Select", - "options": "DocType", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 1, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "fetch_if_empty": 0, - "fieldname": "party_name", - "fieldtype": "Dynamic Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 1, - "in_list_view": 0, - "in_standard_filter": 1, - "label": "Party", - "length": 0, - "no_copy": 0, - "oldfieldname": "customer", - "oldfieldtype": "Link", - "options": "quotation_to", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 1, - "collapsible": 0, - "columns": 0, - "fetch_from": "", - "fieldname": "customer_name", - "fieldtype": "Data", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 1, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Customer Name", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break1", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "oldfieldtype": "Column Break", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, - "width": "50%" - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "amended_from", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 1, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Amended From", - "length": 0, - "no_copy": 1, - "oldfieldname": "amended_from", - "oldfieldtype": "Data", - "options": "Quotation", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, - "width": "150px" - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "", - "fieldname": "company", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Company", - "length": 0, - "no_copy": 0, - "oldfieldname": "company", - "oldfieldtype": "Link", - "options": "Company", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 1, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, - "width": "150px" - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Today", - "fieldname": "transaction_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Date", - "length": 0, - "no_copy": 1, - "oldfieldname": "transaction_date", - "oldfieldtype": "Date", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0, - "width": "100px" - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "valid_till", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Valid Till", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Sales", - "fieldname": "order_type", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 1, - "label": "Order Type", - "length": 0, - "no_copy": 0, - "oldfieldname": "order_type", - "oldfieldtype": "Select", - "options": "\nSales\nMaintenance\nShopping Cart", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "collapsible_depends_on": "", - "columns": 0, - "depends_on": "party_name", - "fieldname": "contact_section", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Address and Contact", - "length": 0, - "no_copy": 0, - "options": "fa fa-bullhorn", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "customer_address", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Customer Address", - "length": 0, - "no_copy": 0, - "options": "Address", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "address_display", - "fieldtype": "Small Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Address", - "length": 0, - "no_copy": 0, - "oldfieldname": "customer_address", - "oldfieldtype": "Small Text", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "fieldname": "contact_person", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Contact Person", - "length": 0, - "no_copy": 0, - "oldfieldname": "contact_person", - "oldfieldtype": "Link", - "options": "Contact", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "contact_display", - "fieldtype": "Small Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 1, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Contact", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "contact_mobile", - "fieldtype": "Small Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Mobile No", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "contact_email", - "fieldtype": "Data", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Contact Email", - "length": 0, - "no_copy": 0, - "options": "Email", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.quotaion_to=='Customer' && doc.party_name", - "fieldname": "col_break98", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, - "width": "50%" - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "shipping_address_name", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Shipping Address", - "length": 0, - "no_copy": 0, - "options": "Address", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "shipping_address", - "fieldtype": "Small Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Shipping Address", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.quotaion_to=='Customer' && doc.party_name", - "description": "", - "fieldname": "customer_group", - "fieldtype": "Link", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Customer Group", - "length": 0, - "no_copy": 0, - "oldfieldname": "customer_group", - "oldfieldtype": "Link", - "options": "Customer Group", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "", - "fieldname": "territory", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Territory", - "length": 0, - "no_copy": 0, - "options": "Territory", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "columns": 0, - "fieldname": "currency_and_price_list", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Currency and Price List", - "length": 0, - "no_copy": 0, - "options": "fa fa-tag", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "currency", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Currency", - "length": 0, - "no_copy": 0, - "oldfieldname": "currency", - "oldfieldtype": "Select", - "options": "Currency", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, - "width": "100px" - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "Rate at which customer's currency is converted to company's base currency", - "fieldname": "conversion_rate", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Exchange Rate", - "length": 0, - "no_copy": 0, - "oldfieldname": "conversion_rate", - "oldfieldtype": "Currency", - "permlevel": 0, - "precision": "9", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, - "width": "100px" - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break2", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, - "width": "50%" - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "selling_price_list", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Price List", - "length": 0, - "no_copy": 0, - "oldfieldname": "price_list_name", - "oldfieldtype": "Select", - "options": "Price List", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, - "width": "100px" - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "price_list_currency", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Price List Currency", - "length": 0, - "no_copy": 0, - "options": "Currency", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "Rate at which Price list currency is converted to company's base currency", - "fieldname": "plc_conversion_rate", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Price List Exchange Rate", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "9", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "ignore_pricing_rule", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Ignore Pricing Rule", - "length": 0, - "no_copy": 1, - "permlevel": 1, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "items_section", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "", - "length": 0, - "no_copy": 0, - "oldfieldtype": "Section Break", - "options": "fa fa-shopping-cart", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 1, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "items", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Items", - "length": 0, - "no_copy": 0, - "oldfieldname": "quotation_details", - "oldfieldtype": "Table", - "options": "Quotation Item", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, - "width": "40px" - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "pricing_rule_details", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Pricing Rules", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "pricing_rules", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Pricing Rule Detail", - "length": 0, - "no_copy": 0, - "options": "Pricing Rule Detail", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "sec_break23", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "total_qty", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Total Quantity", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "base_total", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Total (Company Currency)", - "length": 0, - "no_copy": 0, - "options": "Company:company:default_currency", - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "base_net_total", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Net Total (Company Currency)", - "length": 0, - "no_copy": 0, - "oldfieldname": "net_total", - "oldfieldtype": "Currency", - "options": "Company:company:default_currency", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, - "width": "100px" - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_28", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "total", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Total", - "length": 0, - "no_copy": 0, - "options": "currency", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "net_total", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Net Total", - "length": 0, - "no_copy": 0, - "options": "currency", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "total_net_weight", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Total Net Weight", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "taxes_section", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Taxes and Charges", - "length": 0, - "no_copy": 0, - "oldfieldtype": "Section Break", - "options": "fa fa-money", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "tax_category", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Tax Category", - "length": 0, - "no_copy": 0, - "options": "Tax Category", - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_34", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "shipping_rule", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Shipping Rule", - "length": 0, - "no_copy": 0, - "oldfieldtype": "Button", - "options": "Shipping Rule", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_36", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "taxes_and_charges", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Sales Taxes and Charges Template", - "length": 0, - "no_copy": 0, - "oldfieldname": "charge", - "oldfieldtype": "Link", - "options": "Sales Taxes and Charges Template", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "taxes", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Sales Taxes and Charges", - "length": 0, - "no_copy": 0, - "oldfieldname": "other_charges", - "oldfieldtype": "Table", - "options": "Sales Taxes and Charges", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "columns": 0, - "fieldname": "sec_tax_breakup", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Tax Breakup", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "other_charges_calculation", - "fieldtype": "Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Taxes and Charges Calculation", - "length": 0, - "no_copy": 1, - "oldfieldtype": "HTML", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_39", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "base_total_taxes_and_charges", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Total Taxes and Charges (Company Currency)", - "length": 0, - "no_copy": 0, - "oldfieldname": "other_charges_total", - "oldfieldtype": "Currency", - "options": "Company:company:default_currency", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_42", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "total_taxes_and_charges", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Total Taxes and Charges", - "length": 0, - "no_copy": 0, - "options": "currency", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "collapsible_depends_on": "discount_amount", - "columns": 0, - "fieldname": "section_break_44", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Additional Discount and Coupon Code", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "coupon_code", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Coupon Code", - "length": 0, - "no_copy": 0, - "options": "Coupon Code", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "referral_sales_partner", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Referral Sales Partner", - "length": 0, - "no_copy": 0, - "options": "Sales Partner", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Grand Total", - "fieldname": "apply_discount_on", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Apply Additional Discount On", - "length": 0, - "no_copy": 0, - "options": "\nGrand Total\nNet Total", - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "base_discount_amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Additional Discount Amount (Company Currency)", - "length": 0, - "no_copy": 0, - "options": "Company:company:default_currency", - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_46", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "additional_discount_percentage", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Additional Discount Percentage", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "discount_amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Additional Discount Amount", - "length": 0, - "no_copy": 0, - "options": "currency", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "totals", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "", - "length": 0, - "no_copy": 0, - "oldfieldtype": "Section Break", - "options": "fa fa-money", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "base_grand_total", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Grand Total (Company Currency)", - "length": 0, - "no_copy": 0, - "oldfieldname": "grand_total", - "oldfieldtype": "Currency", - "options": "Company:company:default_currency", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, - "width": "200px" - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "base_rounding_adjustment", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Rounding Adjustment (Company Currency)", - "length": 0, - "no_copy": 1, - "options": "Company:company:default_currency", - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "In Words will be visible once you save the Quotation.", - "fieldname": "base_in_words", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "In Words (Company Currency)", - "length": 0, - "no_copy": 0, - "oldfieldname": "in_words", - "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, - "width": "200px" - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "base_rounded_total", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Rounded Total (Company Currency)", - "length": 0, - "no_copy": 0, - "oldfieldname": "rounded_total", - "oldfieldtype": "Currency", - "options": "Company:company:default_currency", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, - "width": "200px" - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break3", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "oldfieldtype": "Column Break", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, - "width": "50%" - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "grand_total", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Grand Total", - "length": 0, - "no_copy": 0, - "oldfieldname": "grand_total_export", - "oldfieldtype": "Currency", - "options": "currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, - "width": "200px" - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "rounding_adjustment", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Rounding Adjustment", - "length": 0, - "no_copy": 1, - "options": "currency", - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 1, - "collapsible": 0, - "columns": 0, - "fieldname": "rounded_total", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Rounded Total", - "length": 0, - "no_copy": 0, - "oldfieldname": "rounded_total_export", - "oldfieldtype": "Currency", - "options": "currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, - "width": "200px" - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "in_words", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "In Words", - "length": 0, - "no_copy": 0, - "oldfieldname": "in_words_export", - "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, - "width": "200px" - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": "", - "columns": 0, - "depends_on": "", - "fieldname": "payment_schedule_section", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Payment Terms", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "payment_terms_template", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Payment Terms Template", - "length": 0, - "no_copy": 0, - "options": "Payment Terms Template", - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "payment_schedule", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Payment Schedule", - "length": 0, - "no_copy": 1, - "options": "Payment Schedule", - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "collapsible_depends_on": "terms", - "columns": 0, - "fieldname": "terms_section_break", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Terms and Conditions", - "length": 0, - "no_copy": 0, - "oldfieldtype": "Section Break", - "options": "fa fa-legal", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "tc_name", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Terms", - "length": 0, - "no_copy": 0, - "oldfieldname": "tc_name", - "oldfieldtype": "Link", - "options": "Terms and Conditions", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 1, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "terms", - "fieldtype": "Text Editor", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Term Details", - "length": 0, - "no_copy": 0, - "oldfieldname": "terms", - "oldfieldtype": "Text Editor", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "columns": 0, - "fieldname": "print_settings", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Print Settings", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "letter_head", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Letter Head", - "length": 0, - "no_copy": 0, - "oldfieldname": "letter_head", - "oldfieldtype": "Select", - "options": "Letter Head", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "group_same_items", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Group same items", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_73", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "select_print_heading", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Print Heading", - "length": 0, - "no_copy": 1, - "oldfieldname": "select_print_heading", - "oldfieldtype": "Link", - "options": "Print Heading", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 1, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "language", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Print Language", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "subscription_section", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Auto Repeat Section", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "auto_repeat", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Auto Repeat", - "length": 0, - "no_copy": 1, - "options": "Auto Repeat", - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval: doc.auto_repeat", - "fieldname": "update_auto_repeat_reference", - "fieldtype": "Button", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Update Auto Repeat Reference", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "columns": 0, - "fieldname": "more_info", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "More Information", - "length": 0, - "no_copy": 0, - "oldfieldtype": "Section Break", - "options": "fa fa-file-text", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "campaign", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Campaign", - "length": 0, - "no_copy": 0, - "oldfieldname": "campaign", - "oldfieldtype": "Link", - "options": "Campaign", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "source", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Source", - "length": 0, - "no_copy": 0, - "oldfieldname": "source", - "oldfieldtype": "Select", - "options": "Lead Source", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.status===\"Lost\"", - "fieldname": "order_lost_reason", - "fieldtype": "Small Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Detailed Reason", - "length": 0, - "no_copy": 1, - "oldfieldname": "order_lost_reason", - "oldfieldtype": "Small Text", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break4", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "oldfieldtype": "Column Break", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, - "width": "50%" - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Draft", - "fieldname": "status", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Status", - "length": 0, - "no_copy": 1, - "oldfieldname": "status", - "oldfieldtype": "Select", - "options": "Draft\nOpen\nReplied\nOrdered\nLost\nCancelled", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "enq_det", - "fieldtype": "Text", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Opportunity Item", - "length": 0, - "no_copy": 0, - "oldfieldname": "enq_det", - "oldfieldtype": "Text", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "supplier_quotation", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Supplier Quotation", - "length": 0, - "no_copy": 0, - "options": "Supplier Quotation", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "opportunity", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Opportunity", - "length": 0, - "no_copy": 0, - "options": "Opportunity", - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "lost_reasons", - "fieldtype": "Table MultiSelect", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Lost Reasons", - "length": 0, - "no_copy": 0, - "options": "Lost Reason Detail", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "icon": "fa fa-shopping-cart", - "idx": 82, - "image_view": 0, - "in_create": 0, - "is_submittable": 1, - "issingle": 0, - "istable": 0, - "max_attachments": 1, - "menu_index": 0, - "modified": "2019-10-14 01:00:21.545591", - "modified_by": "Administrator", - "module": "Selling", - "name": "Quotation", - "owner": "Administrator", - "permissions": [ - { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Sales User", - "set_user_permissions": 0, - "share": 1, - "submit": 1, - "write": 1 - }, - { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, - "match": "", - "permlevel": 1, - "print": 0, - "read": 1, - "report": 1, - "role": "Sales User", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 - }, - { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, - "match": "", - "permlevel": 1, - "print": 0, - "read": 1, - "report": 1, - "role": "Sales Manager", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 1 - }, - { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 1, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Sales Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 1, - "write": 1 - }, - { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Maintenance Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 1, - "write": 1 - }, - { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, - "match": "", - "permlevel": 1, - "print": 0, - "read": 1, - "report": 1, - "role": "Maintenance Manager", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 - }, - { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Maintenance User", - "set_user_permissions": 0, - "share": 1, - "submit": 1, - "write": 1 - }, - { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, - "match": "", - "permlevel": 1, - "print": 0, - "read": 1, - "report": 1, - "role": "Maintenance User", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 - } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 1, - "search_fields": "status,transaction_date,party_name,order_type", - "show_name_in_global_search": 1, - "sort_field": "modified", - "sort_order": "DESC", - "timeline_field": "party_name", - "title_field": "title", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 + "allow_import": 1, + "autoname": "naming_series:", + "creation": "2013-05-24 19:29:08", + "doctype": "DocType", + "document_type": "Document", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "customer_section", + "title", + "naming_series", + "quotation_to", + "party_name", + "customer_name", + "column_break1", + "amended_from", + "company", + "transaction_date", + "valid_till", + "order_type", + "contact_section", + "customer_address", + "address_display", + "contact_person", + "contact_display", + "contact_mobile", + "contact_email", + "col_break98", + "shipping_address_name", + "shipping_address", + "customer_group", + "territory", + "currency_and_price_list", + "currency", + "conversion_rate", + "column_break2", + "selling_price_list", + "price_list_currency", + "plc_conversion_rate", + "ignore_pricing_rule", + "items_section", + "items", + "pricing_rule_details", + "pricing_rules", + "sec_break23", + "total_qty", + "base_total", + "base_net_total", + "column_break_28", + "total", + "net_total", + "total_net_weight", + "taxes_section", + "tax_category", + "column_break_34", + "shipping_rule", + "section_break_36", + "taxes_and_charges", + "taxes", + "sec_tax_breakup", + "other_charges_calculation", + "section_break_39", + "base_total_taxes_and_charges", + "column_break_42", + "total_taxes_and_charges", + "section_break_44", + "coupon_code", + "referral_sales_partner", + "apply_discount_on", + "base_discount_amount", + "column_break_46", + "additional_discount_percentage", + "discount_amount", + "totals", + "base_grand_total", + "base_rounding_adjustment", + "base_in_words", + "base_rounded_total", + "column_break3", + "grand_total", + "rounding_adjustment", + "rounded_total", + "in_words", + "payment_schedule_section", + "payment_terms_template", + "payment_schedule", + "terms_section_break", + "tc_name", + "terms", + "print_settings", + "letter_head", + "group_same_items", + "column_break_73", + "select_print_heading", + "language", + "subscription_section", + "auto_repeat", + "update_auto_repeat_reference", + "more_info", + "campaign", + "source", + "order_lost_reason", + "column_break4", + "status", + "enq_det", + "supplier_quotation", + "opportunity", + "lost_reasons" + ], + "fields": [ + { + "fieldname": "customer_section", + "fieldtype": "Section Break", + "options": "fa fa-user" + }, + { + "allow_on_submit": 1, + "default": "{customer_name}", + "fieldname": "title", + "fieldtype": "Data", + "hidden": 1, + "label": "Title", + "no_copy": 1, + "print_hide": 1 + }, + { + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Series", + "no_copy": 1, + "oldfieldname": "naming_series", + "oldfieldtype": "Select", + "options": "SAL-QTN-.YYYY.-", + "print_hide": 1, + "reqd": 1, + "set_only_once": 1 + }, + { + "default": "Customer", + "fieldname": "quotation_to", + "fieldtype": "Link", + "in_standard_filter": 1, + "label": "Quotation To", + "oldfieldname": "quotation_to", + "oldfieldtype": "Select", + "options": "DocType", + "print_hide": 1, + "reqd": 1 + }, + { + "bold": 1, + "fieldname": "party_name", + "fieldtype": "Dynamic Link", + "in_global_search": 1, + "in_standard_filter": 1, + "label": "Party", + "oldfieldname": "customer", + "oldfieldtype": "Link", + "options": "quotation_to", + "print_hide": 1, + "search_index": 1 + }, + { + "bold": 1, + "fieldname": "customer_name", + "fieldtype": "Data", + "hidden": 1, + "in_global_search": 1, + "label": "Customer Name", + "read_only": 1 + }, + { + "fieldname": "column_break1", + "fieldtype": "Column Break", + "oldfieldtype": "Column Break", + "width": "50%" + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Amended From", + "no_copy": 1, + "oldfieldname": "amended_from", + "oldfieldtype": "Data", + "options": "Quotation", + "print_hide": 1, + "read_only": 1, + "width": "150px" + }, + { + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "oldfieldname": "company", + "oldfieldtype": "Link", + "options": "Company", + "print_hide": 1, + "remember_last_selected_value": 1, + "reqd": 1, + "width": "150px" + }, + { + "default": "Today", + "fieldname": "transaction_date", + "fieldtype": "Date", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Date", + "no_copy": 1, + "oldfieldname": "transaction_date", + "oldfieldtype": "Date", + "reqd": 1, + "search_index": 1, + "width": "100px" + }, + { + "fieldname": "valid_till", + "fieldtype": "Date", + "label": "Valid Till" + }, + { + "default": "Sales", + "fieldname": "order_type", + "fieldtype": "Select", + "in_standard_filter": 1, + "label": "Order Type", + "oldfieldname": "order_type", + "oldfieldtype": "Select", + "options": "\nSales\nMaintenance\nShopping Cart", + "print_hide": 1, + "reqd": 1 + }, + { + "collapsible": 1, + "depends_on": "party_name", + "fieldname": "contact_section", + "fieldtype": "Section Break", + "label": "Address and Contact", + "options": "fa fa-bullhorn" + }, + { + "fieldname": "customer_address", + "fieldtype": "Link", + "label": "Customer Address", + "options": "Address", + "print_hide": 1 + }, + { + "fieldname": "address_display", + "fieldtype": "Small Text", + "label": "Address", + "oldfieldname": "customer_address", + "oldfieldtype": "Small Text", + "read_only": 1 + }, + { + "fieldname": "contact_person", + "fieldtype": "Link", + "label": "Contact Person", + "oldfieldname": "contact_person", + "oldfieldtype": "Link", + "options": "Contact", + "print_hide": 1 + }, + { + "fieldname": "contact_display", + "fieldtype": "Small Text", + "in_global_search": 1, + "label": "Contact", + "read_only": 1 + }, + { + "fieldname": "contact_mobile", + "fieldtype": "Small Text", + "label": "Mobile No", + "read_only": 1 + }, + { + "fieldname": "contact_email", + "fieldtype": "Data", + "hidden": 1, + "label": "Contact Email", + "options": "Email", + "print_hide": 1, + "read_only": 1 + }, + { + "depends_on": "eval:doc.quotaion_to=='Customer' && doc.party_name", + "fieldname": "col_break98", + "fieldtype": "Column Break", + "width": "50%" + }, + { + "fieldname": "shipping_address_name", + "fieldtype": "Link", + "label": "Shipping Address", + "options": "Address", + "print_hide": 1 + }, + { + "fieldname": "shipping_address", + "fieldtype": "Small Text", + "label": "Shipping Address", + "print_hide": 1, + "read_only": 1 + }, + { + "depends_on": "eval:doc.quotaion_to=='Customer' && doc.party_name", + "fieldname": "customer_group", + "fieldtype": "Link", + "hidden": 1, + "label": "Customer Group", + "oldfieldname": "customer_group", + "oldfieldtype": "Link", + "options": "Customer Group", + "print_hide": 1 + }, + { + "fieldname": "territory", + "fieldtype": "Link", + "label": "Territory", + "options": "Territory", + "print_hide": 1 + }, + { + "collapsible": 1, + "fieldname": "currency_and_price_list", + "fieldtype": "Section Break", + "label": "Currency and Price List", + "options": "fa fa-tag" + }, + { + "fieldname": "currency", + "fieldtype": "Link", + "label": "Currency", + "oldfieldname": "currency", + "oldfieldtype": "Select", + "options": "Currency", + "print_hide": 1, + "reqd": 1, + "width": "100px" + }, + { + "description": "Rate at which customer's currency is converted to company's base currency", + "fieldname": "conversion_rate", + "fieldtype": "Float", + "label": "Exchange Rate", + "oldfieldname": "conversion_rate", + "oldfieldtype": "Currency", + "precision": "9", + "print_hide": 1, + "reqd": 1, + "width": "100px" + }, + { + "fieldname": "column_break2", + "fieldtype": "Column Break", + "width": "50%" + }, + { + "fieldname": "selling_price_list", + "fieldtype": "Link", + "label": "Price List", + "oldfieldname": "price_list_name", + "oldfieldtype": "Select", + "options": "Price List", + "print_hide": 1, + "reqd": 1, + "width": "100px" + }, + { + "fieldname": "price_list_currency", + "fieldtype": "Link", + "label": "Price List Currency", + "options": "Currency", + "print_hide": 1, + "read_only": 1, + "reqd": 1 + }, + { + "description": "Rate at which Price list currency is converted to company's base currency", + "fieldname": "plc_conversion_rate", + "fieldtype": "Float", + "label": "Price List Exchange Rate", + "precision": "9", + "print_hide": 1, + "reqd": 1 + }, + { + "default": "0", + "fieldname": "ignore_pricing_rule", + "fieldtype": "Check", + "label": "Ignore Pricing Rule", + "no_copy": 1, + "permlevel": 1, + "print_hide": 1 + }, + { + "fieldname": "items_section", + "fieldtype": "Section Break", + "oldfieldtype": "Section Break", + "options": "fa fa-shopping-cart" + }, + { + "allow_bulk_edit": 1, + "fieldname": "items", + "fieldtype": "Table", + "label": "Items", + "oldfieldname": "quotation_details", + "oldfieldtype": "Table", + "options": "Quotation Item", + "reqd": 1, + "width": "40px" + }, + { + "fieldname": "pricing_rule_details", + "fieldtype": "Section Break", + "label": "Pricing Rules" + }, + { + "fieldname": "pricing_rules", + "fieldtype": "Table", + "label": "Pricing Rule Detail", + "options": "Pricing Rule Detail", + "read_only": 1 + }, + { + "fieldname": "sec_break23", + "fieldtype": "Section Break" + }, + { + "fieldname": "total_qty", + "fieldtype": "Float", + "label": "Total Quantity", + "read_only": 1 + }, + { + "fieldname": "base_total", + "fieldtype": "Currency", + "label": "Total (Company Currency)", + "options": "Company:company:default_currency", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "base_net_total", + "fieldtype": "Currency", + "label": "Net Total (Company Currency)", + "oldfieldname": "net_total", + "oldfieldtype": "Currency", + "options": "Company:company:default_currency", + "print_hide": 1, + "read_only": 1, + "width": "100px" + }, + { + "fieldname": "column_break_28", + "fieldtype": "Column Break" + }, + { + "fieldname": "total", + "fieldtype": "Currency", + "label": "Total", + "options": "currency", + "read_only": 1 + }, + { + "fieldname": "net_total", + "fieldtype": "Currency", + "label": "Net Total", + "options": "currency", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "total_net_weight", + "fieldtype": "Float", + "label": "Total Net Weight", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "taxes_section", + "fieldtype": "Section Break", + "label": "Taxes and Charges", + "oldfieldtype": "Section Break", + "options": "fa fa-money" + }, + { + "fieldname": "tax_category", + "fieldtype": "Link", + "label": "Tax Category", + "options": "Tax Category", + "print_hide": 1 + }, + { + "fieldname": "column_break_34", + "fieldtype": "Column Break" + }, + { + "fieldname": "shipping_rule", + "fieldtype": "Link", + "label": "Shipping Rule", + "oldfieldtype": "Button", + "options": "Shipping Rule", + "print_hide": 1 + }, + { + "fieldname": "section_break_36", + "fieldtype": "Section Break" + }, + { + "fieldname": "taxes_and_charges", + "fieldtype": "Link", + "label": "Sales Taxes and Charges Template", + "oldfieldname": "charge", + "oldfieldtype": "Link", + "options": "Sales Taxes and Charges Template", + "print_hide": 1 + }, + { + "fieldname": "taxes", + "fieldtype": "Table", + "label": "Sales Taxes and Charges", + "oldfieldname": "other_charges", + "oldfieldtype": "Table", + "options": "Sales Taxes and Charges" + }, + { + "collapsible": 1, + "fieldname": "sec_tax_breakup", + "fieldtype": "Section Break", + "label": "Tax Breakup" + }, + { + "fieldname": "other_charges_calculation", + "fieldtype": "Text", + "label": "Taxes and Charges Calculation", + "no_copy": 1, + "oldfieldtype": "HTML", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "section_break_39", + "fieldtype": "Section Break" + }, + { + "fieldname": "base_total_taxes_and_charges", + "fieldtype": "Currency", + "label": "Total Taxes and Charges (Company Currency)", + "oldfieldname": "other_charges_total", + "oldfieldtype": "Currency", + "options": "Company:company:default_currency", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "column_break_42", + "fieldtype": "Column Break" + }, + { + "fieldname": "total_taxes_and_charges", + "fieldtype": "Currency", + "label": "Total Taxes and Charges", + "options": "currency", + "print_hide": 1, + "read_only": 1 + }, + { + "collapsible": 1, + "collapsible_depends_on": "discount_amount", + "fieldname": "section_break_44", + "fieldtype": "Section Break", + "label": "Additional Discount and Coupon Code" + }, + { + "fieldname": "coupon_code", + "fieldtype": "Link", + "label": "Coupon Code", + "options": "Coupon Code" + }, + { + "fieldname": "referral_sales_partner", + "fieldtype": "Link", + "label": "Referral Sales Partner", + "options": "Sales Partner" + }, + { + "default": "Grand Total", + "fieldname": "apply_discount_on", + "fieldtype": "Select", + "label": "Apply Additional Discount On", + "options": "\nGrand Total\nNet Total", + "print_hide": 1 + }, + { + "fieldname": "base_discount_amount", + "fieldtype": "Currency", + "label": "Additional Discount Amount (Company Currency)", + "options": "Company:company:default_currency", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "column_break_46", + "fieldtype": "Column Break" + }, + { + "fieldname": "additional_discount_percentage", + "fieldtype": "Float", + "label": "Additional Discount Percentage", + "print_hide": 1 + }, + { + "fieldname": "discount_amount", + "fieldtype": "Currency", + "label": "Additional Discount Amount", + "options": "currency", + "print_hide": 1 + }, + { + "fieldname": "totals", + "fieldtype": "Section Break", + "oldfieldtype": "Section Break", + "options": "fa fa-money", + "print_hide": 1 + }, + { + "fieldname": "base_grand_total", + "fieldtype": "Currency", + "label": "Grand Total (Company Currency)", + "oldfieldname": "grand_total", + "oldfieldtype": "Currency", + "options": "Company:company:default_currency", + "print_hide": 1, + "read_only": 1, + "width": "200px" + }, + { + "fieldname": "base_rounding_adjustment", + "fieldtype": "Currency", + "label": "Rounding Adjustment (Company Currency)", + "no_copy": 1, + "options": "Company:company:default_currency", + "print_hide": 1, + "read_only": 1 + }, + { + "description": "In Words will be visible once you save the Quotation.", + "fieldname": "base_in_words", + "fieldtype": "Data", + "label": "In Words (Company Currency)", + "oldfieldname": "in_words", + "oldfieldtype": "Data", + "print_hide": 1, + "read_only": 1, + "width": "200px" + }, + { + "fieldname": "base_rounded_total", + "fieldtype": "Currency", + "label": "Rounded Total (Company Currency)", + "oldfieldname": "rounded_total", + "oldfieldtype": "Currency", + "options": "Company:company:default_currency", + "print_hide": 1, + "read_only": 1, + "width": "200px" + }, + { + "fieldname": "column_break3", + "fieldtype": "Column Break", + "oldfieldtype": "Column Break", + "print_hide": 1, + "width": "50%" + }, + { + "fieldname": "grand_total", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Grand Total", + "oldfieldname": "grand_total_export", + "oldfieldtype": "Currency", + "options": "currency", + "read_only": 1, + "width": "200px" + }, + { + "fieldname": "rounding_adjustment", + "fieldtype": "Currency", + "label": "Rounding Adjustment", + "no_copy": 1, + "options": "currency", + "print_hide": 1, + "read_only": 1 + }, + { + "bold": 1, + "fieldname": "rounded_total", + "fieldtype": "Currency", + "label": "Rounded Total", + "oldfieldname": "rounded_total_export", + "oldfieldtype": "Currency", + "options": "currency", + "read_only": 1, + "width": "200px" + }, + { + "fieldname": "in_words", + "fieldtype": "Data", + "label": "In Words", + "oldfieldname": "in_words_export", + "oldfieldtype": "Data", + "print_hide": 1, + "read_only": 1, + "width": "200px" + }, + { + "fieldname": "payment_schedule_section", + "fieldtype": "Section Break", + "label": "Payment Terms" + }, + { + "fieldname": "payment_terms_template", + "fieldtype": "Link", + "label": "Payment Terms Template", + "options": "Payment Terms Template", + "print_hide": 1 + }, + { + "fieldname": "payment_schedule", + "fieldtype": "Table", + "label": "Payment Schedule", + "no_copy": 1, + "options": "Payment Schedule", + "print_hide": 1 + }, + { + "collapsible": 1, + "collapsible_depends_on": "terms", + "fieldname": "terms_section_break", + "fieldtype": "Section Break", + "label": "Terms and Conditions", + "oldfieldtype": "Section Break", + "options": "fa fa-legal" + }, + { + "fieldname": "tc_name", + "fieldtype": "Link", + "label": "Terms", + "oldfieldname": "tc_name", + "oldfieldtype": "Link", + "options": "Terms and Conditions", + "print_hide": 1, + "report_hide": 1 + }, + { + "fieldname": "terms", + "fieldtype": "Text Editor", + "label": "Term Details", + "oldfieldname": "terms", + "oldfieldtype": "Text Editor" + }, + { + "collapsible": 1, + "fieldname": "print_settings", + "fieldtype": "Section Break", + "label": "Print Settings" + }, + { + "allow_on_submit": 1, + "fieldname": "letter_head", + "fieldtype": "Link", + "label": "Letter Head", + "oldfieldname": "letter_head", + "oldfieldtype": "Select", + "options": "Letter Head", + "print_hide": 1 + }, + { + "allow_on_submit": 1, + "default": "0", + "fieldname": "group_same_items", + "fieldtype": "Check", + "label": "Group same items", + "print_hide": 1 + }, + { + "fieldname": "column_break_73", + "fieldtype": "Column Break" + }, + { + "allow_on_submit": 1, + "fieldname": "select_print_heading", + "fieldtype": "Link", + "label": "Print Heading", + "no_copy": 1, + "oldfieldname": "select_print_heading", + "oldfieldtype": "Link", + "options": "Print Heading", + "print_hide": 1, + "report_hide": 1 + }, + { + "fieldname": "language", + "fieldtype": "Data", + "label": "Print Language", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "subscription_section", + "fieldtype": "Section Break", + "label": "Auto Repeat Section" + }, + { + "fieldname": "auto_repeat", + "fieldtype": "Link", + "label": "Auto Repeat", + "no_copy": 1, + "options": "Auto Repeat", + "print_hide": 1, + "read_only": 1 + }, + { + "allow_on_submit": 1, + "depends_on": "eval: doc.auto_repeat", + "fieldname": "update_auto_repeat_reference", + "fieldtype": "Button", + "label": "Update Auto Repeat Reference" + }, + { + "collapsible": 1, + "fieldname": "more_info", + "fieldtype": "Section Break", + "label": "More Information", + "oldfieldtype": "Section Break", + "options": "fa fa-file-text", + "print_hide": 1 + }, + { + "fieldname": "campaign", + "fieldtype": "Link", + "label": "Campaign", + "oldfieldname": "campaign", + "oldfieldtype": "Link", + "options": "Campaign", + "print_hide": 1 + }, + { + "fieldname": "source", + "fieldtype": "Link", + "label": "Source", + "oldfieldname": "source", + "oldfieldtype": "Select", + "options": "Lead Source", + "print_hide": 1 + }, + { + "allow_on_submit": 1, + "depends_on": "eval:doc.status===\"Lost\"", + "fieldname": "order_lost_reason", + "fieldtype": "Small Text", + "label": "Detailed Reason", + "no_copy": 1, + "oldfieldname": "order_lost_reason", + "oldfieldtype": "Small Text", + "print_hide": 1 + }, + { + "fieldname": "column_break4", + "fieldtype": "Column Break", + "oldfieldtype": "Column Break", + "print_hide": 1, + "width": "50%" + }, + { + "default": "Draft", + "fieldname": "status", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Status", + "no_copy": 1, + "oldfieldname": "status", + "oldfieldtype": "Select", + "options": "Draft\nOpen\nReplied\nOrdered\nLost\nCancelled\nExpired", + "print_hide": 1, + "read_only": 1, + "reqd": 1 + }, + { + "fieldname": "enq_det", + "fieldtype": "Text", + "hidden": 1, + "label": "Opportunity Item", + "oldfieldname": "enq_det", + "oldfieldtype": "Text", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "supplier_quotation", + "fieldtype": "Link", + "label": "Supplier Quotation", + "options": "Supplier Quotation" + }, + { + "fieldname": "opportunity", + "fieldtype": "Link", + "label": "Opportunity", + "options": "Opportunity", + "print_hide": 1, + "read_only": 1 + }, + { + "allow_on_submit": 1, + "fieldname": "lost_reasons", + "fieldtype": "Table MultiSelect", + "label": "Lost Reasons", + "options": "Lost Reason Detail", + "read_only": 1 } + ], + "icon": "fa fa-shopping-cart", + "idx": 82, + "is_submittable": 1, + "max_attachments": 1, + "modified": "2019-11-12 13:19:11.895715", + "modified_by": "Administrator", + "module": "Selling", + "name": "Quotation", + "owner": "Administrator", + "permissions": [ + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Sales User", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "permlevel": 1, + "read": 1, + "report": 1, + "role": "Sales User" + }, + { + "permlevel": 1, + "read": 1, + "report": 1, + "role": "Sales Manager", + "write": 1 + }, + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "import": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Sales Manager", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Maintenance Manager", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "permlevel": 1, + "read": 1, + "report": 1, + "role": "Maintenance Manager" + }, + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Maintenance User", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "permlevel": 1, + "read": 1, + "report": 1, + "role": "Maintenance User" + } + ], + "search_fields": "status,transaction_date,party_name,order_type", + "show_name_in_global_search": 1, + "sort_field": "modified", + "sort_order": "DESC", + "timeline_field": "party_name", + "title_field": "title" +} \ No newline at end of file diff --git a/erpnext/selling/doctype/quotation/quotation.py b/erpnext/selling/doctype/quotation/quotation.py index 4a56e404000..488331a8102 100644 --- a/erpnext/selling/doctype/quotation/quotation.py +++ b/erpnext/selling/doctype/quotation/quotation.py @@ -67,7 +67,6 @@ class Quotation(SellingController): opportunity = self.opportunity opp = frappe.get_doc("Opportunity", opportunity) - opp.status = None opp.set_status(update=True) def declare_enquiry_lost(self, lost_reasons_list, detailed_reason=None): @@ -185,6 +184,10 @@ def _make_sales_order(source_name, target_doc=None, ignore_permissions=False): return doclist +def set_expired_status(): + frappe.db.sql("""UPDATE `tabQuotation` SET `status` = 'Expired' + WHERE `status` != "Expired" AND `valid_till` < %s""", (nowdate())) + @frappe.whitelist() def make_sales_invoice(source_name, target_doc=None): return _make_sales_invoice(source_name, target_doc) diff --git a/erpnext/selling/doctype/quotation/quotation_list.js b/erpnext/selling/doctype/quotation/quotation_list.js index 5f4e2546fbc..802c0ba641d 100644 --- a/erpnext/selling/doctype/quotation/quotation_list.js +++ b/erpnext/selling/doctype/quotation/quotation_list.js @@ -14,15 +14,13 @@ frappe.listview_settings['Quotation'] = { get_indicator: function(doc) { if(doc.status==="Open") { - if (doc.valid_till && doc.valid_till < frappe.datetime.nowdate()) { - return [__("Expired"), "darkgrey", "valid_till,<," + frappe.datetime.nowdate()]; - } else { - return [__("Open"), "orange", "status,=,Open"]; - } + return [__("Open"), "orange", "status,=,Open"]; } else if(doc.status==="Ordered") { return [__("Ordered"), "green", "status,=,Ordered"]; } else if(doc.status==="Lost") { return [__("Lost"), "darkgrey", "status,=,Lost"]; + } else if(doc.status==="Expired") { + return [__("Expired"), "darkgrey", "status,=,Expired"]; } } }; diff --git a/erpnext/selling/doctype/quotation/test_quotation.py b/erpnext/selling/doctype/quotation/test_quotation.py index 7ee4a76ca66..ee6b429ccae 100644 --- a/erpnext/selling/doctype/quotation/test_quotation.py +++ b/erpnext/selling/doctype/quotation/test_quotation.py @@ -201,6 +201,28 @@ class TestQuotation(unittest.TestCase): sec_qo = make_quotation(item_list=qo_item2, do_not_submit=True) sec_qo.submit() + def test_quotation_expiry(self): + from erpnext.selling.doctype.quotation.quotation import set_expired_status + + quotation_item = [ + { + "item_code": "_Test Item", + "warehouse":"", + "qty": 1, + "rate": 500 + } + ] + + yesterday = add_days(nowdate(), -1) + expired_quotation = make_quotation(item_list=quotation_item, transaction_date=yesterday, do_not_submit=True) + expired_quotation.valid_till = yesterday + expired_quotation.save() + expired_quotation.submit() + set_expired_status() + expired_quotation.reload() + self.assertEqual(expired_quotation.status, "Expired") + + test_records = frappe.get_test_records('Quotation') def get_quotation_dict(party_name=None, item_code=None): @@ -258,3 +280,5 @@ def make_quotation(**args): qo.submit() return qo + + diff --git a/erpnext/selling/doctype/sales_order/regional/india.js b/erpnext/selling/doctype/sales_order/regional/india.js new file mode 100644 index 00000000000..c11cfcc50b7 --- /dev/null +++ b/erpnext/selling/doctype/sales_order/regional/india.js @@ -0,0 +1,3 @@ +{% include "erpnext/regional/india/taxes.js" %} + +erpnext.setup_auto_gst_taxation('Sales Order'); diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js index 85e81436d16..2dae0d8063d 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.js +++ b/erpnext/selling/doctype/sales_order/sales_order.js @@ -112,7 +112,6 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend( let allow_delivery = false; if (doc.docstatus==1) { - this.frm.add_custom_button(__('Pick List'), () => this.create_pick_list(), __('Create')); if(this.frm.has_perm("submit")) { if(doc.status === 'On Hold') { @@ -136,7 +135,7 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend( if(doc.status !== 'Closed') { if(doc.status !== 'On Hold') { - allow_delivery = this.frm.doc.items.some(item => item.delivered_by_supplier === 0 && item.qty > flt(item.delivered_qty)) + allow_delivery = this.frm.doc.items.some(item => item.delivered_by_supplier === 0 && item.qty > flt(item.delivered_qty)) && !this.frm.doc.skip_delivery_note if (this.frm.has_perm("submit")) { @@ -148,6 +147,8 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend( } } + this.frm.add_custom_button(__('Pick List'), () => this.create_pick_list(), __('Create')); + // delivery note if(flt(doc.per_delivered, 6) < 100 && ["Sales", "Shopping Cart"].indexOf(doc.order_type)!==-1 && allow_delivery) { this.frm.add_custom_button(__('Delivery Note'), () => this.make_delivery_note_based_on_delivery_date(), __('Create')); @@ -202,7 +203,7 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend( } } // payment request - if(flt(doc.per_billed)==0) { + if(flt(doc.per_billed)<100) { this.frm.add_custom_button(__('Payment Request'), () => this.make_payment_request(), __('Create')); this.frm.add_custom_button(__('Payment'), () => this.make_payment_entry(), __('Create')); } @@ -361,7 +362,7 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend( }, toggle_delivery_date: function() { - this.frm.fields_dict.items.grid.toggle_reqd("delivery_date", + this.frm.fields_dict.items.grid.toggle_reqd("delivery_date", (this.frm.doc.order_type == "Sales" && !this.frm.doc.skip_delivery_note)); }, diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index e12b359bdf1..2112a4174b1 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -578,8 +578,12 @@ def make_delivery_note(source_name, target_doc=None, skip_item_mapping=False): target.run_method("set_po_nos") target.run_method("calculate_taxes_and_totals") - # set company address - target.update(get_company_address(target.company)) + if source.company_address: + target.update({'company_address': source.company_address}) + else: + # set company address + target.update(get_company_address(target.company)) + if target.company_address: target.update(get_fetch_values("Delivery Note", 'company_address', target.company_address)) @@ -645,8 +649,12 @@ def make_sales_invoice(source_name, target_doc=None, ignore_permissions=False): target.run_method("set_po_nos") target.run_method("calculate_taxes_and_totals") - # set company address - target.update(get_company_address(target.company)) + if source.company_address: + target.update({'company_address': source.company_address}) + else: + # set company address + target.update(get_company_address(target.company)) + if target.company_address: target.update(get_fetch_values("Sales Invoice", 'company_address', target.company_address)) @@ -834,6 +842,10 @@ def make_purchase_order(source_name, for_supplier=None, selected_items=[], targe for item in sales_order.items: if item.supplier and item.supplier not in suppliers: suppliers.append(item.supplier) + + if not suppliers: + frappe.throw(_("Please set a Supplier against the Items to be considered in the Purchase Order.")) + for supplier in suppliers: po =frappe.get_list("Purchase Order", filters={"sales_order":source_name, "supplier":supplier, "docstatus": ("<", "2")}) if len(po) == 0: diff --git a/erpnext/selling/doctype/sales_order_item/sales_order_item.json b/erpnext/selling/doctype/sales_order_item/sales_order_item.json index 3fd1e6461e1..317f284eff3 100644 --- a/erpnext/selling/doctype/sales_order_item/sales_order_item.json +++ b/erpnext/selling/doctype/sales_order_item/sales_order_item.json @@ -76,10 +76,12 @@ "ordered_qty", "planned_qty", "column_break_69", - "delivered_qty", "work_order_qty", + "delivered_qty", "produced_qty", "returned_qty", + "shopping_cart_section", + "additional_notes", "section_break_63", "page_break", "item_tax_rate", @@ -741,11 +743,22 @@ "fieldname": "image_section", "fieldtype": "Section Break", "label": "Image" + }, + { + "collapsible": 1, + "fieldname": "shopping_cart_section", + "fieldtype": "Section Break", + "label": "Shopping Cart" + }, + { + "fieldname": "additional_notes", + "fieldtype": "Text", + "label": "Additional Notes" } ], "idx": 1, "istable": 1, - "modified": "2019-10-10 08:46:26.244823", + "modified": "2019-12-11 18:06:26.238169", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order Item", diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.js b/erpnext/selling/page/point_of_sale/point_of_sale.js index d2c2d70dbe7..33fbc229b6e 100644 --- a/erpnext/selling/page/point_of_sale/point_of_sale.js +++ b/erpnext/selling/page/point_of_sale/point_of_sale.js @@ -286,14 +286,14 @@ erpnext.pos.PointOfSale = class PointOfSale { if (in_list(['serial_no', 'batch_no'], field)) { args[field] = value; } - + // add to cur_frm const item = this.frm.add_child('items', args); frappe.flags.hide_serial_batch_dialog = true; frappe.run_serially([ () => { - this.frm.script_manager.trigger('item_code', item.doctype, item.name) + return this.frm.script_manager.trigger('item_code', item.doctype, item.name) .then(() => { this.frm.script_manager.trigger('qty', item.doctype, item.name) .then(() => { @@ -451,7 +451,7 @@ erpnext.pos.PointOfSale = class PointOfSale { change_pos_profile() { return new Promise((resolve) => { - const on_submit = ({ pos_profile, set_as_default }) => { + const on_submit = ({ company, pos_profile, set_as_default }) => { if (pos_profile) { this.pos_profile = pos_profile; } @@ -461,7 +461,7 @@ erpnext.pos.PointOfSale = class PointOfSale { method: "erpnext.accounts.doctype.pos_profile.pos_profile.set_default_profile", args: { 'pos_profile': pos_profile, - 'company': this.frm.doc.company + 'company': company } }).then(() => { this.on_change_pos_profile(); @@ -471,7 +471,42 @@ erpnext.pos.PointOfSale = class PointOfSale { } } - frappe.prompt(this.get_prompt_fields(), + + let me = this; + + var dialog = frappe.prompt([{ + fieldtype: 'Link', + label: __('Company'), + options: 'Company', + fieldname: 'company', + default: me.frm.doc.company, + reqd: 1, + onchange: function(e) { + me.get_default_pos_profile(this.value).then((r) => { + dialog.set_value('pos_profile', (r && r.name)? r.name : ''); + }); + } + }, + { + fieldtype: 'Link', + label: __('POS Profile'), + options: 'POS Profile', + fieldname: 'pos_profile', + default: me.frm.doc.pos_profile, + reqd: 1, + get_query: () => { + return { + query: 'erpnext.accounts.doctype.pos_profile.pos_profile.pos_profile_query', + filters: { + company: dialog.get_value('company') + } + }; + } + }, { + fieldtype: 'Check', + label: __('Set as default'), + fieldname: 'set_as_default' + }], on_submit, __('Select POS Profile') ); @@ -494,26 +529,9 @@ erpnext.pos.PointOfSale = class PointOfSale { ]); } - get_prompt_fields() { - return [{ - fieldtype: 'Link', - label: __('POS Profile'), - options: 'POS Profile', - fieldname: 'pos_profile', - reqd: 1, - get_query: () => { - return { - query: 'erpnext.accounts.doctype.pos_profile.pos_profile.pos_profile_query', - filters: { - company: this.frm.doc.company - } - }; - } - }, { - fieldtype: 'Check', - label: __('Set as default'), - fieldname: 'set_as_default' - }]; + get_default_pos_profile(company) { + return frappe.xcall("erpnext.stock.get_item_details.get_pos_profile", + {'company': company}) } setup_company() { diff --git a/erpnext/selling/report/available_stock_for_packing_items/available_stock_for_packing_items.py b/erpnext/selling/report/available_stock_for_packing_items/available_stock_for_packing_items.py index 32711b2fce0..056492a3274 100644 --- a/erpnext/selling/report/available_stock_for_packing_items/available_stock_for_packing_items.py +++ b/erpnext/selling/report/available_stock_for_packing_items/available_stock_for_packing_items.py @@ -7,7 +7,7 @@ from frappe.utils import flt def execute(filters=None): if not filters: filters = {} - + columns = get_columns() iwq_map = get_item_warehouse_quantity_map() item_map = get_item_details() @@ -15,22 +15,23 @@ def execute(filters=None): for sbom, warehouse in iwq_map.items(): total = 0 total_qty = 0 - + for wh, item_qty in warehouse.items(): total += 1 - row = [sbom, item_map.get(sbom).item_name, item_map.get(sbom).description, - item_map.get(sbom).stock_uom, wh] - available_qty = item_qty - total_qty += flt(available_qty) - row += [available_qty] - - if available_qty: - data.append(row) - if (total == len(warehouse)): - row = ["", "", "Total", "", "", total_qty] + if item_map.get(sbom): + row = [sbom, item_map.get(sbom).item_name, item_map.get(sbom).description, + item_map.get(sbom).stock_uom, wh] + available_qty = item_qty + total_qty += flt(available_qty) + row += [available_qty] + + if available_qty: data.append(row) + if (total == len(warehouse)): + row = ["", "", "Total", "", "", total_qty] + data.append(row) return columns, data - + def get_columns(): columns = ["Item Code:Link/Item:100", "Item Name::100", "Description::120", \ "UOM:Link/UOM:80", "Warehouse:Link/Warehouse:100", "Quantity::100"] diff --git a/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.js b/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.js index ee806a78fb4..daca2e3bd0c 100644 --- a/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.js +++ b/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.js @@ -20,11 +20,15 @@ frappe.query_reports["Item-wise Sales History"] = { }, { fieldname:"from_date", + reqd: 1, label: __("From Date"), fieldtype: "Date", + default: frappe.datetime.add_months(frappe.datetime.get_today(), -1), }, { fieldname:"to_date", + reqd: 1, + default: frappe.datetime.get_today(), label: __("To Date"), fieldtype: "Date", }, diff --git a/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py b/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py index 226c34f735a..1fc3663bed7 100644 --- a/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py +++ b/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py @@ -196,6 +196,7 @@ def get_customer_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, @@ -208,7 +209,6 @@ def get_sales_order_details(company_list, filters): `tabSales Order` so, `tabSales Order Item` so_item WHERE so.name = so_item.parent - AND so.company in (%s) - AND so.docstatus = 1 - {0} - """.format(conditions), company_list, as_dict=1) #nosec + AND so.company in ({0}) + AND so.docstatus = 1 {1} + """.format(','.join(["%s"] * len(company_list)), conditions), tuple(company_list), as_dict=1) diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js index e508476576d..1c9b30b8289 100644 --- a/erpnext/selling/sales_common.js +++ b/erpnext/selling/sales_common.js @@ -309,7 +309,7 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({ child: item, args: { "batch_no": item.batch_no, - "stock_qty": item.stock_qty, + "stock_qty": item.stock_qty || item.qty, //if stock_qty field is not available fetch qty (in case of Packed Items table) "warehouse": item.warehouse, "item_code": item.item_code, "has_serial_no": has_serial_no diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js index 313de677fc5..be736d2d9d1 100644 --- a/erpnext/setup/doctype/company/company.js +++ b/erpnext/setup/doctype/company/company.js @@ -29,7 +29,8 @@ frappe.ui.form.on("Company", { company_name: function(frm) { if(frm.doc.__islocal) { - let parts = frm.doc.company_name.split(); + // add missing " " arg in split method + let parts = frm.doc.company_name.split(" "); let abbr = $.map(parts, function (p) { return p? p.substr(0, 1) : null; }).join(""); diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index 04e8a83e7f5..ff3515485c4 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -14,6 +14,9 @@ from frappe.model.document import Document from frappe.contacts.address_and_contact import load_address_and_contact from frappe.utils.nestedset import NestedSet +from past.builtins import cmp +import functools + class Company(NestedSet): nsm_parent_field = 'parent_company' @@ -560,3 +563,26 @@ def get_timeline_data(doctype, name): return json.loads(history) if history and '{' in history else {} return date_to_value_dict + +@frappe.whitelist() +def get_default_company_address(name, sort_key='is_primary_address', existing_address=None): + if sort_key not in ['is_shipping_address', 'is_primary_address']: + return None + + out = frappe.db.sql(""" SELECT + addr.name, addr.%s + FROM + `tabAddress` addr, `tabDynamic Link` dl + WHERE + dl.parent = addr.name and dl.link_doctype = 'Company' and + dl.link_name = %s and ifnull(addr.disabled, 0) = 0 + """ %(sort_key, '%s'), (name)) #nosec + + if existing_address: + if existing_address in [d[0] for d in out]: + return existing_address + + if out: + return sorted(out, key = functools.cmp_to_key(lambda x,y: cmp(y[1], x[1])))[0][0] + else: + return None \ No newline at end of file diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py index 5603f17a54b..22375ae22c3 100644 --- a/erpnext/setup/doctype/item_group/item_group.py +++ b/erpnext/setup/doctype/item_group/item_group.py @@ -39,6 +39,7 @@ class ItemGroup(NestedSet, WebsiteGenerator): invalidate_cache_for(self) self.validate_name_with_item() self.validate_one_root() + self.delete_child_item_groups_key() def make_route(self): '''Make website route''' @@ -58,6 +59,7 @@ class ItemGroup(NestedSet, WebsiteGenerator): def on_trash(self): NestedSet.on_trash(self) WebsiteGenerator.on_trash(self) + self.delete_child_item_groups_key() def validate_name_with_item(self): if frappe.db.exists("Item", self.name): @@ -83,6 +85,9 @@ class ItemGroup(NestedSet, WebsiteGenerator): return context + def delete_child_item_groups_key(self): + frappe.cache().hdel("child_item_groups", self.name) + @frappe.whitelist(allow_guest=True) def get_product_list_for_group(product_group=None, start=0, limit=10, search=None): if product_group: @@ -136,6 +141,7 @@ def get_child_groups_for_list_in_html(item_group, start, limit, search): fields = ['name', 'route', 'description', 'image'], filters = dict( show_in_website = 1, + parent_item_group = item_group.name, lft = ('>', item_group.lft), rgt = ('<', item_group.rgt), ), @@ -167,6 +173,19 @@ def get_child_groups(item_group_name): from `tabItem Group` where lft>=%(lft)s and rgt<=%(rgt)s and show_in_website = 1""", {"lft": item_group.lft, "rgt": item_group.rgt}) +def get_child_item_groups(item_group_name): + child_item_groups = frappe.cache().hget("child_item_groups", item_group_name) + + if not child_item_groups: + item_group = frappe.get_cached_doc("Item Group", item_group_name) + + child_item_groups = [d.name for d in frappe.get_all('Item Group', + filters= {'lft': ('>=', item_group.lft),'rgt': ('>=', item_group.rgt)})] + + frappe.cache().hset("child_item_groups", item_group_name, child_item_groups) + + return child_item_groups or {} + def get_item_for_list_in_html(context): # add missing absolute link in files # user may forget it during upload diff --git a/erpnext/shopping_cart/cart.py b/erpnext/shopping_cart/cart.py index 1236ade45f9..813d0dd196f 100644 --- a/erpnext/shopping_cart/cart.py +++ b/erpnext/shopping_cart/cart.py @@ -66,6 +66,7 @@ def place_order(): from erpnext.selling.doctype.quotation.quotation import _make_sales_order sales_order = frappe.get_doc(_make_sales_order(quotation.name, ignore_permissions=True)) + sales_order.payment_schedule = [] if not cint(cart_settings.allow_items_not_in_stock): for item in sales_order.get("items"): diff --git a/erpnext/stock/doctype/bin/bin.json b/erpnext/stock/doctype/bin/bin.json index e17429bc0be..04d624ec0b7 100644 --- a/erpnext/stock/doctype/bin/bin.json +++ b/erpnext/stock/doctype/bin/bin.json @@ -1,599 +1,200 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "MAT-BIN-.YYYY.-.#####", - "beta": 0, - "creation": "2013-01-10 16:34:25", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "editable_grid": 0, - "engine": "InnoDB", + "autoname": "MAT-BIN-.YYYY.-.#####", + "creation": "2013-01-10 16:34:25", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "warehouse", + "item_code", + "reserved_qty", + "actual_qty", + "ordered_qty", + "indented_qty", + "planned_qty", + "projected_qty", + "reserved_qty_for_production", + "reserved_qty_for_sub_contract", + "ma_rate", + "stock_uom", + "fcfs_rate", + "valuation_rate", + "stock_value" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "warehouse", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 1, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Warehouse", - "length": 0, - "no_copy": 0, - "oldfieldname": "warehouse", - "oldfieldtype": "Link", - "options": "Warehouse", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "warehouse", + "fieldtype": "Link", + "in_filter": 1, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Warehouse", + "oldfieldname": "warehouse", + "oldfieldtype": "Link", + "options": "Warehouse", + "read_only": 1, + "search_index": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "item_code", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 1, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Item Code", - "length": 0, - "no_copy": 0, - "oldfieldname": "item_code", - "oldfieldtype": "Link", - "options": "Item", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "item_code", + "fieldtype": "Link", + "in_filter": 1, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Item Code", + "oldfieldname": "item_code", + "oldfieldtype": "Link", + "options": "Item", + "read_only": 1, + "search_index": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "0.00", - "fieldname": "reserved_qty", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Reserved Quantity", - "length": 0, - "no_copy": 0, - "oldfieldname": "reserved_qty", - "oldfieldtype": "Currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "0.00", + "fieldname": "reserved_qty", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Reserved Quantity", + "oldfieldname": "reserved_qty", + "oldfieldtype": "Currency", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "0.00", - "fieldname": "actual_qty", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 1, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Actual Quantity", - "length": 0, - "no_copy": 0, - "oldfieldname": "actual_qty", - "oldfieldtype": "Currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "0.00", + "fieldname": "actual_qty", + "fieldtype": "Float", + "in_filter": 1, + "in_list_view": 1, + "label": "Actual Quantity", + "oldfieldname": "actual_qty", + "oldfieldtype": "Currency", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "0.00", - "fieldname": "ordered_qty", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Ordered Quantity", - "length": 0, - "no_copy": 0, - "oldfieldname": "ordered_qty", - "oldfieldtype": "Currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "0.00", + "fieldname": "ordered_qty", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Ordered Quantity", + "oldfieldname": "ordered_qty", + "oldfieldtype": "Currency", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "0.00", - "fieldname": "indented_qty", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Requested Quantity", - "length": 0, - "no_copy": 0, - "oldfieldname": "indented_qty", - "oldfieldtype": "Currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "0.00", + "fieldname": "indented_qty", + "fieldtype": "Float", + "label": "Requested Quantity", + "oldfieldname": "indented_qty", + "oldfieldtype": "Currency", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "planned_qty", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Planned Qty", - "length": 0, - "no_copy": 0, - "oldfieldname": "planned_qty", - "oldfieldtype": "Currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "planned_qty", + "fieldtype": "Float", + "label": "Planned Qty", + "oldfieldname": "planned_qty", + "oldfieldtype": "Currency", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "projected_qty", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Projected Qty", - "length": 0, - "no_copy": 0, - "oldfieldname": "projected_qty", - "oldfieldtype": "Currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "projected_qty", + "fieldtype": "Float", + "label": "Projected Qty", + "oldfieldname": "projected_qty", + "oldfieldtype": "Currency", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "reserved_qty_for_production", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Reserved Qty for Production", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "reserved_qty_for_production", + "fieldtype": "Float", + "label": "Reserved Qty for Production", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "reserved_qty_for_sub_contract", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Reserved Qty for sub contract", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "reserved_qty_for_sub_contract", + "fieldtype": "Float", + "label": "Reserved Qty for sub contract" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "ma_rate", - "fieldtype": "Float", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Moving Average Rate", - "length": 0, - "no_copy": 0, - "oldfieldname": "ma_rate", - "oldfieldtype": "Currency", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 1, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "ma_rate", + "fieldtype": "Float", + "hidden": 1, + "label": "Moving Average Rate", + "oldfieldname": "ma_rate", + "oldfieldtype": "Currency", + "print_hide": 1, + "read_only": 1, + "report_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "stock_uom", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 1, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "UOM", - "length": 0, - "no_copy": 0, - "oldfieldname": "stock_uom", - "oldfieldtype": "Data", - "options": "UOM", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "stock_uom", + "fieldtype": "Link", + "in_filter": 1, + "label": "UOM", + "oldfieldname": "stock_uom", + "oldfieldtype": "Data", + "options": "UOM", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "fcfs_rate", - "fieldtype": "Float", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "FCFS Rate", - "length": 0, - "no_copy": 0, - "oldfieldname": "fcfs_rate", - "oldfieldtype": "Currency", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 1, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "fcfs_rate", + "fieldtype": "Float", + "hidden": 1, + "label": "FCFS Rate", + "oldfieldname": "fcfs_rate", + "oldfieldtype": "Currency", + "print_hide": 1, + "read_only": 1, + "report_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "valuation_rate", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Valuation Rate", - "length": 0, - "no_copy": 0, - "oldfieldname": "valuation_rate", - "oldfieldtype": "Currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "valuation_rate", + "fieldtype": "Float", + "label": "Valuation Rate", + "oldfieldname": "valuation_rate", + "oldfieldtype": "Currency", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "stock_value", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Stock Value", - "length": 0, - "no_copy": 0, - "oldfieldname": "stock_value", - "oldfieldtype": "Currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldname": "stock_value", + "fieldtype": "Float", + "label": "Stock Value", + "oldfieldname": "stock_value", + "oldfieldtype": "Currency", + "read_only": 1 } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 1, - "idx": 1, - "image_view": 0, - "in_create": 1, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-08-21 16:15:39.356230", - "modified_by": "Administrator", - "module": "Stock", - "name": "Bin", - "owner": "Administrator", + ], + "hide_toolbar": 1, + "idx": 1, + "in_create": 1, + "modified": "2019-11-18 18:34:59.456882", + "modified_by": "Administrator", + "module": "Stock", + "name": "Bin", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Sales User", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 - }, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Sales User" + }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Purchase User", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 - }, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Purchase User" + }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Stock User", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Stock User" } - ], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "search_fields": "item_code,warehouse", - "show_name_in_global_search": 0, - "sort_order": "ASC", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 + ], + "quick_entry": 1, + "search_fields": "item_code,warehouse", + "sort_order": "ASC" } \ No newline at end of file diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index c98dfe35fb5..39aad2e0071 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -424,7 +424,12 @@ def make_sales_invoice(source_name, target_doc=None): target.run_method("calculate_taxes_and_totals") # set company address - target.update(get_company_address(target.company)) + if source.company_address: + target.update({'company_address': source.company_address}) + else: + # set company address + target.update(get_company_address(target.company)) + if target.company_address: target.update(get_fetch_values("Sales Invoice", 'company_address', target.company_address)) diff --git a/erpnext/stock/doctype/delivery_note/regional/india.js b/erpnext/stock/doctype/delivery_note/regional/india.js new file mode 100644 index 00000000000..22f4716ea52 --- /dev/null +++ b/erpnext/stock/doctype/delivery_note/regional/india.js @@ -0,0 +1,4 @@ +{% include "erpnext/regional/india/taxes.js" %} + +erpnext.setup_auto_gst_taxation('Delivery Note'); + diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json index a2aab3f69ee..af8e13288a9 100644 --- a/erpnext/stock/doctype/item/item.json +++ b/erpnext/stock/doctype/item/item.json @@ -135,8 +135,7 @@ "publish_in_hub", "hub_category_to_publish", "hub_warehouse", - "synced_with_hub", - "manufacturers" + "synced_with_hub" ], "fields": [ { @@ -1016,12 +1015,6 @@ "label": "Synced With Hub", "read_only": 1 }, - { - "fieldname": "manufacturers", - "fieldtype": "Table", - "label": "Manufacturers", - "options": "Item Manufacturer" - }, { "depends_on": "eval:!doc.__islocal", "fieldname": "over_delivery_receipt_allowance", @@ -1049,7 +1042,7 @@ "idx": 2, "image_field": "image", "max_attachments": 1, - "modified": "2019-10-09 17:05:59.576119", + "modified": "2019-12-13 12:15:56.197246", "modified_by": "Administrator", "module": "Stock", "name": "Item", diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index 7495dffec24..efcede1708d 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -125,7 +125,6 @@ class Item(WebsiteGenerator): self.validate_auto_reorder_enabled_in_stock_settings() self.cant_change() self.update_show_in_website() - self.validate_manufacturer() if not self.get("__islocal"): self.old_item_group = frappe.db.get_value(self.doctype, self.name, "item_group") @@ -145,13 +144,6 @@ class Item(WebsiteGenerator): if cint(frappe.db.get_single_value('Stock Settings', 'clean_description_html')): self.description = clean_html(self.description) - def validate_manufacturer(self): - list_man = [(x.manufacturer, x.manufacturer_part_no) for x in self.get('manufacturers')] - set_man = set(list_man) - - if len(list_man) != len(set_man): - frappe.throw(_("Duplicate entry in Manufacturers table")) - def validate_customer_provided_part(self): if self.is_customer_provided_item: if self.is_purchase_item: diff --git a/erpnext/stock/doctype/packed_item/packed_item.json b/erpnext/stock/doctype/packed_item/packed_item.json index b089e759a0f..2ac5c426c03 100644 --- a/erpnext/stock/doctype/packed_item/packed_item.json +++ b/erpnext/stock/doctype/packed_item/packed_item.json @@ -18,6 +18,7 @@ "serial_no", "column_break_11", "batch_no", + "actual_batch_qty", "section_break_13", "actual_qty", "projected_qty", @@ -189,15 +190,26 @@ "oldfieldtype": "Data", "print_hide": 1, "read_only": 1 + }, + { + "depends_on": "batch_no", + "fieldname": "actual_batch_qty", + "fieldtype": "Float", + "label": "Actual Batch Quantity", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 } ], "idx": 1, "istable": 1, - "modified": "2019-08-27 18:17:37.167512", + "modified": "2019-11-26 20:09:59.400960", "modified_by": "Administrator", "module": "Stock", "name": "Packed Item", "owner": "Administrator", "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", "track_changes": 1 } diff --git a/erpnext/stock/doctype/price_list/price_list.py b/erpnext/stock/doctype/price_list/price_list.py index 8773b9c33f7..33713faf696 100644 --- a/erpnext/stock/doctype/price_list/price_list.py +++ b/erpnext/stock/doctype/price_list/price_list.py @@ -16,6 +16,7 @@ class PriceList(Document): def on_update(self): self.set_default_if_missing() self.update_item_price() + self.delete_price_list_details_key() def set_default_if_missing(self): if cint(self.selling): @@ -32,6 +33,8 @@ class PriceList(Document): (self.currency, cint(self.buying), cint(self.selling), self.name)) def on_trash(self): + self.delete_price_list_details_key() + def _update_default_price_list(module): b = frappe.get_doc(module + " Settings") price_list_fieldname = module.lower() + "_price_list" @@ -43,3 +46,20 @@ class PriceList(Document): for module in ["Selling", "Buying"]: _update_default_price_list(module) + + def delete_price_list_details_key(self): + frappe.cache().hdel("price_list_details", self.name) + +def get_price_list_details(price_list): + price_list_details = frappe.cache().hget("price_list_details", price_list) + + if not price_list_details: + price_list_details = frappe.get_cached_value("Price List", price_list, + ["currency", "price_not_uom_dependent", "enabled"], as_dict=1) + + if not price_list_details or not price_list_details.get("enabled"): + throw(_("Price List {0} is disabled or does not exist").format(price_list)) + + frappe.cache().hset("price_list_details", price_list, price_list_details) + + return price_list_details or {} \ No newline at end of file diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index a55e4cce4c1..060175f9045 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -95,7 +95,8 @@ class PurchaseReceipt(BuyingController): # check cwip accounts before making auto assets # Improves UX by not giving messages of "Assets Created" before throwing error of not finding arbnb account arbnb_account = self.get_company_default("asset_received_but_not_billed") - cwip_account = get_asset_account("capital_work_in_progress_account", company = self.company) + cwip_account = get_asset_account("capital_work_in_progress_account", asset_category = item.asset_category, \ + company = self.company) break def validate_with_previous_doc(self): @@ -364,8 +365,9 @@ class PurchaseReceipt(BuyingController): def add_asset_gl_entries(self, item, gl_entries): arbnb_account = self.get_company_default("asset_received_but_not_billed") - # This returns company's default cwip account - cwip_account = get_asset_account("capital_work_in_progress_account", company = self.company) + # This returns category's cwip account if not then fallback to company's default cwip account + cwip_account = get_asset_account("capital_work_in_progress_account", asset_category = item.asset_category, \ + company = self.company) asset_amount = flt(item.net_amount) + flt(item.item_tax_amount/self.conversion_rate) base_asset_amount = flt(item.base_net_amount + item.item_tax_amount) diff --git a/erpnext/stock/doctype/purchase_receipt/regional/india.js b/erpnext/stock/doctype/purchase_receipt/regional/india.js new file mode 100644 index 00000000000..b4f1201f36c --- /dev/null +++ b/erpnext/stock/doctype/purchase_receipt/regional/india.js @@ -0,0 +1,3 @@ +{% include "erpnext/regional/india/taxes.js" %} + +erpnext.setup_auto_gst_taxation('Purchase Receipt'); \ No newline at end of file diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.py b/erpnext/stock/doctype/quality_inspection/quality_inspection.py index 738c63ca358..37ab807cb7b 100644 --- a/erpnext/stock/doctype/quality_inspection/quality_inspection.py +++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.py @@ -6,6 +6,7 @@ import frappe from frappe.model.document import Document from erpnext.stock.doctype.quality_inspection_template.quality_inspection_template \ import get_template_details +from frappe.model.mapper import get_mapped_doc class QualityInspection(Document): def validate(self): @@ -84,3 +85,37 @@ def item_query(doctype, txt, searchfield, start, page_len, filters): parent=filters.get('parent'), cond = cond, mcond = mcond, start = start, page_len = page_len, qi_condition = qi_condition), {'parent': filters.get('parent'), 'txt': "%%%s%%" % txt}) + +def quality_inspection_query(doctype, txt, searchfield, start, page_len, filters): + return frappe.get_all('Quality Inspection', + limit_start=start, + limit_page_length=page_len, + filters = { + 'docstatus': 1, + 'name': ('like', '%%%s%%' % txt), + 'item_code': filters.get("item_code"), + 'reference_name': ('in', [filters.get("reference_name", ''), '']) + }, as_list=1) + +@frappe.whitelist() +def make_quality_inspection(source_name, target_doc=None): + def postprocess(source, doc): + doc.inspected_by = frappe.session.user + doc.get_quality_inspection_template() + + doc = get_mapped_doc("BOM", source_name, { + 'BOM': { + "doctype": "Quality Inspection", + "validation": { + "docstatus": ["=", 1] + }, + "field_map": { + "name": "bom_no", + "item": "item_code", + "stock_uom": "uom", + "stock_qty": "qty" + }, + } + }, target_doc, postprocess) + + return doc \ No newline at end of file diff --git a/erpnext/stock/doctype/quick_stock_balance/quick_stock_balance.js b/erpnext/stock/doctype/quick_stock_balance/quick_stock_balance.js index a6f7343388c..f261fd99790 100644 --- a/erpnext/stock/doctype/quick_stock_balance/quick_stock_balance.js +++ b/erpnext/stock/doctype/quick_stock_balance/quick_stock_balance.js @@ -16,7 +16,7 @@ frappe.ui.form.on('Quick Stock Balance', { frm.add_custom_button(__('Stock Balance Report'), () => { frappe.set_route('query-report', 'Stock Balance', { 'item_code': frm.doc.item, 'warehouse': frm.doc.warehouse }); - }).addClass("btn-primary"); + }); } }, diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index 6e78b988f66..ca480f969d8 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -102,11 +102,12 @@ frappe.ui.form.on('Stock Entry', { frm.set_query("quality_inspection", "items", function(doc, cdt, cdn) { var d = locals[cdt][cdn]; + return { + query:"erpnext.stock.doctype.quality_inspection.quality_inspection.quality_inspection_query", filters: { - docstatus: 1, - item_code: d.item_code, - reference_name: doc.name + 'item_code': d.item_code, + 'reference_name': doc.name } } }); @@ -535,7 +536,7 @@ frappe.ui.form.on('Stock Entry Detail', { if(r.message) { var d = locals[cdt][cdn]; $.each(r.message, function(k, v) { - d[k] = v; + frappe.model.set_value(cdt, cdn, k, v); // qty and it's subsequent fields weren't triggered }); refresh_field("items"); erpnext.stock.select_batch_and_serial_no(frm, d); diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 26693d208b4..00d27ef232b 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -91,6 +91,7 @@ class StockEntry(StockController): self.update_cost_in_project() self.validate_reserved_serial_no_consumption() self.update_transferred_qty() + self.update_quality_inspection() if self.work_order and self.purpose == "Manufacture": self.update_so_in_serial_number() @@ -108,6 +109,7 @@ class StockEntry(StockController): self.make_gl_entries_on_cancel() self.update_cost_in_project() self.update_transferred_qty() + self.update_quality_inspection() def set_job_card_data(self): if self.job_card and not self.work_order: @@ -647,6 +649,12 @@ class StockEntry(StockController): gl_entries = super(StockEntry, self).get_gl_entries(warehouse_account) total_basic_amount = sum([flt(t.basic_amount) for t in self.get("items") if t.t_warehouse]) + divide_based_on = total_basic_amount + + if self.get("additional_costs") and not total_basic_amount: + # if total_basic_amount is 0, distribute additional charges based on qty + divide_based_on = sum(item.qty for item in list(self.get("items"))) + item_account_wise_additional_cost = {} for t in self.get("additional_costs"): @@ -654,8 +662,11 @@ class StockEntry(StockController): if d.t_warehouse: item_account_wise_additional_cost.setdefault((d.item_code, d.name), {}) item_account_wise_additional_cost[(d.item_code, d.name)].setdefault(t.expense_account, 0.0) + + multiply_based_on = d.basic_amount if total_basic_amount else d.qty + item_account_wise_additional_cost[(d.item_code, d.name)][t.expense_account] += \ - (t.amount * d.basic_amount) / total_basic_amount + (t.amount * multiply_based_on) / divide_based_on if item_account_wise_additional_cost: for d in self.get("items"): @@ -1285,6 +1296,20 @@ class StockEntry(StockController): self._update_percent_field_in_targets(args, update_modified=True) + def update_quality_inspection(self): + if self.inspection_required: + reference_type = reference_name = '' + if self.docstatus == 1: + reference_name = self.name + reference_type = 'Stock Entry' + + for d in self.items: + if d.quality_inspection: + frappe.db.set_value("Quality Inspection", d.quality_inspection, { + 'reference_type': reference_type, + 'reference_name': reference_name + }) + @frappe.whitelist() def move_sample_to_retention_warehouse(company, items): if isinstance(items, string_types): diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index eddab5d79dc..ee5f2370987 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -790,6 +790,50 @@ class TestStockEntry(unittest.TestCase): filters={"voucher_type": "Stock Entry", "voucher_no": mr.name}, fieldname="is_opening") self.assertEqual(is_opening, "Yes") + def test_total_basic_amount_zero(self): + se = frappe.get_doc({"doctype":"Stock Entry", + "purpose":"Material Receipt", + "stock_entry_type":"Material Receipt", + "posting_date": nowdate(), + "company":"_Test Company with perpetual inventory", + "items":[ + { + "item_code":"Basil Leaves", + "description":"Basil Leaves", + "qty": 1, + "basic_rate": 0, + "uom":"Nos", + "t_warehouse": "Stores - TCP1", + "allow_zero_valuation_rate": 1, + "cost_center": "Main - TCP1" + }, + { + "item_code":"Basil Leaves", + "description":"Basil Leaves", + "qty": 2, + "basic_rate": 0, + "uom":"Nos", + "t_warehouse": "Stores - TCP1", + "allow_zero_valuation_rate": 1, + "cost_center": "Main - TCP1" + }, + ], + "additional_costs":[ + {"expense_account":"Miscellaneous Expenses - TCP1", + "amount":100, + "description": "miscellanous"} + ] + }) + se.insert() + se.submit() + + self.check_gl_entries("Stock Entry", se.name, + sorted([ + ["Stock Adjustment - TCP1", 100.0, 0.0], + ["Miscellaneous Expenses - TCP1", 0.0, 100.0] + ]) + ) + def make_serialized_item(item_code=None, serial_no=None, target_warehouse=None): se = frappe.copy_doc(test_records[0]) se.get("items")[0].item_code = item_code or "_Test Serialized Item With Series" diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json index 947f94853e2..c9eba71b0d0 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json +++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json @@ -1,874 +1,299 @@ { "allow_copy": 1, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, "autoname": "MAT-SLE-.YYYY.-.#####", - "beta": 0, "creation": "2013-01-29 19:25:42", - "custom": 0, - "docstatus": 0, "doctype": "DocType", "document_type": "Other", - "editable_grid": 0, "engine": "InnoDB", + "field_order": [ + "item_code", + "serial_no", + "batch_no", + "warehouse", + "posting_date", + "posting_time", + "voucher_type", + "voucher_no", + "voucher_detail_no", + "actual_qty", + "incoming_rate", + "outgoing_rate", + "stock_uom", + "qty_after_transaction", + "valuation_rate", + "stock_value", + "stock_value_difference", + "stock_queue", + "project", + "company", + "fiscal_year", + "is_cancelled", + "to_rename" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "item_code", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, "in_filter": 1, - "in_global_search": 0, "in_list_view": 1, "in_standard_filter": 1, "label": "Item Code", - "length": 0, - "no_copy": 0, "oldfieldname": "item_code", "oldfieldtype": "Link", "options": "Item", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, "print_width": "100px", "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "100px" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "serial_no", - "fieldtype": "Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, + "fieldtype": "Long Text", "in_list_view": 1, - "in_standard_filter": 0, "label": "Serial No", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, "print_width": "100px", "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "100px" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "batch_no", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "Batch No", - "length": 0, - "no_copy": 0, "oldfieldname": "batch_no", "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "warehouse", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, "in_filter": 1, - "in_global_search": 0, "in_list_view": 1, "in_standard_filter": 1, "label": "Warehouse", - "length": 0, - "no_copy": 0, "oldfieldname": "warehouse", "oldfieldtype": "Link", "options": "Warehouse", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, "print_width": "100px", "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "100px" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "posting_date", "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, "in_filter": 1, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "Posting Date", - "length": 0, - "no_copy": 0, "oldfieldname": "posting_date", "oldfieldtype": "Date", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, "print_width": "100px", "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "100px" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "posting_time", "fieldtype": "Time", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Posting Time", - "length": 0, - "no_copy": 0, "oldfieldname": "posting_time", "oldfieldtype": "Time", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, "print_width": "100px", "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "100px" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "voucher_type", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, "in_filter": 1, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Voucher Type", - "length": 0, - "no_copy": 0, "oldfieldname": "voucher_type", "oldfieldtype": "Data", "options": "DocType", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, "print_width": "150px", "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "150px" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "voucher_no", "fieldtype": "Dynamic Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, "in_filter": 1, - "in_global_search": 0, - "in_list_view": 0, "in_standard_filter": 1, "label": "Voucher No", - "length": 0, - "no_copy": 0, "oldfieldname": "voucher_no", "oldfieldtype": "Data", "options": "voucher_type", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, "print_width": "150px", "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "150px" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "voucher_detail_no", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Voucher Detail No", - "length": 0, - "no_copy": 0, "oldfieldname": "voucher_detail_no", "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, "print_width": "150px", "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "150px" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "actual_qty", "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, "in_filter": 1, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Actual Quantity", - "length": 0, - "no_copy": 0, "oldfieldname": "actual_qty", "oldfieldtype": "Currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, "print_width": "150px", "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "150px" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "incoming_rate", "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Incoming Rate", - "length": 0, - "no_copy": 0, "oldfieldname": "incoming_rate", "oldfieldtype": "Currency", "options": "Company:company:default_currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "outgoing_rate", "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Outgoing Rate", - "length": 0, - "no_copy": 0, "options": "Company:company:default_currency", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "stock_uom", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Stock UOM", - "length": 0, - "no_copy": 0, "oldfieldname": "stock_uom", "oldfieldtype": "Data", "options": "UOM", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, "print_width": "150px", "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "150px" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "qty_after_transaction", "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, "in_filter": 1, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Actual Qty After Transaction", - "length": 0, - "no_copy": 0, "oldfieldname": "bin_aqat", "oldfieldtype": "Currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, "print_width": "150px", "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "150px" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "valuation_rate", "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Valuation Rate", - "length": 0, - "no_copy": 0, "oldfieldname": "valuation_rate", "oldfieldtype": "Currency", "options": "Company:company:default_currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, "print_width": "150px", "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "150px" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "stock_value", "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Stock Value", - "length": 0, - "no_copy": 0, "oldfieldname": "stock_value", "oldfieldtype": "Currency", "options": "Company:company:default_currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "stock_value_difference", "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Stock Value Difference", - "length": 0, - "no_copy": 0, "options": "Company:company:default_currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "stock_queue", "fieldtype": "Text", "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Stock Queue (FIFO)", - "length": 0, - "no_copy": 0, "oldfieldname": "fcfs_stack", "oldfieldtype": "Text", - "permlevel": 0, "print_hide": 1, - "print_hide_if_no_value": 0, "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 1, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "report_hide": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "project", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Project", - "length": 0, - "no_copy": 0, - "options": "Project", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Project" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "company", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, "in_filter": 1, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Company", - "length": 0, - "no_copy": 0, "oldfieldname": "company", "oldfieldtype": "Data", "options": "Company", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, "print_width": "150px", "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "150px" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "fiscal_year", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, "in_filter": 1, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Fiscal Year", - "length": 0, - "no_copy": 0, "oldfieldname": "fiscal_year", "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, "print_width": "150px", "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "150px" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "is_cancelled", "fieldtype": "Select", "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Is Cancelled", - "length": 0, - "no_copy": 0, "options": "\nNo\nYes", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 1, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "report_hide": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "1", "fieldname": "to_rename", "fieldtype": "Check", "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "To Rename", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "search_index": 1 } ], - "has_web_view": 0, - "hide_heading": 0, "hide_toolbar": 1, "icon": "fa fa-list", "idx": 1, - "image_view": 0, "in_create": 1, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2019-01-07 07:04:37.523024", + "modified": "2019-11-27 12:17:31.522675", "modified_by": "Administrator", "module": "Stock", "name": "Stock Ledger Entry", "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 0, "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, - "role": "Stock User", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 + "role": "Stock User" }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 0, "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 0, "read": 1, "report": 1, - "role": "Accounts Manager", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 + "role": "Accounts Manager" } ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 + "sort_order": "DESC" } \ No newline at end of file diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index eb373189c0a..55f4be136b6 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -12,6 +12,7 @@ from frappe.model.meta import get_field_precision from erpnext.stock.doctype.batch.batch import get_batch_no from erpnext import get_company_currency from erpnext.stock.doctype.item.item import get_item_defaults, get_uom_conv_factor +from erpnext.stock.doctype.price_list.price_list import get_price_list_details from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults from erpnext.setup.doctype.brand.brand import get_brand_defaults from erpnext.stock.doctype.item_manufacturer.item_manufacturer import get_item_manufacturer_part_no @@ -22,7 +23,7 @@ sales_doctypes = ['Quotation', 'Sales Order', 'Delivery Note', 'Sales Invoice'] purchase_doctypes = ['Material Request', 'Supplier Quotation', 'Purchase Order', 'Purchase Receipt', 'Purchase Invoice'] @frappe.whitelist() -def get_item_details(args, doc=None, overwrite_warehouse=True): +def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=True): """ args = { "item_code": "", @@ -74,7 +75,9 @@ def get_item_details(args, doc=None, overwrite_warehouse=True): if args.get(key) is None: args[key] = value - data = get_pricing_rule_for_item(args, out.price_list_rate, doc) + data = get_pricing_rule_for_item(args, out.price_list_rate, + doc, for_validate=for_validate) + out.update(data) update_stock(args, out) @@ -485,7 +488,6 @@ def get_price_list_rate(args, item_doc, out): if meta.get_field("currency") or args.get('currency'): pl_details = get_price_list_currency_and_exchange_rate(args) args.update(pl_details) - validate_price_list(args) if meta.get_field("currency"): validate_conversion_rate(args, meta) @@ -640,14 +642,6 @@ def check_packing_list(price_list_rate_name, desired_qty, item_code): return flag -def validate_price_list(args): - if args.get("price_list"): - if not frappe.db.get_value("Price List", - {"name": args.price_list, args.transaction_type: 1, "enabled": 1}): - throw(_("Price List {0} is disabled or does not exist").format(args.price_list)) - elif args.get("customer"): - throw(_("Price List not selected")) - def validate_conversion_rate(args, meta): from erpnext.controllers.accounts_controller import validate_conversion_rate @@ -911,27 +905,6 @@ def apply_price_list_on_item(args): return item_details -def get_price_list_currency(price_list): - if price_list: - result = frappe.db.get_value("Price List", {"name": price_list, - "enabled": 1}, ["name", "currency"], as_dict=True) - - if not result: - throw(_("Price List {0} is disabled or does not exist").format(price_list)) - - return result.currency - -def get_price_list_uom_dependant(price_list): - if price_list: - result = frappe.db.get_value("Price List", {"name": price_list, - "enabled": 1}, ["name", "price_not_uom_dependent"], as_dict=True) - - if not result: - throw(_("Price List {0} is disabled or does not exist").format(price_list)) - - return not result.price_not_uom_dependent - - def get_price_list_currency_and_exchange_rate(args): if not args.price_list: return {} @@ -941,8 +914,11 @@ def get_price_list_currency_and_exchange_rate(args): elif args.doctype in ['Purchase Order', 'Purchase Receipt', 'Purchase Invoice']: args.update({"exchange_rate": "for_buying"}) - price_list_currency = get_price_list_currency(args.price_list) - price_list_uom_dependant = get_price_list_uom_dependant(args.price_list) + price_list_details = get_price_list_details(args.price_list) + + price_list_currency = price_list_details.get("currency") + price_list_uom_dependant = price_list_details.get("price_list_uom_dependant") + plc_conversion_rate = args.plc_conversion_rate company_currency = get_company_currency(args.company) diff --git a/erpnext/stock/report/purchase_order_items_to_be_received_or_billed/purchase_order_items_to_be_received_or_billed.json b/erpnext/stock/report/purchase_order_items_to_be_received_or_billed/purchase_order_items_to_be_received_or_billed.json index caf7eb88634..48c0f423fd9 100644 --- a/erpnext/stock/report/purchase_order_items_to_be_received_or_billed/purchase_order_items_to_be_received_or_billed.json +++ b/erpnext/stock/report/purchase_order_items_to_be_received_or_billed/purchase_order_items_to_be_received_or_billed.json @@ -15,7 +15,7 @@ "prepared_report": 0, "query": "SELECT\n\t`poi_pri`.`purchase_order` as \"Purchase Order:Link/Purchase Order:120\",\n\t`poi_pri`.`status` as \"Status:Data:120\",\n\t`poi_pri`.`transaction_date` as \"Date:Date:100\",\n\t`poi_pri`.`schedule_date` as \"Reqd by Date:Date:110\",\n\t`poi_pri`.`supplier` as \"Supplier:Link/Supplier:120\",\n\t`poi_pri`.`supplier_name` as \"Supplier Name::150\",\n\t`poi_pri`.`item_code` as \"Item Code:Link/Item:120\",\n\t`poi_pri`.`qty` as \"Qty:Float:100\",\n\t`poi_pri`.`base_amount` as \"Base Amount:Currency:100\",\n\t`poi_pri`.`received_qty` as \"Received Qty:Float:100\",\n\t`poi_pri`.`received_amount` as \"Received Qty Amount:Currency:100\",\n\t`poi_pri`.`qty_to_receive` as \"Qty to Receive:Float:100\",\n\t`poi_pri`.`amount_to_be_received` as \"Amount to Receive:Currency:100\",\n\t`poi_pri`.`billed_amount` as \"Billed Amount:Currency:100\",\n\t`poi_pri`.`amount_to_be_billed` as \"Amount To Be Billed:Currency:100\",\n\tSUM(`pii`.`qty`) AS \"Billed Qty:Float:100\",\n\t`poi_pri`.qty - SUM(`pii`.`qty`) AS \"Qty To Be Billed:Float:100\",\n\t`poi_pri`.`warehouse` as \"Warehouse:Link/Warehouse:150\",\n\t`poi_pri`.`item_name` as \"Item Name::150\",\n\t`poi_pri`.`description` as \"Description::200\",\n\t`poi_pri`.`brand` as \"Brand::100\",\n\t`poi_pri`.`project` as \"Project\",\n\t`poi_pri`.`company` as \"Company:Link/Company:\"\nFROM\n\t(SELECT\n\t\t`po`.`name` AS 'purchase_order',\n\t\t`po`.`status`,\n\t\t`po`.`company`,\n\t\t`poi`.`warehouse`,\n\t\t`poi`.`brand`,\n\t\t`poi`.`description`,\n\t\t`po`.`transaction_date`,\n\t\t`poi`.`schedule_date`,\n\t\t`po`.`supplier`,\n\t\t`po`.`supplier_name`,\n\t\t`poi`.`project`,\n\t\t`poi`.`item_code`,\n\t\t`poi`.`item_name`,\n\t\t`poi`.`qty`,\n\t\t`poi`.`base_amount`,\n\t\t`poi`.`received_qty`,\n\t\t(`poi`.billed_amt * ifnull(`po`.conversion_rate, 1)) as billed_amount,\n\t\t(`poi`.base_amount - (`poi`.billed_amt * ifnull(`po`.conversion_rate, 1))) as amount_to_be_billed,\n\t\t`poi`.`qty` - IFNULL(`poi`.`received_qty`, 0) AS 'qty_to_receive',\n\t\t(`poi`.`qty` - IFNULL(`poi`.`received_qty`, 0)) * `poi`.`rate` AS 'amount_to_be_received',\n\t\tSUM(`pri`.`amount`) AS 'received_amount',\n\t\t`poi`.`name` AS 'poi_name',\n\t\t`pri`.`name` AS 'pri_name'\n\tFROM\n\t\t`tabPurchase Order` po\n\t\tLEFT JOIN `tabPurchase Order Item` poi\n\t\tON `poi`.`parent` = `po`.`name`\n\t\tLEFT JOIN `tabPurchase Receipt Item` pri\n\t\tON `pri`.`purchase_order_item` = `poi`.`name`\n\t\t\tAND `pri`.`docstatus`=1\n\tWHERE\n\t\t`po`.`status` not in ('Stopped', 'Closed')\n\t\tAND `po`.`docstatus` = 1\n\t\tAND IFNULL(`poi`.`received_qty`, 0) < IFNULL(`poi`.`qty`, 0)\n\tGROUP BY `poi`.`name`\n\tORDER BY `po`.`transaction_date` ASC\n\t) poi_pri\n\tLEFT JOIN `tabPurchase Invoice Item` pii\n\tON `pii`.`po_detail` = `poi_pri`.`poi_name`\n\t\tAND `pii`.`docstatus`=1\nGROUP BY `poi_pri`.`poi_name`", "ref_doctype": "Purchase Order", - "report_name": "Purchase Order Items To Be Received or Billed1", + "report_name": "Purchase Order Items To Be Received or Billed", "report_type": "Query Report", "roles": [ { diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py index 68b8b502e5b..a74253e8722 100644 --- a/erpnext/stock/report/stock_balance/stock_balance.py +++ b/erpnext/stock/report/stock_balance/stock_balance.py @@ -292,7 +292,7 @@ def validate_filters(filters): if not (filters.get("item_code") or filters.get("warehouse")): sle_count = flt(frappe.db.sql("""select count(name) from `tabStock Ledger Entry`""")[0][0]) if sle_count > 500000: - frappe.throw(_("Please set filter based on Item or Warehouse")) + frappe.throw(_("Please set filter based on Item or Warehouse due to a large amount of entries.")) def get_variants_attributes(): '''Return all item variant attributes.''' diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py index db7f6ad1b9c..d757ecb293d 100644 --- a/erpnext/stock/report/stock_ledger/stock_ledger.py +++ b/erpnext/stock/report/stock_ledger/stock_ledger.py @@ -122,8 +122,8 @@ def get_item_details(items, sl_entries, include_uom): cf_field = cf_join = "" if include_uom: cf_field = ", ucd.conversion_factor" - cf_join = "left join `tabUOM Conversion Detail` ucd on ucd.parent=item.name and ucd.uom='%s'" \ - % (include_uom) + cf_join = "left join `tabUOM Conversion Detail` ucd on ucd.parent=item.name and ucd.uom=%s" \ + % frappe.db.escape(include_uom) res = frappe.db.sql(""" select diff --git a/erpnext/templates/emails/confirm_appointment.html b/erpnext/templates/emails/confirm_appointment.html new file mode 100644 index 00000000000..6c9b28bc136 --- /dev/null +++ b/erpnext/templates/emails/confirm_appointment.html @@ -0,0 +1,10 @@ +

{{_("Dear")}} {{ full_name }}{% if last_name %} {{ last_name}}{% endif %},

+

{{_("A new appointment has been created for you with {0}").format(site_url)}}.

+

{{_("Click on the link below to verify your email and confirm the appointment")}}.

+ +

+ {{ _("Verify Email") }} +

+ +
+

{{_("You can also copy-paste this link in your browser")}} {{ link }}

diff --git a/erpnext/templates/includes/cart/cart_address.html b/erpnext/templates/includes/cart/cart_address.html index fe53f34dba5..f7f35483208 100644 --- a/erpnext/templates/includes/cart/cart_address.html +++ b/erpnext/templates/includes/cart/cart_address.html @@ -64,16 +64,6 @@ frappe.ready(() => { fieldtype: 'Data', reqd: 1 }, - { - label: __('Address Type'), - fieldname: 'address_type', - fieldtype: 'Select', - options: [ - 'Billing', - 'Shipping' - ], - reqd: 1 - }, { label: __('Address Line 1'), fieldname: 'address_line1', @@ -96,16 +86,37 @@ frappe.ready(() => { fieldname: 'state', fieldtype: 'Data' }, + { + label: __('Country'), + fieldname: 'country', + fieldtype: 'Link', + options: 'Country', + reqd: 1 + }, + { + fieldname: "column_break0", + fieldtype: "Column Break", + width: "50%" + }, + { + label: __('Address Type'), + fieldname: 'address_type', + fieldtype: 'Select', + options: [ + 'Billing', + 'Shipping' + ], + reqd: 1 + }, { label: __('Pin Code'), fieldname: 'pincode', fieldtype: 'Data' }, { - label: __('Country'), - fieldname: 'country', - fieldtype: 'Link', - reqd: 1 + fieldname: "phone", + fieldtype: "Data", + label: "Phone" }, ], primary_action_label: __('Save'), diff --git a/erpnext/templates/pages/cart.html b/erpnext/templates/pages/cart.html index b301fc0665a..912702e386d 100644 --- a/erpnext/templates/pages/cart.html +++ b/erpnext/templates/pages/cart.html @@ -83,12 +83,10 @@ {% endif %} - {% if cart_settings.enable_checkout %}
{% include "templates/includes/cart/cart_address.html" %}
{% endif %} - {% endif %}
diff --git a/erpnext/www/book_appointment/__init__.py b/erpnext/www/book_appointment/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/www/book_appointment/index.css b/erpnext/www/book_appointment/index.css new file mode 100644 index 00000000000..6c49fde739e --- /dev/null +++ b/erpnext/www/book_appointment/index.css @@ -0,0 +1,53 @@ +.time-slot { + margin-bottom: 2em; + margin-left: 0.5em; + margin-right: 0.5em; + border-radius: 0.4em; + cursor: pointer; + border: 0.5px solid #cccccc; + min-height: 75px; + padding: 0.5em 1em; +} + +@media (max-width: 768px) { + #submit-button-area { + display: grid; + grid-template-areas: + "submit" + "back"; + } +} +#customer-form{ + border-color: black; +} +#customer-form ::placeholder{ + color: #ddd; +} +#timeslot-container{ + display: flex; + flex-wrap: wrap; + justify-content: center; +} + +.time-slot:hover { + background: #ddd; +} + +.time-slot.unavailable { + background: #CBD5E0; + cursor: not-allowed; + color: #718096 +} + +.time-slot.unavailable .text-muted { + color: #718096 +} + +.time-slot.selected { + color: white; + background: #5e64ff; +} + +.time-slot.selected .text-muted { + color: #EDF2F7 !important; +} diff --git a/erpnext/www/book_appointment/index.html b/erpnext/www/book_appointment/index.html new file mode 100644 index 00000000000..f242f43ad54 --- /dev/null +++ b/erpnext/www/book_appointment/index.html @@ -0,0 +1,66 @@ +{% extends "templates/web.html" %} + +{% block title %}{{ _("Book Appointment") }}{% endblock %} + +{% block script %} + + +{% endblock %} + +{% block page_content %} +
+ +
+
+

Book an appointment

+

Select the date and your timezone

+
+
+
+
+ + +
+
+
+
+ +
+
+
+ +
+
+
+
+ +
+
+

Add details

+

Selected date is at +

+
+
+
+
+ + + + + + +
+
+
+
+
+
+
+
+
+ +{% endblock %} \ No newline at end of file diff --git a/erpnext/www/book_appointment/index.js b/erpnext/www/book_appointment/index.js new file mode 100644 index 00000000000..3c296980688 --- /dev/null +++ b/erpnext/www/book_appointment/index.js @@ -0,0 +1,231 @@ +frappe.ready(async () => { + initialise_select_date(); +}) + +window.holiday_list = []; + +async function initialise_select_date() { + navigate_to_page(1); + await get_global_variables(); + setup_date_picker(); + setup_timezone_selector(); + hide_next_button(); +} + +async function get_global_variables() { + // Using await through this file instead of then. + window.appointment_settings = (await frappe.call({ + method: 'erpnext.www.book_appointment.index.get_appointment_settings' + })).message; + window.timezones = (await frappe.call({ + method:'erpnext.www.book_appointment.index.get_timezones' + })).message; + window.holiday_list = window.appointment_settings.holiday_list; +} + +function setup_timezone_selector() { + let timezones_element = document.getElementById('appointment-timezone'); + let local_timezone = moment.tz.guess() + window.timezones.forEach((timezone) => { + let opt = document.createElement('option'); + opt.value = timezone; + if (timezone == local_timezone) { + opt.selected = true; + } + opt.innerHTML = timezone; + timezones_element.appendChild(opt); + }); +} + +function setup_date_picker() { + let date_picker = document.getElementById('appointment-date'); + let today = new Date(); + date_picker.min = today.toISOString().substr(0, 10); + today.setDate(today.getDate() + window.appointment_settings.advance_booking_days); + date_picker.max = today.toISOString().substr(0, 10); +} + +function hide_next_button() { + let next_button = document.getElementById('next-button'); + next_button.disabled = true; + next_button.onclick = () => frappe.msgprint("Please select a date and time"); +} + +function show_next_button() { + let next_button = document.getElementById('next-button'); + next_button.disabled = false; + next_button.onclick = setup_details_page; +} + +function on_date_or_timezone_select() { + let date_picker = document.getElementById('appointment-date'); + let timezone = document.getElementById('appointment-timezone'); + if (date_picker.value === '') { + clear_time_slots(); + hide_next_button(); + frappe.throw('Please select a date'); + } + window.selected_date = date_picker.value; + window.selected_timezone = timezone.value; + update_time_slots(date_picker.value, timezone.value); + let lead_text = document.getElementById('lead-text'); + lead_text.innerHTML = "Select Time" +} + +async function get_time_slots(date, timezone) { + let slots = (await frappe.call({ + method: 'erpnext.www.book_appointment.index.get_appointment_slots', + args: { + date: date, + timezone: timezone + } + })).message; + return slots; +} + +async function update_time_slots(selected_date, selected_timezone) { + let timeslot_container = document.getElementById('timeslot-container'); + window.slots = await get_time_slots(selected_date, selected_timezone); + clear_time_slots(); + if (window.slots.length <= 0) { + let message_div = document.createElement('p'); + message_div.innerHTML = "There are no slots available on this date"; + timeslot_container.appendChild(message_div); + return + } + window.slots.forEach((slot, index) => { + // Get and append timeslot div + let timeslot_div = get_timeslot_div_layout(slot) + timeslot_container.appendChild(timeslot_div); + }); + set_default_timeslot(); +} + +function get_timeslot_div_layout(timeslot) { + let start_time = new Date(timeslot.time) + let timeslot_div = document.createElement('div'); + timeslot_div.classList.add('time-slot'); + if (!timeslot.availability) { + timeslot_div.classList.add('unavailable') + } + timeslot_div.innerHTML = get_slot_layout(start_time); + timeslot_div.id = timeslot.time.substring(11, 19); + timeslot_div.addEventListener('click', select_time); + return timeslot_div +} + +function clear_time_slots() { + // Clear any existing divs in timeslot container + let timeslot_container = document.getElementById('timeslot-container'); + while (timeslot_container.firstChild) { + timeslot_container.removeChild(timeslot_container.firstChild); + } +} + +function get_slot_layout(time) { + let timezone = document.getElementById("appointment-timezone").value; + time = new Date(time); + let start_time_string = moment(time).tz(timezone).format("LT"); + let end_time = moment(time).tz(timezone).add(window.appointment_settings.appointment_duration, 'minutes'); + let end_time_string = end_time.format("LT"); + return `${start_time_string}
to ${end_time_string}`; +} + +function select_time() { + if (this.classList.contains('unavailable')) { + return; + } + let selected_element = document.getElementsByClassName('selected'); + if (!(selected_element.length > 0)) { + this.classList.add('selected'); + show_next_button(); + return; + } + selected_element = selected_element[0] + window.selected_time = this.id; + selected_element.classList.remove('selected'); + this.classList.add('selected'); + show_next_button(); +} + +function set_default_timeslot() { + let timeslots = document.getElementsByClassName('time-slot') + // Can't use a forEach here since, we need to break the loop after a timeslot is selected + for (let i = 0; i < timeslots.length; i++) { + const timeslot = timeslots[i]; + if (!timeslot.classList.contains('unavailable')) { + timeslot.classList.add('selected'); + break; + } + } +} + +function navigate_to_page(page_number) { + let page1 = document.getElementById('select-date-time'); + let page2 = document.getElementById('enter-details'); + switch (page_number) { + case 1: + page1.style.display = 'block'; + page2.style.display = 'none'; + break; + case 2: + page1.style.display = 'none'; + page2.style.display = 'block'; + break; + default: + break; + } +} + +function setup_details_page() { + navigate_to_page(2) + let date_container = document.getElementsByClassName('date-span')[0]; + let time_container = document.getElementsByClassName('time-span')[0]; + date_container.innerHTML = moment(window.selected_date).format("MMM Do YYYY"); + time_container.innerHTML = moment(window.selected_time, "HH:mm:ss").format("LT"); +} + +async function submit() { + let button = document.getElementById('submit-button'); + button.disabled = true; + let form = document.querySelector('#customer-form'); + if (!form.checkValidity()) { + form.reportValidity(); + button.disabled = false; + return; + } + let contact = get_form_data(); + let appointment = frappe.call({ + method: 'erpnext.www.book_appointment.index.create_appointment', + args: { + 'date': window.selected_date, + 'time': window.selected_time, + 'contact': contact, + 'tz':window.selected_timezone + }, + callback: (response)=>{ + if (response.message.status == "Unverified") { + frappe.show_alert("Please check your email to confirm the appointment") + } else { + frappe.show_alert("Appointment Created Successfully"); + } + setTimeout(()=>{ + let redirect_url = "/"; + if (window.appointment_settings.success_redirect_url){ + redirect_url += window.appointment_settings.success_redirect_url; + } + window.location.href = redirect_url;},5000) + }, + error: (err)=>{ + frappe.show_alert("Something went wrong please try again"); + button.disabled = false; + } + }); +} + +function get_form_data() { + contact = {}; + let inputs = ['name', 'skype', 'number', 'notes', 'email']; + inputs.forEach((id) => contact[id] = document.getElementById(`customer_${id}`).value) + return contact +} diff --git a/erpnext/www/book_appointment/index.py b/erpnext/www/book_appointment/index.py new file mode 100644 index 00000000000..7bfac89f308 --- /dev/null +++ b/erpnext/www/book_appointment/index.py @@ -0,0 +1,149 @@ +import frappe +import datetime +import json +import pytz + + +WEEKDAYS = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"] + +no_cache = 1 + + +def get_context(context): + is_enabled = frappe.db.get_single_value('Appointment Booking Settings', 'enable_scheduling') + if is_enabled: + return context + else: + frappe.local.flags.redirect_location = '/404' + raise frappe.Redirect + +@frappe.whitelist(allow_guest=True) +def get_appointment_settings(): + settings = frappe.get_doc('Appointment Booking Settings') + settings.holiday_list = frappe.get_doc('Holiday List', settings.holiday_list) + return settings + +@frappe.whitelist(allow_guest=True) +def get_timezones(): + import pytz + return pytz.all_timezones + +@frappe.whitelist(allow_guest=True) +def get_appointment_slots(date, timezone): + # Convert query to local timezones + format_string = '%Y-%m-%d %H:%M:%S' + query_start_time = datetime.datetime.strptime(date + ' 00:00:00', format_string) + query_end_time = datetime.datetime.strptime(date + ' 23:59:59', format_string) + query_start_time = convert_to_system_timezone(timezone, query_start_time) + query_end_time = convert_to_system_timezone(timezone, query_end_time) + now = convert_to_guest_timezone(timezone, datetime.datetime.now()) + + # Database queries + settings = frappe.get_doc('Appointment Booking Settings') + holiday_list = frappe.get_doc('Holiday List', settings.holiday_list) + timeslots = get_available_slots_between(query_start_time, query_end_time, settings) + + # Filter and convert timeslots + converted_timeslots = [] + for timeslot in timeslots: + converted_timeslot = convert_to_guest_timezone(timezone, timeslot) + # Check if holiday + if _is_holiday(converted_timeslot.date(), holiday_list): + converted_timeslots.append(dict(time=converted_timeslot, availability=False)) + continue + # Check availability + if check_availabilty(timeslot, settings) and converted_timeslot >= now: + converted_timeslots.append(dict(time=converted_timeslot, availability=True)) + else: + converted_timeslots.append(dict(time=converted_timeslot, availability=False)) + date_required = datetime.datetime.strptime(date + ' 00:00:00', format_string).date() + converted_timeslots = filter_timeslots(date_required, converted_timeslots) + return converted_timeslots + +def get_available_slots_between(query_start_time, query_end_time, settings): + records = _get_records(query_start_time, query_end_time, settings) + timeslots = [] + appointment_duration = datetime.timedelta( + minutes=settings.appointment_duration) + for record in records: + if record.day_of_week == WEEKDAYS[query_start_time.weekday()]: + current_time = _deltatime_to_datetime(query_start_time, record.from_time) + end_time = _deltatime_to_datetime(query_start_time, record.to_time) + else: + current_time = _deltatime_to_datetime(query_end_time, record.from_time) + end_time = _deltatime_to_datetime(query_end_time, record.to_time) + while current_time + appointment_duration <= end_time: + timeslots.append(current_time) + current_time += appointment_duration + return timeslots + + +@frappe.whitelist(allow_guest=True) +def create_appointment(date, time, tz, contact): + format_string = '%Y-%m-%d %H:%M:%S' + scheduled_time = datetime.datetime.strptime(date + " " + time, format_string) + # Strip tzinfo from datetime objects since it's handled by the doctype + scheduled_time = scheduled_time.replace(tzinfo = None) + scheduled_time = convert_to_system_timezone(tz, scheduled_time) + scheduled_time = scheduled_time.replace(tzinfo = None) + # Create a appointment document from form + appointment = frappe.new_doc('Appointment') + appointment.scheduled_time = scheduled_time + contact = json.loads(contact) + appointment.customer_name = contact.get('name', None) + appointment.customer_phone_number = contact.get('number', None) + appointment.customer_skype = contact.get('skype', None) + appointment.customer_details = contact.get('notes', None) + appointment.customer_email = contact.get('email', None) + appointment.status = 'Open' + appointment.insert() + return appointment + +# Helper Functions +def filter_timeslots(date, timeslots): + filtered_timeslots = [] + for timeslot in timeslots: + if(timeslot['time'].date() == date): + filtered_timeslots.append(timeslot) + return filtered_timeslots + +def convert_to_guest_timezone(guest_tz, datetimeobject): + guest_tz = pytz.timezone(guest_tz) + local_timezone = pytz.timezone(frappe.utils.get_time_zone()) + datetimeobject = local_timezone.localize(datetimeobject) + datetimeobject = datetimeobject.astimezone(guest_tz) + return datetimeobject + +def convert_to_system_timezone(guest_tz,datetimeobject): + guest_tz = pytz.timezone(guest_tz) + datetimeobject = guest_tz.localize(datetimeobject) + system_tz = pytz.timezone(frappe.utils.get_time_zone()) + datetimeobject = datetimeobject.astimezone(system_tz) + return datetimeobject + +def check_availabilty(timeslot, settings): + return frappe.db.count('Appointment', {'scheduled_time': timeslot}) < settings.number_of_agents + +def _is_holiday(date, holiday_list): + for holiday in holiday_list.holidays: + if holiday.holiday_date == date: + return True + return False + + +def _get_records(start_time, end_time, settings): + records = [] + for record in settings.availability_of_slots: + if record.day_of_week == WEEKDAYS[start_time.weekday()] or record.day_of_week == WEEKDAYS[end_time.weekday()]: + records.append(record) + return records + + +def _deltatime_to_datetime(date, deltatime): + time = (datetime.datetime.min + deltatime).time() + return datetime.datetime.combine(date.date(), time) + + +def _datetime_to_deltatime(date_time): + midnight = datetime.datetime.combine(date_time.date(), datetime.time.min) + return (date_time-midnight) \ No newline at end of file diff --git a/erpnext/www/book_appointment/verify/__init__.py b/erpnext/www/book_appointment/verify/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/www/book_appointment/verify/index.html b/erpnext/www/book_appointment/verify/index.html new file mode 100644 index 00000000000..ebb65b1f24e --- /dev/null +++ b/erpnext/www/book_appointment/verify/index.html @@ -0,0 +1,18 @@ +{% extends "templates/web.html" %} + +{% block title %} +{{ _("Verify Email") }} +{% endblock%} + +{% block page_content %} + + {% if success==True %} +
+ Your email has been verified and your appointment has been scheduled +
+ {% else %} +
+ Verification failed please check the link +
+ {% endif %} +{% endblock%} \ No newline at end of file diff --git a/erpnext/www/book_appointment/verify/index.py b/erpnext/www/book_appointment/verify/index.py new file mode 100644 index 00000000000..d4478ae34a8 --- /dev/null +++ b/erpnext/www/book_appointment/verify/index.py @@ -0,0 +1,20 @@ +import frappe + +from frappe.utils.verified_command import verify_request +@frappe.whitelist(allow_guest=True) +def get_context(context): + if not verify_request(): + context.success = False + return context + + email = frappe.form_dict['email'] + appointment_name = frappe.form_dict['appointment'] + + if email and appointment_name: + appointment = frappe.get_doc('Appointment',appointment_name) + appointment.set_verified(email) + context.success = True + return context + else: + context.success = False + return context \ No newline at end of file