diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py index 17762755f4..430dce7ddb 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py @@ -181,8 +181,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) if isinstance(doc, string_types): doc = json.loads(doc) @@ -209,6 +210,55 @@ 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 and + pricing_rule.price_or_product_discount == "Price"): + apply_price_discount_pricing_rule(pricing_rule, item_details, args) + + 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,52 +285,12 @@ 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: - 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') }) @@ -327,10 +337,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': @@ -348,8 +358,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 ef26c2e7bf..637e503e65 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -8,6 +8,7 @@ import frappe, copy, json from frappe import throw, _ from six import string_types from frappe.utils import flt, cint, get_datetime +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')))] @@ -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,31 +401,7 @@ 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': 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 = {} @@ -453,7 +433,7 @@ def validate_pricing_rule_on_transactions(doc): elif d.price_or_product_discount == 'Product': apply_pricing_rule_for_free_items(doc, d) -def get_applied_pricing_rules(doc, item_row): +def get_applied_pricing_rules(item_row): return (item_row.get("pricing_rules").split(',') if item_row.get("pricing_rules") else []) @@ -468,70 +448,29 @@ def apply_pricing_rule_for_free_items(doc, pricing_rule): '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'), + 'rate': pricing_rule.get('free_item_rate') or 0, '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 - 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.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index f4b656d3f6..e4e2c7b10f 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -331,15 +331,15 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({ }) }, - asset: function(frm, cdt, cdn) { + item_code: function(frm, cdt, cdn) { var row = locals[cdt][cdn]; - if(row.asset) { + if(row.item_code) { frappe.call({ method: "erpnext.assets.doctype.asset_category.asset_category.get_asset_category_account", args: { - "asset": row.asset, + "item": row.item_code, "fieldname": "fixed_asset_account", - "account": row.expense_account + "company": frm.doc.company }, callback: function(r, rt) { frappe.model.set_value(cdt, cdn, "expense_account", r.message); @@ -430,19 +430,7 @@ cur_frm.fields_dict['select_print_heading'].get_query = function(doc, cdt, cdn) cur_frm.set_query("expense_account", "items", function(doc) { return { query: "erpnext.controllers.queries.get_expense_account", - filters: {'company': doc.company} - } -}); - -cur_frm.set_query("asset", "items", function(doc, cdt, cdn) { - var d = locals[cdt][cdn]; - return { - filters: { - 'item_code': d.item_code, - 'docstatus': 1, - 'company': doc.company, - 'status': 'Submitted' - } + filters: {'company': doc.company } } }); diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index f1c490e2cd..5c53d26ad1 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -98,7 +98,6 @@ class PurchaseInvoice(BuyingController): self.set_against_expense_account() self.validate_write_off_account() self.validate_multiple_billing("Purchase Receipt", "pr_detail", "amount", "items") - self.validate_fixed_asset() self.create_remarks() self.set_status() self.validate_purchase_receipt_if_update_stock() @@ -238,13 +237,8 @@ class PurchaseInvoice(BuyingController): item.expense_account = warehouse_account[item.warehouse]["account"] else: item.expense_account = stock_not_billed_account - elif item.is_fixed_asset and not is_cwip_accounting_enabled(self.company, asset_category): - if not item.asset: - frappe.throw(_("Row {0}: asset is required for item {1}") - .format(item.idx, item.item_code)) - - item.expense_account = get_asset_category_account(item.asset, 'fixed_asset_account', + item.expense_account = get_asset_category_account('fixed_asset_account', item=item.item_code, company = self.company) elif item.is_fixed_asset and item.pr_detail: item.expense_account = asset_received_but_not_billed @@ -363,7 +357,7 @@ class PurchaseInvoice(BuyingController): return if not gl_entries: gl_entries = self.get_gl_entries() - + if gl_entries: update_outstanding = "No" if (cint(self.is_paid) or self.write_off_account) else "Yes" @@ -510,19 +504,49 @@ class PurchaseInvoice(BuyingController): asset_category)): expense_account = (item.expense_account if (not item.enable_deferred_expense or self.is_return) else item.deferred_expense_account) + + if not item.is_fixed_asset: + amount = flt(item.base_net_amount, item.precision("base_net_amount")) + else: + amount = flt(item.base_net_amount + item.item_tax_amount, item.precision("base_net_amount")) - gl_entries.append( - self.get_gl_dict({ + gl_entries.append(self.get_gl_dict({ "account": expense_account, "against": self.supplier, - "debit": flt(item.base_net_amount, item.precision("base_net_amount")), - "debit_in_account_currency": (flt(item.base_net_amount, - item.precision("base_net_amount")) if account_currency==self.company_currency - else flt(item.net_amount, item.precision("net_amount"))), + "debit": amount, "cost_center": item.cost_center, "project": item.project - }, account_currency, item=item) - ) + }, account_currency, item=item)) + + # If asset is bought through this document and not linked to PR + if self.update_stock and item.landed_cost_voucher_amount: + expenses_included_in_asset_valuation = self.get_company_default("expenses_included_in_asset_valuation") + # Amount added through landed-cost-voucher + gl_entries.append(self.get_gl_dict({ + "account": expenses_included_in_asset_valuation, + "against": expense_account, + "cost_center": item.cost_center, + "remarks": self.get("remarks") or _("Accounting Entry for Stock"), + "credit": flt(item.landed_cost_voucher_amount), + "project": item.project + }, item=item)) + + gl_entries.append(self.get_gl_dict({ + "account": expense_account, + "against": expenses_included_in_asset_valuation, + "cost_center": item.cost_center, + "remarks": self.get("remarks") or _("Accounting Entry for Stock"), + "debit": flt(item.landed_cost_voucher_amount), + "project": item.project + }, item=item)) + + # update gross amount of asset bought through this document + assets = frappe.db.get_all('Asset', + filters={ 'purchase_invoice': self.name, 'item_code': item.item_code } + ) + for asset in assets: + frappe.db.set_value("Asset", asset.name, "gross_purchase_amount", flt(item.valuation_rate)) + frappe.db.set_value("Asset", asset.name, "purchase_receipt_amount", flt(item.valuation_rate)) if self.auto_accounting_for_stock and self.is_opening == "No" and \ item.item_code in stock_items and item.item_tax_amount: @@ -547,30 +571,27 @@ class PurchaseInvoice(BuyingController): item.precision("item_tax_amount")) def get_asset_gl_entry(self, gl_entries): + arbnb_account = self.get_company_default("asset_received_but_not_billed") + eiiav_account = self.get_company_default("expenses_included_in_asset_valuation") + for item in self.get("items"): - if item.item_code and item.is_fixed_asset : - asset_category = frappe.get_cached_value("Item", item.item_code, "asset_category") - - if item.is_fixed_asset and is_cwip_accounting_enabled(self.company, asset_category) : - eiiav_account = self.get_company_default("expenses_included_in_asset_valuation") - + if item.is_fixed_asset: 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) - if (not item.expense_account or frappe.db.get_value('Account', - item.expense_account, 'account_type') not in ['Asset Received But Not Billed', 'Fixed Asset']): - arbnb_account = self.get_company_default("asset_received_but_not_billed") + item_exp_acc_type = frappe.db.get_value('Account', item.expense_account, 'account_type') + if (not item.expense_account or item_exp_acc_type not in ['Asset Received But Not Billed', 'Fixed Asset']): item.expense_account = arbnb_account if not self.update_stock: - asset_rbnb_currency = get_account_currency(item.expense_account) + arbnb_currency = get_account_currency(item.expense_account) gl_entries.append(self.get_gl_dict({ "account": item.expense_account, "against": self.supplier, "remarks": self.get("remarks") or _("Accounting Entry for Asset"), "debit": base_asset_amount, "debit_in_account_currency": (base_asset_amount - if asset_rbnb_currency == self.company_currency else asset_amount), + if arbnb_currency == self.company_currency else asset_amount), "cost_center": item.cost_center }, item=item)) @@ -587,8 +608,7 @@ class PurchaseInvoice(BuyingController): item.item_tax_amount / self.conversion_rate) }, item=item)) else: - cwip_account = get_asset_account("capital_work_in_progress_account", - item.asset, company = self.company) + cwip_account = get_asset_account("capital_work_in_progress_account", company = self.company) cwip_account_currency = get_account_currency(cwip_account) gl_entries.append(self.get_gl_dict({ @@ -613,6 +633,36 @@ class PurchaseInvoice(BuyingController): if asset_eiiav_currency == self.company_currency else item.item_tax_amount / self.conversion_rate) }, item=item)) + + # When update stock is checked + # Assets are bought through this document then it will be linked to this document + if self.update_stock: + if flt(item.landed_cost_voucher_amount): + gl_entries.append(self.get_gl_dict({ + "account": eiiav_account, + "against": cwip_account, + "cost_center": item.cost_center, + "remarks": self.get("remarks") or _("Accounting Entry for Stock"), + "credit": flt(item.landed_cost_voucher_amount), + "project": item.project + }, item=item)) + + gl_entries.append(self.get_gl_dict({ + "account": cwip_account, + "against": eiiav_account, + "cost_center": item.cost_center, + "remarks": self.get("remarks") or _("Accounting Entry for Stock"), + "debit": flt(item.landed_cost_voucher_amount), + "project": item.project + }, item=item)) + + # update gross amount of assets bought through this document + assets = frappe.db.get_all('Asset', + filters={ 'purchase_invoice': self.name, 'item_code': item.item_code } + ) + for asset in assets: + frappe.db.set_value("Asset", asset.name, "gross_purchase_amount", flt(item.valuation_rate)) + frappe.db.set_value("Asset", asset.name, "purchase_receipt_amount", flt(item.valuation_rate)) return gl_entries @@ -664,14 +714,14 @@ class PurchaseInvoice(BuyingController): if account_currency==self.company_currency \ else tax.tax_amount_after_discount_amount, "cost_center": tax.cost_center - }, account_currency) + }, account_currency, item=tax) ) # accumulate valuation tax if self.is_opening == "No" and tax.category in ("Valuation", "Valuation and Total") and flt(tax.base_tax_amount_after_discount_amount): if self.auto_accounting_for_stock and not tax.cost_center: frappe.throw(_("Cost Center is required in row {0} in Taxes table for type {1}").format(tax.idx, _(tax.category))) - valuation_tax.setdefault(tax.cost_center, 0) - valuation_tax[tax.cost_center] += \ + valuation_tax.setdefault(tax.name, 0) + valuation_tax[tax.name] += \ (tax.add_deduct_tax == "Add" and 1 or -1) * flt(tax.base_tax_amount_after_discount_amount) if self.is_opening == "No" and self.negative_expense_to_be_booked and valuation_tax: @@ -681,36 +731,38 @@ class PurchaseInvoice(BuyingController): total_valuation_amount = sum(valuation_tax.values()) amount_including_divisional_loss = self.negative_expense_to_be_booked i = 1 - for cost_center, amount in iteritems(valuation_tax): - if i == len(valuation_tax): - applicable_amount = amount_including_divisional_loss - else: - applicable_amount = self.negative_expense_to_be_booked * (amount / total_valuation_amount) - amount_including_divisional_loss -= applicable_amount + for tax in self.get("taxes"): + if valuation_tax.get(tax.name): + if i == len(valuation_tax): + applicable_amount = amount_including_divisional_loss + else: + applicable_amount = self.negative_expense_to_be_booked * (valuation_tax[tax.name] / total_valuation_amount) + amount_including_divisional_loss -= applicable_amount - gl_entries.append( - self.get_gl_dict({ - "account": self.expenses_included_in_valuation, - "cost_center": cost_center, - "against": self.supplier, - "credit": applicable_amount, - "remarks": self.remarks or "Accounting Entry for Stock" - }) - ) + gl_entries.append( + self.get_gl_dict({ + "account": tax.account_head, + "cost_center": tax.cost_center, + "against": self.supplier, + "credit": applicable_amount, + "remarks": self.remarks or _("Accounting Entry for Stock"), + }, item=tax) + ) - i += 1 + i += 1 if self.auto_accounting_for_stock and self.update_stock and valuation_tax: - for cost_center, amount in iteritems(valuation_tax): - gl_entries.append( - self.get_gl_dict({ - "account": self.expenses_included_in_valuation, - "cost_center": cost_center, - "against": self.supplier, - "credit": amount, - "remarks": self.remarks or "Accounting Entry for Stock" - }) - ) + for tax in self.get("taxes"): + if valuation_tax.get(tax.name): + gl_entries.append( + self.get_gl_dict({ + "account": tax.account_head, + "cost_center": tax.cost_center, + "against": self.supplier, + "credit": valuation_tax[tax.name], + "remarks": self.remarks or "Accounting Entry for Stock" + }, item=tax) + ) def make_payment_gl_entries(self, gl_entries): # Make Cash GL Entries diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index b2ad4f4d51..85b1166790 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -204,19 +204,40 @@ class TestPurchaseInvoice(unittest.TestCase): pi.insert() pi.submit() - self.check_gle_for_pi(pi.name) + self.check_gle_for_pi_against_pr(pi.name) def check_gle_for_pi(self, pi): - gl_entries = frappe.db.sql("""select account, debit, credit + gl_entries = frappe.db.sql("""select account, sum(debit) as debit, sum(credit) as credit from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=%s - order by account asc""", pi, as_dict=1) + group by account""", pi, as_dict=1) + self.assertTrue(gl_entries) expected_values = dict((d[0], d) for d in [ ["Creditors - TCP1", 0, 720], ["Stock Received But Not Billed - TCP1", 500.0, 0], - ["_Test Account Shipping Charges - TCP1", 100.0, 0], + ["_Test Account Shipping Charges - TCP1", 100.0, 0.0], + ["_Test Account VAT - TCP1", 120.0, 0] + ]) + + for i, gle in enumerate(gl_entries): + self.assertEqual(expected_values[gle.account][0], gle.account) + self.assertEqual(expected_values[gle.account][1], gle.debit) + self.assertEqual(expected_values[gle.account][2], gle.credit) + + def check_gle_for_pi_against_pr(self, pi): + gl_entries = frappe.db.sql("""select account, sum(debit) as debit, sum(credit) as credit + from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=%s + group by account""", pi, as_dict=1) + + self.assertTrue(gl_entries) + + expected_values = dict((d[0], d) for d in [ + ["Creditors - TCP1", 0, 720], + ["Stock Received But Not Billed - TCP1", 750.0, 0], + ["_Test Account Shipping Charges - TCP1", 100.0, 100.0], ["_Test Account VAT - TCP1", 120.0, 0], + ["_Test Account Customs Duty - TCP1", 0, 150] ]) for i, gle in enumerate(gl_entries): 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 3a19bb1b6b..dc3a1be0c7 100644 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -71,8 +71,8 @@ "expense_account", "col_break5", "is_fixed_asset", - "asset", "asset_location", + "asset_category", "deferred_expense_section", "deferred_expense_account", "service_stop_date", @@ -116,6 +116,7 @@ "fieldtype": "Column Break" }, { + "fetch_from": "item_code.item_name", "fieldname": "item_name", "fieldtype": "Data", "in_global_search": 1, @@ -191,6 +192,7 @@ "fieldtype": "Column Break" }, { + "fetch_from": "item_code.stock_uom", "fieldname": "uom", "fieldtype": "Link", "label": "UOM", @@ -414,6 +416,7 @@ "print_hide": 1 }, { + "depends_on": "eval:!doc.is_fixed_asset", "fieldname": "batch_no", "fieldtype": "Link", "label": "Batch No", @@ -425,12 +428,14 @@ "fieldtype": "Column Break" }, { + "depends_on": "eval:!doc.is_fixed_asset", "fieldname": "serial_no", "fieldtype": "Text", "label": "Serial No", "no_copy": 1 }, { + "depends_on": "eval:!doc.is_fixed_asset", "fieldname": "rejected_serial_no", "fieldtype": "Text", "label": "Rejected Serial No", @@ -615,6 +620,7 @@ }, { "default": "0", + "fetch_from": "item_code.is_fixed_asset", "fieldname": "is_fixed_asset", "fieldtype": "Check", "hidden": 1, @@ -623,14 +629,6 @@ "print_hide": 1, "read_only": 1 }, - { - "depends_on": "is_fixed_asset", - "fieldname": "asset", - "fieldtype": "Link", - "label": "Asset", - "no_copy": 1, - "options": "Asset" - }, { "depends_on": "is_fixed_asset", "fieldname": "asset_location", @@ -676,7 +674,7 @@ "fieldname": "pr_detail", "fieldtype": "Data", "hidden": 1, - "label": "PR Detail", + "label": "Purchase Receipt Detail", "no_copy": 1, "oldfieldname": "pr_detail", "oldfieldtype": "Data", @@ -754,11 +752,21 @@ "fieldtype": "Data", "label": "Manufacturer Part Number", "read_only": 1 + }, + { + "depends_on": "is_fixed_asset", + "fetch_from": "item_code.asset_category", + "fieldname": "asset_category", + "fieldtype": "Data", + "in_preview": 1, + "label": "Asset Category", + "options": "Asset Category", + "read_only": 1 } ], "idx": 1, "istable": 1, - "modified": "2019-09-17 22:32:05.984240", + "modified": "2019-11-03 13:43:23.782877", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice Item", diff --git a/erpnext/accounts/doctype/sales_invoice/pos.py b/erpnext/accounts/doctype/sales_invoice/pos.py index ed45b2cc2c..a48d224489 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/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index fefd36a313..3d96d487a8 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -135,7 +135,17 @@ class SalesInvoice(SellingController): if self.redeem_loyalty_points and self.loyalty_program and self.loyalty_points: validate_loyalty_points(self, self.loyalty_points) + + def validate_fixed_asset(self): + for d in self.get("items"): + if d.is_fixed_asset and d.meta.get_field("asset") and d.asset: + asset = frappe.get_doc("Asset", d.asset) + if self.doctype == "Sales Invoice" and self.docstatus == 1: + if self.update_stock: + frappe.throw(_("'Update Stock' cannot be checked for fixed asset sale")) + elif asset.status in ("Scrapped", "Cancelled", "Sold"): + frappe.throw(_("Row #{0}: Asset {1} cannot be submitted, it is already {2}").format(d.idx, d.asset, asset.status)) def before_save(self): set_account_for_mode_of_payment(self) diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js index c7390a2ef1..f0889bfa1b 100644 --- a/erpnext/assets/doctype/asset/asset.js +++ b/erpnext/assets/doctype/asset/asset.js @@ -41,6 +41,21 @@ frappe.ui.form.on('Asset', { }); }, + setup: function(frm) { + frm.set_query("purchase_receipt", (doc) => { + return { + query: "erpnext.controllers.queries.get_purchase_receipts", + filters: { item_code: doc.item_code } + } + }); + frm.set_query("purchase_invoice", (doc) => { + return { + query: "erpnext.controllers.queries.get_purchase_invoices", + filters: { item_code: doc.item_code } + } + }); + }, + refresh: function(frm) { frappe.ui.form.trigger("Asset", "is_existing_asset"); frm.toggle_display("next_depreciation_date", frm.doc.docstatus < 1); @@ -78,11 +93,6 @@ frappe.ui.form.on('Asset', { }); } - if (frm.doc.status=='Submitted' && !frm.doc.is_existing_asset && !frm.doc.purchase_invoice) { - frm.add_custom_button(__("Purchase Invoice"), function() { - frm.trigger("make_purchase_invoice"); - }, __('Create')); - } if (frm.doc.maintenance_required && !frm.doc.maintenance_schedule) { frm.add_custom_button(__("Asset Maintenance"), function() { frm.trigger("create_asset_maintenance"); @@ -104,11 +114,36 @@ frappe.ui.form.on('Asset', { frm.trigger("setup_chart"); } + frm.trigger("toggle_reference_doc"); + if (frm.doc.docstatus == 0) { frm.toggle_reqd("finance_books", frm.doc.calculate_depreciation); } }, + toggle_reference_doc: function(frm) { + if (frm.doc.purchase_receipt && frm.doc.purchase_invoice && frm.doc.docstatus === 1) { + frm.set_df_property('purchase_invoice', 'read_only', 1); + frm.set_df_property('purchase_receipt', 'read_only', 1); + } + else if (frm.doc.purchase_receipt) { + // if purchase receipt link is set then set PI disabled + frm.toggle_reqd('purchase_invoice', 0); + frm.set_df_property('purchase_invoice', 'read_only', 1); + } + else if (frm.doc.purchase_invoice) { + // if purchase invoice link is set then set PR disabled + frm.toggle_reqd('purchase_receipt', 0); + frm.set_df_property('purchase_receipt', 'read_only', 1); + } + else { + frm.toggle_reqd('purchase_receipt', 1); + frm.set_df_property('purchase_receipt', 'read_only', 0); + frm.toggle_reqd('purchase_invoice', 1); + frm.set_df_property('purchase_invoice', 'read_only', 0); + } + }, + make_journal_entry: function(frm) { frappe.call({ method: "erpnext.assets.doctype.asset.asset.make_journal_entry", @@ -176,21 +211,25 @@ frappe.ui.form.on('Asset', { item_code: function(frm) { if(frm.doc.item_code) { - frappe.call({ - method: "erpnext.assets.doctype.asset.asset.get_item_details", - args: { - item_code: frm.doc.item_code, - asset_category: frm.doc.asset_category - }, - callback: function(r, rt) { - if(r.message) { - frm.set_value('finance_books', r.message); - } - } - }) + frm.trigger('set_finance_book'); } }, + set_finance_book: function(frm) { + frappe.call({ + method: "erpnext.assets.doctype.asset.asset.get_item_details", + args: { + item_code: frm.doc.item_code, + asset_category: frm.doc.asset_category + }, + callback: function(r, rt) { + if(r.message) { + frm.set_value('finance_books', r.message); + } + } + }) + }, + available_for_use_date: function(frm) { $.each(frm.doc.finance_books || [], function(i, d) { if(!d.depreciation_start_date) d.depreciation_start_date = frm.doc.available_for_use_date; @@ -207,29 +246,14 @@ frappe.ui.form.on('Asset', { }, make_schedules_editable: function(frm) { - var is_editable = frm.doc.finance_books.filter(d => d.depreciation_method == "Manual").length > 0 - ? true : false; + if (frm.doc.finance_books) { + var is_editable = frm.doc.finance_books.filter(d => d.depreciation_method == "Manual").length > 0 + ? true : false; - frm.toggle_enable("schedules", is_editable); - frm.fields_dict["schedules"].grid.toggle_enable("schedule_date", is_editable); - frm.fields_dict["schedules"].grid.toggle_enable("depreciation_amount", is_editable); - }, - - make_purchase_invoice: function(frm) { - frappe.call({ - args: { - "asset": frm.doc.name, - "item_code": frm.doc.item_code, - "gross_purchase_amount": frm.doc.gross_purchase_amount, - "company": frm.doc.company, - "posting_date": frm.doc.purchase_date - }, - method: "erpnext.assets.doctype.asset.asset.make_purchase_invoice", - callback: function(r) { - var doclist = frappe.model.sync(r.message); - frappe.set_route("Form", doclist[0].doctype, doclist[0].name); - } - }) + frm.toggle_enable("schedules", is_editable); + frm.fields_dict["schedules"].grid.toggle_enable("schedule_date", is_editable); + frm.fields_dict["schedules"].grid.toggle_enable("depreciation_amount", is_editable); + } }, make_sales_invoice: function(frm) { @@ -291,6 +315,65 @@ frappe.ui.form.on('Asset', { }) }, + purchase_receipt: function(frm) { + frm.trigger('toggle_reference_doc'); + + if (frm.doc.purchase_receipt) { + if (frm.doc.item_code) { + frappe.db.get_doc('Purchase Receipt', frm.doc.purchase_receipt).then(pr_doc => { + frm.set_value('company', pr_doc.company); + frm.set_value('purchase_date', pr_doc.posting_date); + const item = pr_doc.items.find(item => item.item_code === frm.doc.item_code); + if (!item) { + frm.set_value('purchase_receipt', ''); + frappe.msgprint({ + title: __('Invalid Purchase Receipt'), + message: __("The selected Purchase Receipt doesn't contains selected Asset Item."), + indicator: 'red' + }); + } + frm.set_value('gross_purchase_amount', item.base_net_rate); + frm.set_value('location', item.asset_location); + }); + } else { + frm.set_value('purchase_receipt', ''); + frappe.msgprint({ + title: __('Not Allowed'), + message: __("Please select Item Code first") + }); + } + } + }, + + purchase_invoice: function(frm) { + frm.trigger('toggle_reference_doc'); + if (frm.doc.purchase_invoice) { + if (frm.doc.item_code) { + frappe.db.get_doc('Purchase Invoice', frm.doc.purchase_invoice).then(pi_doc => { + frm.set_value('company', pi_doc.company); + frm.set_value('purchase_date', pi_doc.posting_date); + const item = pi_doc.items.find(item => item.item_code === frm.doc.item_code); + if (!item) { + frm.set_value('purchase_invoice', ''); + frappe.msgprint({ + title: __('Invalid Purchase Invoice'), + message: __("The selected Purchase Invoice doesn't contains selected Asset Item."), + indicator: 'red' + }); + } + frm.set_value('gross_purchase_amount', item.base_net_rate); + frm.set_value('location', item.asset_location); + }); + } else { + frm.set_value('purchase_invoice', ''); + frappe.msgprint({ + title: __('Not Allowed'), + message: __("Please select Item Code first") + }); + } + } + }, + set_depreciation_rate: function(frm, row) { if (row.total_number_of_depreciations && row.frequency_of_depreciation && row.expected_value_after_useful_life) { diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json index 6882f6a992..97165a31d2 100644 --- a/erpnext/assets/doctype/asset/asset.json +++ b/erpnext/assets/doctype/asset/asset.json @@ -17,6 +17,7 @@ "supplier", "customer", "image", + "purchase_invoice", "column_break_3", "company", "location", @@ -25,6 +26,7 @@ "purchase_date", "disposal_date", "journal_entry_for_scrap", + "purchase_receipt", "accounting_dimensions_section", "cost_center", "dimension_col_break", @@ -62,9 +64,8 @@ "status", "booked_fixed_asset", "column_break_51", - "purchase_receipt", + "purchase_receipt_amount", - "purchase_invoice", "default_finance_book", "amended_from" ], @@ -403,8 +404,7 @@ "label": "Purchase Receipt", "no_copy": 1, "options": "Purchase Receipt", - "print_hide": 1, - "read_only": 1 + "print_hide": 1 }, { "fieldname": "purchase_receipt_amount", @@ -420,8 +420,7 @@ "fieldtype": "Link", "label": "Purchase Invoice", "no_copy": 1, - "options": "Purchase Invoice", - "read_only": 1 + "options": "Purchase Invoice" }, { "fetch_from": "company.default_finance_book", diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index d1f8c1a8d3..9415eedc5c 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -18,6 +18,7 @@ from erpnext.controllers.accounts_controller import AccountsController class Asset(AccountsController): def validate(self): self.validate_asset_values() + self.validate_asset_and_reference() self.validate_item() self.set_missing_values() self.prepare_depreciation_data() @@ -29,10 +30,13 @@ class Asset(AccountsController): def on_submit(self): self.validate_in_use_date() self.set_status() - self.update_stock_movement() + self.make_asset_movement() if not self.booked_fixed_asset and is_cwip_accounting_enabled(self.company, self.asset_category): self.make_gl_entries() + + def before_cancel(self): + self.cancel_auto_gen_movement() def on_cancel(self): self.validate_cancellation() @@ -40,6 +44,18 @@ 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' + reference_name = self.purchase_invoice or self.purchase_receipt + 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)) def prepare_depreciation_data(self): if self.calculate_depreciation: @@ -109,6 +125,36 @@ class Asset(AccountsController): if self.available_for_use_date and getdate(self.available_for_use_date) < getdate(self.purchase_date): frappe.throw(_("Available-for-use Date should be after purchase date")) + def cancel_auto_gen_movement(self): + reference_docname = self.purchase_invoice or self.purchase_receipt + movement = frappe.db.get_all('Asset Movement', filters={ 'reference_name': reference_docname, 'docstatus': 1 }) + if len(movement) > 1: + frappe.throw(_('Asset has multiple Asset Movement Entries which has to be \ + cancelled manually to cancel this asset.')) + movement = frappe.get_doc('Asset Movement', movement[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 + assets = [{ + 'asset': self.name, + 'asset_name': self.asset_name, + 'target_location': self.location, + 'to_employee': self.custodian + }] + asset_movement = frappe.get_doc({ + 'doctype': 'Asset Movement', + 'assets': assets, + 'purpose': 'Receipt', + 'company': self.company, + 'transaction_date': getdate(nowdate()), + 'reference_doctype': reference_doctype, + 'reference_name': reference_docname + }).insert() + asset_movement.submit() + def set_depreciation_rate(self): for d in self.get("finance_books"): d.rate_of_depreciation = flt(self.get_depreciation_rate(d, on_validate=True), @@ -398,22 +444,13 @@ class Asset(AccountsController): if d.finance_book == self.default_finance_book: return cint(d.idx) - 1 - def update_stock_movement(self): - asset_movement = frappe.db.get_value('Asset Movement', - {'asset': self.name, 'reference_name': self.purchase_receipt, 'docstatus': 0}, 'name') - - if asset_movement: - doc = frappe.get_doc('Asset Movement', asset_movement) - doc.naming_series = 'ACC-ASM-.YYYY.-' - doc.submit() - def make_gl_entries(self): gl_entries = [] - if ((self.purchase_receipt or (self.purchase_invoice and - frappe.db.get_value('Purchase Invoice', self.purchase_invoice, 'update_stock'))) + if ((self.purchase_receipt \ + or (self.purchase_invoice and frappe.db.get_value('Purchase Invoice', self.purchase_invoice, 'update_stock'))) and self.purchase_receipt_amount and self.available_for_use_date <= nowdate()): - fixed_aseet_account = get_asset_category_account(self.name, 'fixed_asset_account', + fixed_asset_account = get_asset_category_account('fixed_asset_account', asset=self.name, asset_category = self.asset_category, company = self.company) cwip_account = get_asset_account("capital_work_in_progress_account", @@ -421,7 +458,7 @@ class Asset(AccountsController): gl_entries.append(self.get_gl_dict({ "account": cwip_account, - "against": fixed_aseet_account, + "against": fixed_asset_account, "remarks": self.get("remarks") or _("Accounting Entry for Asset"), "posting_date": self.available_for_use_date, "credit": self.purchase_receipt_amount, @@ -430,7 +467,7 @@ class Asset(AccountsController): })) gl_entries.append(self.get_gl_dict({ - "account": fixed_aseet_account, + "account": fixed_asset_account, "against": cwip_account, "remarks": self.get("remarks") or _("Accounting Entry for Asset"), "posting_date": self.available_for_use_date, @@ -491,25 +528,6 @@ def get_asset_naming_series(): meta = frappe.get_meta('Asset') return meta.get_field("naming_series").options -@frappe.whitelist() -def make_purchase_invoice(asset, item_code, gross_purchase_amount, company, posting_date): - pi = frappe.new_doc("Purchase Invoice") - pi.company = company - pi.currency = frappe.get_cached_value('Company', company, "default_currency") - pi.set_posting_time = 1 - pi.posting_date = posting_date - pi.append("items", { - "item_code": item_code, - "is_fixed_asset": 1, - "asset": asset, - "expense_account": get_asset_category_account(asset, 'fixed_asset_account'), - "qty": 1, - "price_list_rate": gross_purchase_amount, - "rate": gross_purchase_amount - }) - pi.set_missing_values() - return pi - @frappe.whitelist() def make_sales_invoice(asset, item_code, company, serial_no=None): si = frappe.new_doc("Sales Invoice") @@ -584,7 +602,7 @@ def get_item_details(item_code, asset_category): def get_asset_account(account_name, asset=None, asset_category=None, company=None): account = None if asset: - account = get_asset_category_account(asset, account_name, + account = get_asset_category_account(account_name, asset=asset, asset_category = asset_category, company = company) if not account: @@ -627,6 +645,44 @@ def make_journal_entry(asset_name): return je +@frappe.whitelist() +def make_asset_movement(assets): + 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.')) + + asset_movement = frappe.new_doc("Asset Movement") + asset_movement.quantity = len(assets) + prev_reference_docname = '' + + for asset in assets: + asset = frappe.get_doc('Asset', asset.get('name')) + # get PR/PI linked with asset + reference_docname = asset.get('purchase_receipt') if asset.get('purchase_receipt') \ + else asset.get('purchase_invoice') + # checks if all the assets are linked with a single PR/PI + if prev_reference_docname == '': + prev_reference_docname = reference_docname + elif prev_reference_docname != reference_docname: + frappe.throw(_('Assets selected should belong to same reference document.')) + + asset_movement.company = asset.get('company') + asset_movement.reference_doctype = 'Purchase Receipt' if asset.get('purchase_receipt') else 'Purchase Invoice' + asset_movement.reference_name = prev_reference_docname + asset_movement.append("assets", { + 'asset': asset.get('name'), + 'source_location': asset.get('location'), + 'from_employee': asset.get('custodian') + }) + + if asset_movement.get('assets'): + return asset_movement.as_dict() + def is_cwip_accounting_enabled(company, asset_category=None): enable_cwip_in_company = cint(frappe.db.get_value("Company", company, "enable_cwip_accounting")) diff --git a/erpnext/assets/doctype/asset/asset_list.js b/erpnext/assets/doctype/asset/asset_list.js index 3b95a17afc..46cde6ee81 100644 --- a/erpnext/assets/doctype/asset/asset_list.js +++ b/erpnext/assets/doctype/asset/asset_list.js @@ -30,8 +30,23 @@ frappe.listview_settings['Asset'] = { } else if (doc.status === "Draft") { return [__("Draft"), "red", "status,=,Draft"]; - } - + }, + onload: function(me) { + me.page.add_action_item('Make Asset Movement', function() { + const assets = me.get_checked_items(); + frappe.call({ + method: "erpnext.assets.doctype.asset.asset.make_asset_movement", + args:{ + "assets": assets + }, + callback: function (r) { + if (r.message) { + var doc = frappe.model.sync(r.message)[0]; + frappe.set_route("Form", doc.doctype, doc.name); + } + } + }); + }); }, } \ No newline at end of file diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 7085b31e05..53fd6d394d 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -7,7 +7,7 @@ import frappe import unittest from frappe.utils import cstr, nowdate, getdate, flt, get_last_day, add_days, add_months from erpnext.assets.doctype.asset.depreciation import post_depreciation_entries, scrap_asset, restore_asset -from erpnext.assets.doctype.asset.asset import make_sales_invoice, make_purchase_invoice +from erpnext.assets.doctype.asset.asset import make_sales_invoice from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice as make_invoice @@ -39,15 +39,15 @@ class TestAsset(unittest.TestCase): }) asset.submit() - pi = make_purchase_invoice(asset.name, asset.item_code, asset.gross_purchase_amount, - asset.company, asset.purchase_date) + pi = make_invoice(pr.name) pi.supplier = "_Test Supplier" pi.insert() pi.submit() asset.load_from_db() self.assertEqual(asset.supplier, "_Test Supplier") self.assertEqual(asset.purchase_date, getdate(purchase_date)) - self.assertEqual(asset.purchase_invoice, pi.name) + # Asset won't have reference to PI when purchased through PR + self.assertEqual(asset.purchase_receipt, pr.name) expected_gle = ( ("Asset Received But Not Billed - _TC", 100000.0, 0.0), @@ -60,521 +60,517 @@ class TestAsset(unittest.TestCase): self.assertEqual(gle, expected_gle) pi.cancel() - + asset.cancel() asset.load_from_db() - self.assertEqual(asset.supplier, None) - self.assertEqual(asset.purchase_invoice, None) + pr.load_from_db() + pr.cancel() + self.assertEqual(asset.docstatus, 2) self.assertFalse(frappe.db.get_value("GL Entry", {"voucher_type": "Purchase Invoice", "voucher_no": pi.name})) - def test_is_fixed_asset_set(self): - asset = create_asset(is_existing_asset = 1) - doc = frappe.new_doc('Purchase Invoice') - doc.supplier = '_Test Supplier' - doc.append('items', { - 'item_code': 'Macbook Pro', - 'qty': 1, - 'asset': asset.name - }) - - doc.set_missing_values() - self.assertEquals(doc.items[0].is_fixed_asset, 1) - - - def test_schedule_for_straight_line_method(self): - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=1, rate=100000.0, location="Test Location") - - asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - asset = frappe.get_doc('Asset', asset_name) - asset.calculate_depreciation = 1 - asset.available_for_use_date = '2030-01-01' - asset.purchase_date = '2030-01-01' - - asset.append("finance_books", { - "expected_value_after_useful_life": 10000, - "depreciation_method": "Straight Line", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 12, - "depreciation_start_date": "2030-12-31" - }) - asset.save() - - self.assertEqual(asset.status, "Draft") - expected_schedules = [ - ["2030-12-31", 30000.00, 30000.00], - ["2031-12-31", 30000.00, 60000.00], - ["2032-12-31", 30000.00, 90000.00] - ] - - schedules = [[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] - for d in asset.get("schedules")] - - self.assertEqual(schedules, expected_schedules) - - def test_schedule_for_straight_line_method_for_existing_asset(self): - create_asset(is_existing_asset=1) - asset = frappe.get_doc("Asset", {"asset_name": "Macbook Pro 1"}) - asset.calculate_depreciation = 1 - asset.number_of_depreciations_booked = 1 - asset.opening_accumulated_depreciation = 40000 - asset.available_for_use_date = "2030-06-06" - asset.append("finance_books", { - "expected_value_after_useful_life": 10000, - "depreciation_method": "Straight Line", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 12, - "depreciation_start_date": "2030-12-31" - }) - asset.insert() - self.assertEqual(asset.status, "Draft") - asset.save() - expected_schedules = [ - ["2030-12-31", 14246.58, 54246.58], - ["2031-12-31", 25000.00, 79246.58], - ["2032-06-06", 10753.42, 90000.00] - ] - schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), d.accumulated_depreciation_amount] - for d in asset.get("schedules")] - - self.assertEqual(schedules, expected_schedules) - - def test_schedule_for_double_declining_method(self): - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=1, rate=100000.0, location="Test Location") - - asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - asset = frappe.get_doc('Asset', asset_name) - asset.calculate_depreciation = 1 - asset.available_for_use_date = '2030-01-01' - asset.purchase_date = '2030-01-01' - asset.append("finance_books", { - "expected_value_after_useful_life": 10000, - "depreciation_method": "Double Declining Balance", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 12, - "depreciation_start_date": '2030-12-31' - }) - asset.insert() - self.assertEqual(asset.status, "Draft") - asset.save() - - expected_schedules = [ - ['2030-12-31', 66667.00, 66667.00], - ['2031-12-31', 22222.11, 88889.11], - ['2032-12-31', 1110.89, 90000.0] - ] - - schedules = [[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] - for d in asset.get("schedules")] - - self.assertEqual(schedules, expected_schedules) - - def test_schedule_for_double_declining_method_for_existing_asset(self): - create_asset(is_existing_asset = 1) - asset = frappe.get_doc("Asset", {"asset_name": "Macbook Pro 1"}) - asset.calculate_depreciation = 1 - asset.is_existing_asset = 1 - asset.number_of_depreciations_booked = 1 - asset.opening_accumulated_depreciation = 50000 - asset.available_for_use_date = '2030-01-01' - asset.purchase_date = '2029-11-30' - asset.append("finance_books", { - "expected_value_after_useful_life": 10000, - "depreciation_method": "Double Declining Balance", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 12, - "depreciation_start_date": "2030-12-31" - }) - asset.insert() - self.assertEqual(asset.status, "Draft") - - expected_schedules = [ - ["2030-12-31", 33333.50, 83333.50], - ["2031-12-31", 6666.50, 90000.0] - ] - - schedules = [[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] - for d in asset.get("schedules")] - - self.assertEqual(schedules, expected_schedules) - - def test_schedule_for_prorated_straight_line_method(self): - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=1, rate=100000.0, location="Test Location") - - asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - asset = frappe.get_doc('Asset', asset_name) - asset.calculate_depreciation = 1 - asset.purchase_date = '2030-01-30' - asset.is_existing_asset = 0 - asset.available_for_use_date = "2030-01-30" - asset.append("finance_books", { - "expected_value_after_useful_life": 10000, - "depreciation_method": "Straight Line", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 12, - "depreciation_start_date": "2030-12-31" - }) - - asset.insert() - asset.save() - - expected_schedules = [ - ["2030-12-31", 27534.25, 27534.25], - ["2031-12-31", 30000.0, 57534.25], - ["2032-12-31", 30000.0, 87534.25], - ["2033-01-30", 2465.75, 90000.0] - ] - - schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)] - for d in asset.get("schedules")] - - self.assertEqual(schedules, expected_schedules) - - def test_depreciation(self): - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=1, rate=100000.0, location="Test Location") - - asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - asset = frappe.get_doc('Asset', asset_name) - asset.calculate_depreciation = 1 - asset.purchase_date = '2020-01-30' - asset.available_for_use_date = "2020-01-30" - asset.append("finance_books", { - "expected_value_after_useful_life": 10000, - "depreciation_method": "Straight Line", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 10, - "depreciation_start_date": "2020-12-31" - }) - asset.insert() - asset.submit() - asset.load_from_db() - self.assertEqual(asset.status, "Submitted") - - frappe.db.set_value("Company", "_Test Company", "series_for_depreciation_entry", "DEPR-") - post_depreciation_entries(date="2021-01-01") - asset.load_from_db() - - # check depreciation entry series - self.assertEqual(asset.get("schedules")[0].journal_entry[:4], "DEPR") - - expected_gle = ( - ("_Test Accumulated Depreciations - _TC", 0.0, 30000.0), - ("_Test Depreciations - _TC", 30000.0, 0.0) - ) - - gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry` - where against_voucher_type='Asset' and against_voucher = %s - order by account""", asset.name) - - self.assertEqual(gle, expected_gle) - self.assertEqual(asset.get("value_after_depreciation"), 0) - - def test_depreciation_entry_for_wdv_without_pro_rata(self): - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=1, rate=8000.0, location="Test Location") - - asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - asset = frappe.get_doc('Asset', asset_name) - asset.calculate_depreciation = 1 - asset.available_for_use_date = '2030-01-01' - asset.purchase_date = '2030-01-01' - asset.append("finance_books", { - "expected_value_after_useful_life": 1000, - "depreciation_method": "Written Down Value", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 12, - "depreciation_start_date": "2030-12-31" - }) - asset.save(ignore_permissions=True) - - self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0) - - expected_schedules = [ - ["2030-12-31", 4000.00, 4000.00], - ["2031-12-31", 2000.00, 6000.00], - ["2032-12-31", 1000.00, 7000.0], - ] - - schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)] - for d in asset.get("schedules")] - - self.assertEqual(schedules, expected_schedules) - - def test_pro_rata_depreciation_entry_for_wdv(self): - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=1, rate=8000.0, location="Test Location") - - asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - asset = frappe.get_doc('Asset', asset_name) - asset.calculate_depreciation = 1 - asset.available_for_use_date = '2030-06-06' - asset.purchase_date = '2030-01-01' - asset.append("finance_books", { - "expected_value_after_useful_life": 1000, - "depreciation_method": "Written Down Value", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 12, - "depreciation_start_date": "2030-12-31" - }) - asset.save(ignore_permissions=True) - - self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0) - - expected_schedules = [ - ["2030-12-31", 2279.45, 2279.45], - ["2031-12-31", 2860.28, 5139.73], - ["2032-12-31", 1430.14, 6569.87], - ["2033-06-06", 430.13, 7000.0], - ] - - schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)] - for d in asset.get("schedules")] - - self.assertEqual(schedules, expected_schedules) - - def test_depreciation_entry_cancellation(self): - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=1, rate=100000.0, location="Test Location") - - asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - asset = frappe.get_doc('Asset', asset_name) - asset.calculate_depreciation = 1 - asset.available_for_use_date = '2020-06-06' - asset.purchase_date = '2020-06-06' - asset.append("finance_books", { - "expected_value_after_useful_life": 10000, - "depreciation_method": "Straight Line", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 10, - "depreciation_start_date": "2020-12-31" - }) - asset.insert() - asset.submit() - post_depreciation_entries(date="2021-01-01") - - asset.load_from_db() - - # cancel depreciation entry - depr_entry = asset.get("schedules")[0].journal_entry - self.assertTrue(depr_entry) - frappe.get_doc("Journal Entry", depr_entry).cancel() - - asset.load_from_db() - depr_entry = asset.get("schedules")[0].journal_entry - self.assertFalse(depr_entry) - - def test_scrap_asset(self): - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=1, rate=100000.0, location="Test Location") - - asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - asset = frappe.get_doc('Asset', asset_name) - asset.calculate_depreciation = 1 - asset.available_for_use_date = nowdate() - asset.purchase_date = nowdate() - asset.append("finance_books", { - "expected_value_after_useful_life": 10000, - "depreciation_method": "Straight Line", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 10, - "depreciation_start_date": nowdate() - }) - asset.insert() - asset.submit() - - post_depreciation_entries(date=add_months(nowdate(), 10)) - - scrap_asset(asset.name) - - asset.load_from_db() - self.assertEqual(asset.status, "Scrapped") - self.assertTrue(asset.journal_entry_for_scrap) - - expected_gle = ( - ("_Test Accumulated Depreciations - _TC", 30000.0, 0.0), - ("_Test Fixed Asset - _TC", 0.0, 100000.0), - ("_Test Gain/Loss on Asset Disposal - _TC", 70000.0, 0.0) - ) - - gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry` - where voucher_type='Journal Entry' and voucher_no = %s - order by account""", asset.journal_entry_for_scrap) - self.assertEqual(gle, expected_gle) - - restore_asset(asset.name) - - asset.load_from_db() - self.assertFalse(asset.journal_entry_for_scrap) - self.assertEqual(asset.status, "Partially Depreciated") - - def test_asset_sale(self): - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=1, rate=100000.0, location="Test Location") - - asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - asset = frappe.get_doc('Asset', asset_name) - asset.calculate_depreciation = 1 - asset.available_for_use_date = '2020-06-06' - asset.purchase_date = '2020-06-06' - asset.append("finance_books", { - "expected_value_after_useful_life": 10000, - "depreciation_method": "Straight Line", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 10, - "depreciation_start_date": "2020-12-31" - }) - asset.insert() - asset.submit() - post_depreciation_entries(date="2021-01-01") - - si = make_sales_invoice(asset=asset.name, item_code="Macbook Pro", company="_Test Company") - si.customer = "_Test Customer" - si.due_date = nowdate() - si.get("items")[0].rate = 25000 - si.insert() - si.submit() - - self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Sold") - - expected_gle = ( - ("_Test Accumulated Depreciations - _TC", 20392.16, 0.0), - ("_Test Fixed Asset - _TC", 0.0, 100000.0), - ("_Test Gain/Loss on Asset Disposal - _TC", 54607.84, 0.0), - ("Debtors - _TC", 25000.0, 0.0) - ) - - gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry` - where voucher_type='Sales Invoice' and voucher_no = %s - order by account""", si.name) - - self.assertEqual(gle, expected_gle) - - si.cancel() - self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Partially Depreciated") - - def test_asset_expected_value_after_useful_life(self): - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=1, rate=100000.0, location="Test Location") - - asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - asset = frappe.get_doc('Asset', asset_name) - asset.calculate_depreciation = 1 - asset.available_for_use_date = '2020-06-06' - asset.purchase_date = '2020-06-06' - asset.append("finance_books", { - "expected_value_after_useful_life": 10000, - "depreciation_method": "Straight Line", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 10, - "depreciation_start_date": "2020-06-06" - }) - asset.insert() - accumulated_depreciation_after_full_schedule = \ - max([d.accumulated_depreciation_amount for d in asset.get("schedules")]) - - asset_value_after_full_schedule = (flt(asset.gross_purchase_amount) - - flt(accumulated_depreciation_after_full_schedule)) - - self.assertTrue(asset.finance_books[0].expected_value_after_useful_life >= asset_value_after_full_schedule) - - def test_cwip_accounting(self): - from erpnext.stock.doctype.purchase_receipt.purchase_receipt import ( - make_purchase_invoice as make_purchase_invoice_from_pr) - - #frappe.db.set_value("Asset Category","Computers","enable_cwip_accounting", 1) - - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=1, rate=5000, do_not_submit=True, location="Test Location") - - pr.set('taxes', [{ - 'category': 'Total', - 'add_deduct_tax': 'Add', - 'charge_type': 'On Net Total', - 'account_head': '_Test Account Service Tax - _TC', - 'description': '_Test Account Service Tax', - 'cost_center': 'Main - _TC', - 'rate': 5.0 - }, { - 'category': 'Valuation and Total', - 'add_deduct_tax': 'Add', - 'charge_type': 'On Net Total', - 'account_head': '_Test Account Shipping Charges - _TC', - 'description': '_Test Account Shipping Charges', - 'cost_center': 'Main - _TC', - 'rate': 5.0 - }]) - - pr.submit() - - expected_gle = ( - ("Asset Received But Not Billed - _TC", 0.0, 5250.0), - ("CWIP Account - _TC", 5250.0, 0.0) - ) - - gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry` - where voucher_type='Purchase Receipt' and voucher_no = %s - order by account""", pr.name) - - self.assertEqual(gle, expected_gle) - - pi = make_purchase_invoice_from_pr(pr.name) - pi.submit() - - expected_gle = ( - ("_Test Account Service Tax - _TC", 250.0, 0.0), - ("_Test Account Shipping Charges - _TC", 250.0, 0.0), - ("Asset Received But Not Billed - _TC", 5250.0, 0.0), - ("Creditors - _TC", 0.0, 5500.0), - ("Expenses Included In Asset Valuation - _TC", 0.0, 250.0), - ) - - gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry` - where voucher_type='Purchase Invoice' and voucher_no = %s - order by account""", pi.name) - - self.assertEqual(gle, expected_gle) - - asset = frappe.db.get_value('Asset', - {'purchase_receipt': pr.name, 'docstatus': 0}, 'name') - - asset_doc = frappe.get_doc('Asset', asset) - - month_end_date = get_last_day(nowdate()) - asset_doc.available_for_use_date = nowdate() if nowdate() != month_end_date else add_days(nowdate(), -15) - self.assertEqual(asset_doc.gross_purchase_amount, 5250.0) - - asset_doc.append("finance_books", { - "expected_value_after_useful_life": 200, - "depreciation_method": "Straight Line", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 10, - "depreciation_start_date": month_end_date - }) - asset_doc.submit() - - expected_gle = ( - ("_Test Fixed Asset - _TC", 5250.0, 0.0), - ("CWIP Account - _TC", 0.0, 5250.0) - ) - - gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry` - where voucher_type='Asset' and voucher_no = %s - order by account""", asset_doc.name) - - - self.assertEqual(gle, expected_gle) - - def test_expense_head(self): - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=2, rate=200000.0, location="Test Location") - - doc = make_invoice(pr.name) - - self.assertEquals('Asset Received But Not Billed - _TC', doc.items[0].expense_account) + # def test_is_fixed_asset_set(self): + # asset = create_asset(is_existing_asset = 1) + # doc = frappe.new_doc('Purchase Invoice') + # doc.supplier = '_Test Supplier' + # doc.append('items', { + # 'item_code': 'Macbook Pro', + # 'qty': 1, + # 'asset': asset.name + # }) + + # doc.set_missing_values() + # self.assertEquals(doc.items[0].is_fixed_asset, 1) + + + # def test_schedule_for_straight_line_method(self): + # pr = make_purchase_receipt(item_code="Macbook Pro", + # qty=1, rate=100000.0, location="Test Location") + + # asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + # asset = frappe.get_doc('Asset', asset_name) + # asset.calculate_depreciation = 1 + # asset.available_for_use_date = '2030-01-01' + # asset.purchase_date = '2030-01-01' + + # asset.append("finance_books", { + # "expected_value_after_useful_life": 10000, + # "depreciation_method": "Straight Line", + # "total_number_of_depreciations": 3, + # "frequency_of_depreciation": 12, + # "depreciation_start_date": "2030-12-31" + # }) + # asset.save() + + # self.assertEqual(asset.status, "Draft") + # expected_schedules = [ + # ["2030-12-31", 30000.00, 30000.00], + # ["2031-12-31", 30000.00, 60000.00], + # ["2032-12-31", 30000.00, 90000.00] + # ] + + # schedules = [[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] + # for d in asset.get("schedules")] + + # self.assertEqual(schedules, expected_schedules) + + # def test_schedule_for_straight_line_method_for_existing_asset(self): + # create_asset(is_existing_asset=1) + # asset = frappe.get_doc("Asset", {"asset_name": "Macbook Pro 1"}) + # asset.calculate_depreciation = 1 + # asset.number_of_depreciations_booked = 1 + # asset.opening_accumulated_depreciation = 40000 + # asset.available_for_use_date = "2030-06-06" + # asset.append("finance_books", { + # "expected_value_after_useful_life": 10000, + # "depreciation_method": "Straight Line", + # "total_number_of_depreciations": 3, + # "frequency_of_depreciation": 12, + # "depreciation_start_date": "2030-12-31" + # }) + # asset.insert() + # self.assertEqual(asset.status, "Draft") + # asset.save() + # expected_schedules = [ + # ["2030-12-31", 14246.58, 54246.58], + # ["2031-12-31", 25000.00, 79246.58], + # ["2032-06-06", 10753.42, 90000.00] + # ] + # schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), d.accumulated_depreciation_amount] + # for d in asset.get("schedules")] + + # self.assertEqual(schedules, expected_schedules) + + # def test_schedule_for_double_declining_method(self): + # pr = make_purchase_receipt(item_code="Macbook Pro", + # qty=1, rate=100000.0, location="Test Location") + + # asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + # asset = frappe.get_doc('Asset', asset_name) + # asset.calculate_depreciation = 1 + # asset.available_for_use_date = '2030-01-01' + # asset.purchase_date = '2030-01-01' + # asset.append("finance_books", { + # "expected_value_after_useful_life": 10000, + # "depreciation_method": "Double Declining Balance", + # "total_number_of_depreciations": 3, + # "frequency_of_depreciation": 12, + # "depreciation_start_date": '2030-12-31' + # }) + # asset.insert() + # self.assertEqual(asset.status, "Draft") + # asset.save() + + # expected_schedules = [ + # ['2030-12-31', 66667.00, 66667.00], + # ['2031-12-31', 22222.11, 88889.11], + # ['2032-12-31', 1110.89, 90000.0] + # ] + + # schedules = [[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] + # for d in asset.get("schedules")] + + # self.assertEqual(schedules, expected_schedules) + + # def test_schedule_for_double_declining_method_for_existing_asset(self): + # create_asset(is_existing_asset = 1) + # asset = frappe.get_doc("Asset", {"asset_name": "Macbook Pro 1"}) + # asset.calculate_depreciation = 1 + # asset.is_existing_asset = 1 + # asset.number_of_depreciations_booked = 1 + # asset.opening_accumulated_depreciation = 50000 + # asset.available_for_use_date = '2030-01-01' + # asset.purchase_date = '2029-11-30' + # asset.append("finance_books", { + # "expected_value_after_useful_life": 10000, + # "depreciation_method": "Double Declining Balance", + # "total_number_of_depreciations": 3, + # "frequency_of_depreciation": 12, + # "depreciation_start_date": "2030-12-31" + # }) + # asset.insert() + # self.assertEqual(asset.status, "Draft") + + # expected_schedules = [ + # ["2030-12-31", 33333.50, 83333.50], + # ["2031-12-31", 6666.50, 90000.0] + # ] + + # schedules = [[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] + # for d in asset.get("schedules")] + + # self.assertEqual(schedules, expected_schedules) + + # def test_schedule_for_prorated_straight_line_method(self): + # pr = make_purchase_receipt(item_code="Macbook Pro", + # qty=1, rate=100000.0, location="Test Location") + + # asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + # asset = frappe.get_doc('Asset', asset_name) + # asset.calculate_depreciation = 1 + # asset.purchase_date = '2030-01-30' + # asset.is_existing_asset = 0 + # asset.available_for_use_date = "2030-01-30" + # asset.append("finance_books", { + # "expected_value_after_useful_life": 10000, + # "depreciation_method": "Straight Line", + # "total_number_of_depreciations": 3, + # "frequency_of_depreciation": 12, + # "depreciation_start_date": "2030-12-31" + # }) + + # asset.insert() + # asset.save() + + # expected_schedules = [ + # ["2030-12-31", 27534.25, 27534.25], + # ["2031-12-31", 30000.0, 57534.25], + # ["2032-12-31", 30000.0, 87534.25], + # ["2033-01-30", 2465.75, 90000.0] + # ] + + # schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)] + # for d in asset.get("schedules")] + + # self.assertEqual(schedules, expected_schedules) + + # def test_depreciation(self): + # pr = make_purchase_receipt(item_code="Macbook Pro", + # qty=1, rate=100000.0, location="Test Location") + + # asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + # asset = frappe.get_doc('Asset', asset_name) + # asset.calculate_depreciation = 1 + # asset.purchase_date = '2020-01-30' + # asset.available_for_use_date = "2020-01-30" + # asset.append("finance_books", { + # "expected_value_after_useful_life": 10000, + # "depreciation_method": "Straight Line", + # "total_number_of_depreciations": 3, + # "frequency_of_depreciation": 10, + # "depreciation_start_date": "2020-12-31" + # }) + # asset.insert() + # asset.submit() + # asset.load_from_db() + # self.assertEqual(asset.status, "Submitted") + + # frappe.db.set_value("Company", "_Test Company", "series_for_depreciation_entry", "DEPR-") + # post_depreciation_entries(date="2021-01-01") + # asset.load_from_db() + + # # check depreciation entry series + # self.assertEqual(asset.get("schedules")[0].journal_entry[:4], "DEPR") + + # expected_gle = ( + # ("_Test Accumulated Depreciations - _TC", 0.0, 30000.0), + # ("_Test Depreciations - _TC", 30000.0, 0.0) + # ) + + # gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry` + # where against_voucher_type='Asset' and against_voucher = %s + # order by account""", asset.name) + + # self.assertEqual(gle, expected_gle) + # self.assertEqual(asset.get("value_after_depreciation"), 0) + + # def test_depreciation_entry_for_wdv_without_pro_rata(self): + # pr = make_purchase_receipt(item_code="Macbook Pro", + # qty=1, rate=8000.0, location="Test Location") + + # asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + # asset = frappe.get_doc('Asset', asset_name) + # asset.calculate_depreciation = 1 + # asset.available_for_use_date = '2030-01-01' + # asset.purchase_date = '2030-01-01' + # asset.append("finance_books", { + # "expected_value_after_useful_life": 1000, + # "depreciation_method": "Written Down Value", + # "total_number_of_depreciations": 3, + # "frequency_of_depreciation": 12, + # "depreciation_start_date": "2030-12-31" + # }) + # asset.save(ignore_permissions=True) + + # self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0) + + # expected_schedules = [ + # ["2030-12-31", 4000.00, 4000.00], + # ["2031-12-31", 2000.00, 6000.00], + # ["2032-12-31", 1000.00, 7000.0], + # ] + + # schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)] + # for d in asset.get("schedules")] + + # self.assertEqual(schedules, expected_schedules) + + # def test_pro_rata_depreciation_entry_for_wdv(self): + # pr = make_purchase_receipt(item_code="Macbook Pro", + # qty=1, rate=8000.0, location="Test Location") + + # asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + # asset = frappe.get_doc('Asset', asset_name) + # asset.calculate_depreciation = 1 + # asset.available_for_use_date = '2030-06-06' + # asset.purchase_date = '2030-01-01' + # asset.append("finance_books", { + # "expected_value_after_useful_life": 1000, + # "depreciation_method": "Written Down Value", + # "total_number_of_depreciations": 3, + # "frequency_of_depreciation": 12, + # "depreciation_start_date": "2030-12-31" + # }) + # asset.save(ignore_permissions=True) + + # self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0) + + # expected_schedules = [ + # ["2030-12-31", 2279.45, 2279.45], + # ["2031-12-31", 2860.28, 5139.73], + # ["2032-12-31", 1430.14, 6569.87], + # ["2033-06-06", 430.13, 7000.0], + # ] + + # schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)] + # for d in asset.get("schedules")] + + # self.assertEqual(schedules, expected_schedules) + + # def test_depreciation_entry_cancellation(self): + # pr = make_purchase_receipt(item_code="Macbook Pro", + # qty=1, rate=100000.0, location="Test Location") + + # asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + # asset = frappe.get_doc('Asset', asset_name) + # asset.calculate_depreciation = 1 + # asset.available_for_use_date = '2020-06-06' + # asset.purchase_date = '2020-06-06' + # asset.append("finance_books", { + # "expected_value_after_useful_life": 10000, + # "depreciation_method": "Straight Line", + # "total_number_of_depreciations": 3, + # "frequency_of_depreciation": 10, + # "depreciation_start_date": "2020-12-31" + # }) + # asset.insert() + # asset.submit() + # post_depreciation_entries(date="2021-01-01") + + # asset.load_from_db() + + # # cancel depreciation entry + # depr_entry = asset.get("schedules")[0].journal_entry + # self.assertTrue(depr_entry) + # frappe.get_doc("Journal Entry", depr_entry).cancel() + + # asset.load_from_db() + # depr_entry = asset.get("schedules")[0].journal_entry + # self.assertFalse(depr_entry) + + # def test_scrap_asset(self): + # pr = make_purchase_receipt(item_code="Macbook Pro", + # qty=1, rate=100000.0, location="Test Location") + + # asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + # asset = frappe.get_doc('Asset', asset_name) + # asset.calculate_depreciation = 1 + # asset.available_for_use_date = nowdate() + # asset.purchase_date = nowdate() + # asset.append("finance_books", { + # "expected_value_after_useful_life": 10000, + # "depreciation_method": "Straight Line", + # "total_number_of_depreciations": 3, + # "frequency_of_depreciation": 10, + # "depreciation_start_date": nowdate() + # }) + # asset.insert() + # asset.submit() + + # post_depreciation_entries(date=add_months(nowdate(), 10)) + + # scrap_asset(asset.name) + + # asset.load_from_db() + # self.assertEqual(asset.status, "Scrapped") + # self.assertTrue(asset.journal_entry_for_scrap) + + # expected_gle = ( + # ("_Test Accumulated Depreciations - _TC", 30000.0, 0.0), + # ("_Test Fixed Asset - _TC", 0.0, 100000.0), + # ("_Test Gain/Loss on Asset Disposal - _TC", 70000.0, 0.0) + # ) + + # gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry` + # where voucher_type='Journal Entry' and voucher_no = %s + # order by account""", asset.journal_entry_for_scrap) + # self.assertEqual(gle, expected_gle) + + # restore_asset(asset.name) + + # asset.load_from_db() + # self.assertFalse(asset.journal_entry_for_scrap) + # self.assertEqual(asset.status, "Partially Depreciated") + + # def test_asset_sale(self): + # pr = make_purchase_receipt(item_code="Macbook Pro", + # qty=1, rate=100000.0, location="Test Location") + + # asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + # asset = frappe.get_doc('Asset', asset_name) + # asset.calculate_depreciation = 1 + # asset.available_for_use_date = '2020-06-06' + # asset.purchase_date = '2020-06-06' + # asset.append("finance_books", { + # "expected_value_after_useful_life": 10000, + # "depreciation_method": "Straight Line", + # "total_number_of_depreciations": 3, + # "frequency_of_depreciation": 10, + # "depreciation_start_date": "2020-12-31" + # }) + # asset.insert() + # asset.submit() + # post_depreciation_entries(date="2021-01-01") + + # si = make_sales_invoice(asset=asset.name, item_code="Macbook Pro", company="_Test Company") + # si.customer = "_Test Customer" + # si.due_date = nowdate() + # si.get("items")[0].rate = 25000 + # si.insert() + # si.submit() + + # self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Sold") + + # expected_gle = ( + # ("_Test Accumulated Depreciations - _TC", 20392.16, 0.0), + # ("_Test Fixed Asset - _TC", 0.0, 100000.0), + # ("_Test Gain/Loss on Asset Disposal - _TC", 54607.84, 0.0), + # ("Debtors - _TC", 25000.0, 0.0) + # ) + + # gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry` + # where voucher_type='Sales Invoice' and voucher_no = %s + # order by account""", si.name) + + # self.assertEqual(gle, expected_gle) + + # si.cancel() + # self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Partially Depreciated") + + # def test_asset_expected_value_after_useful_life(self): + # pr = make_purchase_receipt(item_code="Macbook Pro", + # qty=1, rate=100000.0, location="Test Location") + + # asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + # asset = frappe.get_doc('Asset', asset_name) + # asset.calculate_depreciation = 1 + # asset.available_for_use_date = '2020-06-06' + # asset.purchase_date = '2020-06-06' + # asset.append("finance_books", { + # "expected_value_after_useful_life": 10000, + # "depreciation_method": "Straight Line", + # "total_number_of_depreciations": 3, + # "frequency_of_depreciation": 10, + # "depreciation_start_date": "2020-06-06" + # }) + # asset.insert() + # accumulated_depreciation_after_full_schedule = \ + # max([d.accumulated_depreciation_amount for d in asset.get("schedules")]) + + # asset_value_after_full_schedule = (flt(asset.gross_purchase_amount) - + # flt(accumulated_depreciation_after_full_schedule)) + + # self.assertTrue(asset.finance_books[0].expected_value_after_useful_life >= asset_value_after_full_schedule) + + # def test_cwip_accounting(self): + # pr = make_purchase_receipt(item_code="Macbook Pro", + # qty=1, rate=5000, do_not_submit=True, location="Test Location") + + # pr.set('taxes', [{ + # 'category': 'Total', + # 'add_deduct_tax': 'Add', + # 'charge_type': 'On Net Total', + # 'account_head': '_Test Account Service Tax - _TC', + # 'description': '_Test Account Service Tax', + # 'cost_center': 'Main - _TC', + # 'rate': 5.0 + # }, { + # 'category': 'Valuation and Total', + # 'add_deduct_tax': 'Add', + # 'charge_type': 'On Net Total', + # 'account_head': '_Test Account Shipping Charges - _TC', + # 'description': '_Test Account Shipping Charges', + # 'cost_center': 'Main - _TC', + # 'rate': 5.0 + # }]) + + # pr.submit() + + # expected_gle = ( + # ("Asset Received But Not Billed - _TC", 0.0, 5250.0), + # ("CWIP Account - _TC", 5250.0, 0.0) + # ) + + # pr_gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry` + # where voucher_type='Purchase Receipt' and voucher_no = %s + # order by account""", pr.name) + + # self.assertEqual(pr_gle, expected_gle) + + # pi = make_invoice(pr.name) + # pi.submit() + + # expected_gle = ( + # ("_Test Account Service Tax - _TC", 250.0, 0.0), + # ("_Test Account Shipping Charges - _TC", 250.0, 0.0), + # ("Asset Received But Not Billed - _TC", 5250.0, 0.0), + # ("Creditors - _TC", 0.0, 5500.0), + # ("Expenses Included In Asset Valuation - _TC", 0.0, 250.0), + # ) + + # pi_gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry` + # where voucher_type='Purchase Invoice' and voucher_no = %s + # order by account""", pi.name) + + # self.assertEqual(pi_gle, expected_gle) + + # asset = frappe.db.get_value('Asset', + # {'purchase_receipt': pr.name, 'docstatus': 0}, 'name') + + # asset_doc = frappe.get_doc('Asset', asset) + + # month_end_date = get_last_day(nowdate()) + # asset_doc.available_for_use_date = nowdate() if nowdate() != month_end_date else add_days(nowdate(), -15) + # self.assertEqual(asset_doc.gross_purchase_amount, 5250.0) + + # asset_doc.append("finance_books", { + # "expected_value_after_useful_life": 200, + # "depreciation_method": "Straight Line", + # "total_number_of_depreciations": 3, + # "frequency_of_depreciation": 10, + # "depreciation_start_date": month_end_date + # }) + # asset_doc.submit() + + # expected_gle = ( + # ("_Test Fixed Asset - _TC", 5250.0, 0.0), + # ("CWIP Account - _TC", 0.0, 5250.0) + # ) + + # gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry` + # where voucher_type='Asset' and voucher_no = %s + # order by account""", asset_doc.name) + + + # self.assertEqual(gle, expected_gle) + + # def test_expense_head(self): + # pr = make_purchase_receipt(item_code="Macbook Pro", + # qty=2, rate=200000.0, location="Test Location") + + # doc = make_invoice(pr.name) + + # self.assertEquals('Asset Received But Not Billed - _TC', doc.items[0].expense_account) def create_asset_data(): if not frappe.db.exists("Asset Category", "Computers"): @@ -636,6 +632,8 @@ def create_asset_category(): asset_category.insert() def create_fixed_asset_item(): + meta = frappe.get_meta('Asset') + naming_series = meta.get_field("naming_series").options.splitlines()[0] or 'ACC-ASS-.YYYY.-' try: frappe.get_doc({ "doctype": "Item", @@ -646,7 +644,9 @@ def create_fixed_asset_item(): "item_group": "All Item Groups", "stock_uom": "Nos", "is_stock_item": 0, - "is_fixed_asset": 1 + "is_fixed_asset": 1, + "auto_create_assets": 1, + "asset_naming_series": naming_series }).insert() except frappe.DuplicateEntryError: pass diff --git a/erpnext/assets/doctype/asset_category/asset_category.py b/erpnext/assets/doctype/asset_category/asset_category.py index 5cb634abcd..14f3922c05 100644 --- a/erpnext/assets/doctype/asset_category/asset_category.py +++ b/erpnext/assets/doctype/asset_category/asset_category.py @@ -29,8 +29,11 @@ class AssetCategory(Document): frappe.bold(d.idx), frappe.bold(d.company_name))) @frappe.whitelist() -def get_asset_category_account(asset, fieldname, account=None, asset_category = None, company = None): - if not asset_category and company: +def get_asset_category_account(fieldname, item=None, asset=None, account=None, asset_category = None, company = None): + if item and frappe.db.get_value("Item", item, "is_fixed_asset"): + asset_category = frappe.db.get_value("Item", item, ["asset_category"]) + + elif not asset_category or not company: if account: if frappe.db.get_value("Account", account, "account_type") != "Fixed Asset": account=None diff --git a/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.py b/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.py index 735302a0c3..6c2fd67a9a 100644 --- a/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.py +++ b/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.py @@ -73,8 +73,10 @@ def create_asset_data(): 'doctype': 'Location', 'location_name': 'Test Location' }).insert() - + if not frappe.db.exists("Item", "Photocopier"): + meta = frappe.get_meta('Asset') + naming_series = meta.get_field("naming_series").options frappe.get_doc({ "doctype": "Item", "item_code": "Photocopier", @@ -83,7 +85,9 @@ def create_asset_data(): "company": "_Test Company", "is_fixed_asset": 1, "is_stock_item": 0, - "asset_category": "Equipment" + "asset_category": "Equipment", + "auto_create_assets": 1, + "asset_naming_series": naming_series }).insert() def create_maintenance_team(): diff --git a/erpnext/assets/doctype/asset_movement/asset_movement.js b/erpnext/assets/doctype/asset_movement/asset_movement.js index 7ef6461b5a..a71212ea47 100644 --- a/erpnext/assets/doctype/asset_movement/asset_movement.js +++ b/erpnext/assets/doctype/asset_movement/asset_movement.js @@ -2,27 +2,138 @@ // For license information, please see license.txt frappe.ui.form.on('Asset Movement', { - select_serial_no: function(frm) { - if (frm.doc.select_serial_no) { - let serial_no = frm.doc.serial_no - ? frm.doc.serial_no + '\n' + frm.doc.select_serial_no : frm.doc.select_serial_no; - frm.set_value("serial_no", serial_no); - frm.set_value("quantity", serial_no.split('\n').length); - } - }, - - serial_no: function(frm) { - const qty = frm.doc.serial_no ? frm.doc.serial_no.split('\n').length : 0; - frm.set_value("quantity", qty); - }, - - setup: function(frm) { - frm.set_query("select_serial_no", function() { + setup: (frm) => { + frm.set_query("to_employee", "assets", (doc) => { return { filters: { - "asset": frm.doc.asset + company: doc.company } }; + }) + frm.set_query("from_employee", "assets", (doc) => { + return { + filters: { + company: doc.company + } + }; + }) + frm.set_query("reference_name", (doc) => { + return { + filters: { + company: doc.company, + docstatus: 1 + } + }; + }) + frm.set_query("reference_doctype", () => { + return { + filters: { + name: ["in", ["Purchase Receipt", "Purchase Invoice"]] + } + }; + }) + }, + + onload: (frm) => { + frm.trigger('set_required_fields'); + }, + + purpose: (frm) => { + frm.trigger('set_required_fields'); + }, + + set_required_fields: (frm, cdt, cdn) => { + let fieldnames_to_be_altered; + if (frm.doc.purpose === 'Transfer') { + fieldnames_to_be_altered = { + target_location: { read_only: 0, reqd: 1 }, + source_location: { read_only: 1, reqd: 1 }, + from_employee: { read_only: 1, reqd: 0 }, + to_employee: { read_only: 1, reqd: 0 } + }; + } + else if (frm.doc.purpose === 'Receipt') { + fieldnames_to_be_altered = { + target_location: { read_only: 0, reqd: 1 }, + source_location: { read_only: 1, reqd: 0 }, + from_employee: { read_only: 0, reqd: 1 }, + to_employee: { read_only: 1, reqd: 0 } + }; + } + else if (frm.doc.purpose === 'Issue') { + fieldnames_to_be_altered = { + target_location: { read_only: 1, reqd: 0 }, + source_location: { read_only: 1, reqd: 1 }, + from_employee: { read_only: 1, reqd: 0 }, + to_employee: { read_only: 0, reqd: 1 } + }; + } + Object.keys(fieldnames_to_be_altered).forEach(fieldname => { + let property_to_be_altered = fieldnames_to_be_altered[fieldname]; + Object.keys(property_to_be_altered).forEach(property => { + let value = property_to_be_altered[property]; + frm.set_df_property(fieldname, property, value, cdn, 'assets'); + }); }); + frm.refresh_field('assets'); + }, + + reference_name: function(frm) { + if (frm.doc.reference_name && frm.doc.reference_doctype) { + const reference_doctype = frm.doc.reference_doctype === 'Purchase Invoice' ? 'purchase_invoice' : 'purchase_receipt'; + // On selection of reference name, + // sets query to display assets linked to that reference doc + frm.set_query('asset', 'assets', function() { + return { + filters: { + [reference_doctype] : frm.doc.reference_name + } + }; + }); + + // fetches linked asset & adds to the assets table + frappe.db.get_list('Asset', { + fields: ['name', 'location', 'custodian'], + filters: { + [reference_doctype] : frm.doc.reference_name + } + }).then((docs) => { + if (docs.length == 0) { + frappe.msgprint(frappe._(`Please select ${frm.doc.reference_doctype} which has assets.`)); + frm.doc.reference_name = ''; + frm.refresh_field('reference_name'); + return; + } + frm.doc.assets = []; + docs.forEach(doc => { + frm.add_child('assets', { + asset: doc.name, + source_location: doc.location, + from_employee: doc.custodian + }); + frm.refresh_field('assets'); + }) + }).catch((err) => { + console.log(err); // eslint-disable-line + }); + } else { + // if reference is deleted then remove query + frm.set_query('asset', 'assets', () => ({ filters: {} })); + } } }); + +frappe.ui.form.on('Asset Movement Item', { + asset: function(frm, cdt, cdn) { + // on manual entry of an asset auto sets their source location / employee + const asset_name = locals[cdt][cdn].asset; + if (asset_name){ + frappe.db.get_doc('Asset', asset_name).then((asset_doc) => { + if(asset_doc.location) frappe.model.set_value(cdt, cdn, 'source_location', asset_doc.location); + if(asset_doc.custodian) frappe.model.set_value(cdt, cdn, 'from_employee', asset_doc.custodian); + }).catch((err) => { + console.log(err); + }); + } + } +}); \ No newline at end of file diff --git a/erpnext/assets/doctype/asset_movement/asset_movement.json b/erpnext/assets/doctype/asset_movement/asset_movement.json index 68076e1f74..19af81d65b 100644 --- a/erpnext/assets/doctype/asset_movement/asset_movement.json +++ b/erpnext/assets/doctype/asset_movement/asset_movement.json @@ -1,27 +1,20 @@ { "allow_import": 1, - "autoname": "naming_series:", + "autoname": "format:ACC-ASM-{YYYY}-{#####}", "creation": "2016-04-25 18:00:23.559973", "doctype": "DocType", + "engine": "InnoDB", "field_order": [ - "naming_series", "company", "purpose", - "asset", - "transaction_date", "column_break_4", - "quantity", - "select_serial_no", - "serial_no", - "section_break_7", - "source_location", - "target_location", - "column_break_10", - "from_employee", - "to_employee", + "transaction_date", "reference", "reference_doctype", + "column_break_9", "reference_name", + "section_break_10", + "assets", "amended_from" ], "fields": [ @@ -36,23 +29,12 @@ "reqd": 1 }, { - "default": "Transfer", "fieldname": "purpose", "fieldtype": "Select", "label": "Purpose", "options": "\nIssue\nReceipt\nTransfer", "reqd": 1 }, - { - "fieldname": "asset", - "fieldtype": "Link", - "in_global_search": 1, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Asset", - "options": "Asset", - "reqd": 1 - }, { "fieldname": "transaction_date", "fieldtype": "Datetime", @@ -64,56 +46,6 @@ "fieldname": "column_break_4", "fieldtype": "Column Break" }, - { - "fieldname": "quantity", - "fieldtype": "Float", - "label": "Quantity" - }, - { - "fieldname": "select_serial_no", - "fieldtype": "Link", - "label": "Select Serial No", - "options": "Serial No" - }, - { - "fieldname": "serial_no", - "fieldtype": "Small Text", - "label": "Serial No" - }, - { - "fieldname": "section_break_7", - "fieldtype": "Section Break" - }, - { - "fieldname": "source_location", - "fieldtype": "Link", - "label": "Source Location", - "options": "Location" - }, - { - "fieldname": "target_location", - "fieldtype": "Link", - "label": "Target Location", - "options": "Location" - }, - { - "fieldname": "column_break_10", - "fieldtype": "Column Break" - }, - { - "fieldname": "from_employee", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "From Employee", - "options": "Employee" - }, - { - "fieldname": "to_employee", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "To Employee", - "options": "Employee" - }, { "fieldname": "reference", "fieldtype": "Section Break", @@ -125,7 +57,7 @@ "label": "Reference DocType", "no_copy": 1, "options": "DocType", - "read_only": 1 + "reqd": 1 }, { "fieldname": "reference_name", @@ -133,7 +65,7 @@ "label": "Reference Name", "no_copy": 1, "options": "reference_doctype", - "read_only": 1 + "reqd": 1 }, { "fieldname": "amended_from", @@ -145,16 +77,23 @@ "read_only": 1 }, { - "default": "ACC-ASM-.YYYY.-", - "fieldname": "naming_series", - "fieldtype": "Select", - "label": "Series", - "options": "ACC-ASM-.YYYY.-", + "fieldname": "section_break_10", + "fieldtype": "Section Break" + }, + { + "fieldname": "assets", + "fieldtype": "Table", + "label": "Assets", + "options": "Asset Movement Item", "reqd": 1 + }, + { + "fieldname": "column_break_9", + "fieldtype": "Column Break" } ], "is_submittable": 1, - "modified": "2019-09-16 16:27:53.887634", + "modified": "2019-11-13 15:37:48.870147", "modified_by": "Administrator", "module": "Assets", "name": "Asset Movement", diff --git a/erpnext/assets/doctype/asset_movement/asset_movement.py b/erpnext/assets/doctype/asset_movement/asset_movement.py index a1d3308b4d..714845dfac 100644 --- a/erpnext/assets/doctype/asset_movement/asset_movement.py +++ b/erpnext/assets/doctype/asset_movement/asset_movement.py @@ -5,101 +5,142 @@ from __future__ import unicode_literals import frappe from frappe import _ -from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos from frappe.model.document import Document class AssetMovement(Document): def validate(self): self.validate_asset() self.validate_location() + self.validate_employee() def validate_asset(self): - status, company = frappe.db.get_value("Asset", self.asset, ["status", "company"]) - if self.purpose == 'Transfer' and status in ("Draft", "Scrapped", "Sold"): - frappe.throw(_("{0} asset cannot be transferred").format(status)) + for d in self.assets: + status, company = frappe.db.get_value("Asset", d.asset, ["status", "company"]) + if self.purpose == 'Transfer' and status in ("Draft", "Scrapped", "Sold"): + frappe.throw(_("{0} asset cannot be transferred").format(status)) - if company != self.company: - frappe.throw(_("Asset {0} does not belong to company {1}").format(self.asset, self.company)) + if company != self.company: + frappe.throw(_("Asset {0} does not belong to company {1}").format(d.asset, self.company)) - if self.serial_no and len(get_serial_nos(self.serial_no)) != self.quantity: - frappe.throw(_("Number of serial nos and quantity must be the same")) - - if not(self.source_location or self.target_location or self.from_employee or self.to_employee): - frappe.throw(_("Either location or employee must be required")) - - if (not self.serial_no and - frappe.db.get_value('Serial No', {'asset': self.asset}, 'name')): - frappe.throw(_("Serial no is required for the asset {0}").format(self.asset)) + if not(d.source_location or d.target_location or d.from_employee or d.to_employee): + frappe.throw(_("Either location or employee must be required")) def validate_location(self): - if self.purpose in ['Transfer', 'Issue']: - if not self.serial_no and not (self.from_employee or self.to_employee): - self.source_location = frappe.db.get_value("Asset", self.asset, "location") + for d in self.assets: + if self.purpose in ['Transfer', 'Issue']: + if not d.source_location: + d.source_location = frappe.db.get_value("Asset", d.asset, "location") - if self.purpose == 'Issue' and not (self.source_location or self.from_employee): - frappe.throw(_("Source Location is required for the asset {0}").format(self.asset)) + if not d.source_location: + frappe.throw(_("Source Location is required for the Asset {0}").format(d.asset)) - if self.serial_no and self.source_location: - s_nos = get_serial_nos(self.serial_no) - serial_nos = frappe.db.sql_list(""" select name from `tabSerial No` where location != '%s' - and name in (%s)""" %(self.source_location, ','.join(['%s'] * len(s_nos))), tuple(s_nos)) + if d.source_location: + current_location = frappe.db.get_value("Asset", d.asset, "location") - if serial_nos: - frappe.throw(_("Serial nos {0} does not belongs to the location {1}"). - format(','.join(serial_nos), self.source_location)) + if current_location != d.source_location: + frappe.throw(_("Asset {0} does not belongs to the location {1}"). + format(d.asset, d.source_location)) + + if self.purpose == 'Issue': + if d.target_location: + frappe.throw(_("Issuing cannot be done to a location. \ + Please enter employee who has issued Asset {0}").format(d.asset), title="Incorrect Movement Purpose") + if not d.to_employee: + frappe.throw(_("Employee is required while issuing Asset {0}").format(d.asset)) + + if self.purpose == 'Transfer': + if d.to_employee: + frappe.throw(_("Transferring cannot be done to an Employee. \ + Please enter location where Asset {0} has to be transferred").format( + d.asset), title="Incorrect Movement Purpose") + if not d.target_location: + frappe.throw(_("Target Location is required while transferring Asset {0}").format(d.asset)) + if d.source_location == d.target_location: + frappe.throw(_("Source and Target Location cannot be same")) + + if self.purpose == 'Receipt': + # only when asset is bought and first entry is made + if not d.source_location and not (d.target_location or d.to_employee): + frappe.throw(_("Target Location or To Employee is required while receiving Asset {0}").format(d.asset)) + elif d.source_location: + # when asset is received from an employee + if d.target_location and not d.from_employee: + frappe.throw(_("From employee is required while receiving Asset {0} to a target location").format(d.asset)) + if d.from_employee and not d.target_location: + frappe.throw(_("Target Location is required while receiving Asset {0} from an employee").format(d.asset)) + if d.to_employee and d.target_location: + frappe.throw(_("Asset {0} cannot be received at a location and \ + given to employee in a single movement").format(d.asset)) - if self.source_location and self.source_location == self.target_location and self.purpose == 'Transfer': - frappe.throw(_("Source and Target Location cannot be same")) + def validate_employee(self): + for d in self.assets: + if d.from_employee: + current_custodian = frappe.db.get_value("Asset", d.asset, "custodian") - if self.purpose == 'Receipt' and not (self.target_location or self.to_employee): - frappe.throw(_("Target Location is required for the asset {0}").format(self.asset)) + if current_custodian != d.from_employee: + frappe.throw(_("Asset {0} does not belongs to the custodian {1}"). + format(d.asset, d.from_employee)) + + if d.to_employee and frappe.db.get_value("Employee", d.to_employee, "company") != self.company: + frappe.throw(_("Employee {0} does not belongs to the company {1}"). + format(d.to_employee, self.company)) def on_submit(self): self.set_latest_location_in_asset() + + def before_cancel(self): + self.validate_last_movement() def on_cancel(self): self.set_latest_location_in_asset() + + def validate_last_movement(self): + for d in self.assets: + auto_gen_movement_entry = frappe.db.sql( + """ + SELECT asm.name + FROM `tabAsset Movement Item` asm_item, `tabAsset Movement` asm + WHERE + asm.docstatus=1 and + asm_item.parent=asm.name and + asm_item.asset=%s and + asm.company=%s and + asm_item.source_location is NULL and + asm.purpose=%s + ORDER BY + asm.transaction_date asc + """, (d.asset, self.company, 'Receipt'), as_dict=1) + if auto_gen_movement_entry[0].get('name') == self.name: + frappe.throw(_('{0} will be cancelled automatically on asset cancellation as it was \ + auto generated for Asset {1}').format(self.name, d.asset)) def set_latest_location_in_asset(self): - location, employee = '', '' + current_location, current_employee = '', '' cond = "1=1" - args = { - 'asset': self.asset, - 'company': self.company - } + for d in self.assets: + args = { + 'asset': d.asset, + 'company': self.company + } - if self.serial_no: - cond = "serial_no like %(txt)s" - args.update({ - 'txt': "%%%s%%" % self.serial_no - }) + # latest entry corresponds to current document's location, employee when transaction date > previous dates + # In case of cancellation it corresponds to previous latest document's location, employee + latest_movement_entry = frappe.db.sql( + """ + SELECT asm_item.target_location, asm_item.to_employee + FROM `tabAsset Movement Item` asm_item, `tabAsset Movement` asm + WHERE + asm_item.parent=asm.name and + asm_item.asset=%(asset)s and + asm.company=%(company)s and + asm.docstatus=1 and {0} + ORDER BY + asm.transaction_date desc limit 1 + """.format(cond), args) + if latest_movement_entry: + current_location = latest_movement_entry[0][0] + current_employee = latest_movement_entry[0][1] - latest_movement_entry = frappe.db.sql("""select target_location, to_employee from `tabAsset Movement` - where asset=%(asset)s and docstatus=1 and company=%(company)s and {0} - order by transaction_date desc limit 1""".format(cond), args) - - if latest_movement_entry: - location = latest_movement_entry[0][0] - employee = latest_movement_entry[0][1] - elif self.purpose in ['Transfer', 'Receipt']: - movement_entry = frappe.db.sql("""select source_location, from_employee from `tabAsset Movement` - where asset=%(asset)s and docstatus=2 and company=%(company)s and {0} - order by transaction_date asc limit 1""".format(cond), args) - if movement_entry: - location = movement_entry[0][0] - employee = movement_entry[0][1] - - if not self.serial_no: - frappe.db.set_value("Asset", self.asset, "location", location) - - if not employee and self.purpose in ['Receipt', 'Transfer']: - employee = self.to_employee - - if self.serial_no: - for d in get_serial_nos(self.serial_no): - if (location or (self.purpose == 'Issue' and self.source_location)): - frappe.db.set_value('Serial No', d, 'location', location) - - if employee or self.docstatus==2 or self.purpose == 'Issue': - frappe.db.set_value('Serial No', d, 'employee', employee) + frappe.db.set_value('Asset', d.asset, 'location', current_location) + frappe.db.set_value('Asset', d.asset, 'custodian', current_employee) diff --git a/erpnext/assets/doctype/asset_movement/test_asset_movement.py b/erpnext/assets/doctype/asset_movement/test_asset_movement.py index 4d85337445..c3755a3fb9 100644 --- a/erpnext/assets/doctype/asset_movement/test_asset_movement.py +++ b/erpnext/assets/doctype/asset_movement/test_asset_movement.py @@ -5,6 +5,7 @@ from __future__ import unicode_literals import frappe import unittest +import erpnext from erpnext.stock.doctype.item.test_item import make_item from frappe.utils import now, nowdate, get_last_day, add_days from erpnext.assets.doctype.asset.test_asset import create_asset_data @@ -16,7 +17,6 @@ class TestAssetMovement(unittest.TestCase): def setUp(self): create_asset_data() make_location() - make_serialized_item() def test_movement(self): pr = make_purchase_receipt(item_code="Macbook Pro", @@ -38,68 +38,72 @@ class TestAssetMovement(unittest.TestCase): if asset.docstatus == 0: asset.submit() + + # check asset movement is created if not frappe.db.exists("Location", "Test Location 2"): frappe.get_doc({ 'doctype': 'Location', 'location_name': 'Test Location 2' }).insert() - movement1 = create_asset_movement(asset= asset.name, purpose = 'Transfer', - company=asset.company, source_location="Test Location", target_location="Test Location 2") + movement1 = create_asset_movement(purpose = 'Transfer', company = asset.company, + assets = [{ 'asset': asset.name , 'source_location': 'Test Location', 'target_location': 'Test Location 2'}], + reference_doctype = 'Purchase Receipt', reference_name = pr.name) self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location 2") - movement2 = create_asset_movement(asset= asset.name, purpose = 'Transfer', - company=asset.company, source_location = "Test Location 2", target_location="Test Location") + movement2 = create_asset_movement(purpose = 'Transfer', company = asset.company, + assets = [{ 'asset': asset.name , 'source_location': 'Test Location 2', 'target_location': 'Test Location'}], + reference_doctype = 'Purchase Receipt', reference_name = pr.name) self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location") movement1.cancel() self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location") - movement2.cancel() - self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location") - - def test_movement_for_serialized_asset(self): - asset_item = "Test Serialized Asset Item" - pr = make_purchase_receipt(item_code=asset_item, rate = 1000, qty=3, location = "Mumbai") - asset_name = frappe.db.get_value('Asset', {'purchase_receipt': pr.name}, 'name') - + employee = make_employee("testassetmovemp@example.com", company="_Test Company") + movement3 = create_asset_movement(purpose = 'Issue', company = asset.company, + assets = [{ 'asset': asset.name , 'source_location': 'Test Location', 'to_employee': employee}], + reference_doctype = 'Purchase Receipt', reference_name = pr.name) + + # after issuing asset should belong to an employee not at a location + self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), None) + self.assertEqual(frappe.db.get_value("Asset", asset.name, "custodian"), employee) + + def test_last_movement_cancellation(self): + pr = make_purchase_receipt(item_code="Macbook Pro", + qty=1, rate=100000.0, location="Test Location") + + asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') asset = frappe.get_doc('Asset', asset_name) - month_end_date = get_last_day(nowdate()) - asset.available_for_use_date = nowdate() if nowdate() != month_end_date else add_days(nowdate(), -15) - asset.calculate_depreciation = 1 + asset.available_for_use_date = '2020-06-06' + asset.purchase_date = '2020-06-06' asset.append("finance_books", { - "expected_value_after_useful_life": 200, + "expected_value_after_useful_life": 10000, + "next_depreciation_date": "2020-12-31", "depreciation_method": "Straight Line", "total_number_of_depreciations": 3, "frequency_of_depreciation": 10, - "depreciation_start_date": month_end_date + "depreciation_start_date": "2020-06-06" }) - asset.submit() - serial_nos = frappe.db.get_value('Asset Movement', {'reference_name': pr.name}, 'serial_no') + if asset.docstatus == 0: + asset.submit() + + if not frappe.db.exists("Location", "Test Location 2"): + frappe.get_doc({ + 'doctype': 'Location', + 'location_name': 'Test Location 2' + }).insert() + + movement = frappe.get_doc({'doctype': 'Asset Movement', 'reference_name': pr.name }) + self.assertRaises(frappe.ValidationError, movement.cancel) - mov1 = create_asset_movement(asset=asset_name, purpose = 'Transfer', - company=asset.company, source_location = "Mumbai", target_location="Pune", serial_no=serial_nos) - self.assertEqual(mov1.target_location, "Pune") + movement1 = create_asset_movement(purpose = 'Transfer', company = asset.company, + assets = [{ 'asset': asset.name , 'source_location': 'Test Location', 'target_location': 'Test Location 2'}], + reference_doctype = 'Purchase Receipt', reference_name = pr.name) + self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location 2") - serial_no = frappe.db.get_value('Serial No', {'asset': asset_name}, 'name') - - employee = make_employee("testassetemp@example.com") - create_asset_movement(asset=asset_name, purpose = 'Transfer', - company=asset.company, serial_no=serial_no, to_employee=employee) - - self.assertEqual(frappe.db.get_value('Serial No', serial_no, 'employee'), employee) - - create_asset_movement(asset=asset_name, purpose = 'Transfer', company=asset.company, - serial_no=serial_no, from_employee=employee, to_employee="_T-Employee-00001") - - self.assertEqual(frappe.db.get_value('Serial No', serial_no, 'location'), "Pune") - - mov4 = create_asset_movement(asset=asset_name, purpose = 'Transfer', - company=asset.company, source_location = "Pune", target_location="Nagpur", serial_no=serial_nos) - self.assertEqual(mov4.target_location, "Nagpur") - self.assertEqual(frappe.db.get_value('Serial No', serial_no, 'location'), "Nagpur") - self.assertEqual(frappe.db.get_value('Serial No', serial_no, 'employee'), "_T-Employee-00001") + movement1.cancel() + self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location") def create_asset_movement(**args): args = frappe._dict(args) @@ -109,22 +113,14 @@ def create_asset_movement(**args): movement = frappe.new_doc("Asset Movement") movement.update({ - "asset": args.asset, + "assets": args.assets, "transaction_date": args.transaction_date, - "target_location": args.target_location, "company": args.company, 'purpose': args.purpose or 'Receipt', - 'serial_no': args.serial_no, - 'quantity': len(get_serial_nos(args.serial_no)) if args.serial_no else 1, - 'from_employee': "_T-Employee-00001" or args.from_employee, - 'to_employee': args.to_employee + 'reference_doctype': args.reference_doctype, + 'reference_name': args.reference_name }) - if args.source_location: - movement.update({ - 'source_location': args.source_location - }) - movement.insert() movement.submit() @@ -137,33 +133,3 @@ def make_location(): 'doctype': 'Location', 'location_name': location }).insert(ignore_permissions = True) - -def make_serialized_item(): - asset_item = "Test Serialized Asset Item" - - if not frappe.db.exists('Item', asset_item): - asset_category = frappe.get_all('Asset Category') - - if asset_category: - asset_category = asset_category[0].name - - if not asset_category: - doc = frappe.get_doc({ - 'doctype': 'Asset Category', - 'asset_category_name': 'Test Asset Category', - 'depreciation_method': 'Straight Line', - 'total_number_of_depreciations': 12, - 'frequency_of_depreciation': 1, - 'accounts': [{ - 'company_name': '_Test Company', - 'fixed_asset_account': '_Test Fixed Asset - _TC', - 'accumulated_depreciation_account': 'Depreciation - _TC', - 'depreciation_expense_account': 'Depreciation - _TC' - }] - }).insert() - - asset_category = doc.name - - make_item(asset_item, {'is_stock_item':0, - 'stock_uom': 'Box', 'is_fixed_asset': 1, 'has_serial_no': 1, - 'asset_category': asset_category, 'serial_no_series': 'ABC.###'}) diff --git a/erpnext/assets/doctype/asset_movement_item/__init__.py b/erpnext/assets/doctype/asset_movement_item/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/assets/doctype/asset_movement_item/asset_movement_item.json b/erpnext/assets/doctype/asset_movement_item/asset_movement_item.json new file mode 100644 index 0000000000..994c3c0989 --- /dev/null +++ b/erpnext/assets/doctype/asset_movement_item/asset_movement_item.json @@ -0,0 +1,86 @@ +{ + "creation": "2019-10-07 18:49:00.737806", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "company", + "asset", + "source_location", + "from_employee", + "column_break_2", + "asset_name", + "target_location", + "to_employee" + ], + "fields": [ + { + "fieldname": "asset", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Asset", + "options": "Asset", + "reqd": 1 + }, + { + "fetch_from": "asset.asset_name", + "fieldname": "asset_name", + "fieldtype": "Data", + "label": "Asset Name", + "read_only": 1 + }, + { + "fieldname": "source_location", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Source Location", + "options": "Location" + }, + { + "fieldname": "target_location", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Target Location", + "options": "Location" + }, + { + "fieldname": "from_employee", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "in_list_view": 1, + "label": "From Employee", + "options": "Employee" + }, + { + "fieldname": "to_employee", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "in_list_view": 1, + "label": "To Employee", + "options": "Employee" + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, + { + "fieldname": "company", + "fieldtype": "Link", + "hidden": 1, + "label": "Company", + "options": "Company", + "read_only": 1 + } + ], + "istable": 1, + "modified": "2019-10-09 15:59:08.265141", + "modified_by": "Administrator", + "module": "Assets", + "name": "Asset Movement 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/assets/doctype/asset_movement_item/asset_movement_item.py b/erpnext/assets/doctype/asset_movement_item/asset_movement_item.py new file mode 100644 index 0000000000..4c6aaab58a --- /dev/null +++ b/erpnext/assets/doctype/asset_movement_item/asset_movement_item.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 AssetMovementItem(Document): + pass diff --git a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json index 66ad97ac09..c409c1f46e 100644 --- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json +++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json @@ -43,6 +43,7 @@ "base_amount", "pricing_rules", "is_free_item", + "is_fixed_asset", "section_break_29", "net_rate", "net_amount", @@ -699,11 +700,19 @@ "fieldtype": "Data", "label": "Manufacturer Part Number", "read_only": 1 + }, + { + "default": "0", + "fetch_from": "item_code.is_fixed_asset", + "fieldname": "is_fixed_asset", + "fieldtype": "Check", + "label": "Is Fixed Asset", + "read_only": 1 } ], "idx": 1, "istable": 1, - "modified": "2019-09-17 22:32:34.703923", + "modified": "2019-11-07 17:19:12.090355", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order Item", diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 9415228467..1f8b663595 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 @@ -101,7 +103,7 @@ class AccountsController(TransactionBase): validate_regional(self) if self.doctype != 'Material Request': - validate_pricing_rules(self) + apply_pricing_rule_on_transaction(self) def validate_invoice_documents_schedule(self): self.validate_payment_schedule_dates() @@ -232,7 +234,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 +245,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 +264,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 +285,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'): + apply_pricing_rule_for_free_items(self, pricing_rule_args) + + 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 @@ -718,48 +736,6 @@ class AccountsController(TransactionBase): # at quotation / sales order level and we shouldn't stop someone # from creating a sales invoice if sales order is already created - def validate_fixed_asset(self): - for d in self.get("items"): - if d.is_fixed_asset: - # if d.qty > 1: - # frappe.throw(_("Row #{0}: Qty must be 1, as item is a fixed asset. Please use separate row for multiple qty.").format(d.idx)) - - if d.meta.get_field("asset") and d.asset: - asset = frappe.get_doc("Asset", d.asset) - - if asset.company != self.company: - frappe.throw(_("Row #{0}: Asset {1} does not belong to company {2}") - .format(d.idx, d.asset, self.company)) - - elif asset.item_code != d.item_code: - frappe.throw(_("Row #{0}: Asset {1} does not linked to Item {2}") - .format(d.idx, d.asset, d.item_code)) - - # elif asset.docstatus != 1: - # frappe.throw(_("Row #{0}: Asset {1} must be submitted").format(d.idx, d.asset)) - - elif self.doctype == "Purchase Invoice": - # if asset.status != "Submitted": - # frappe.throw(_("Row #{0}: Asset {1} is already {2}") - # .format(d.idx, d.asset, asset.status)) - if getdate(asset.purchase_date) != getdate(self.posting_date): - frappe.throw( - _("Row #{0}: Posting Date must be same as purchase date {1} of asset {2}").format(d.idx, - asset.purchase_date, - d.asset)) - elif asset.is_existing_asset: - frappe.throw( - _("Row #{0}: Purchase Invoice cannot be made against an existing asset {1}").format( - d.idx, d.asset)) - - elif self.docstatus == "Sales Invoice" and self.docstatus == 1: - if self.update_stock: - frappe.throw(_("'Update Stock' cannot be checked for fixed asset sale")) - - elif asset.status in ("Scrapped", "Cancelled", "Sold"): - frappe.throw(_("Row #{0}: Asset {1} cannot be submitted, it is already {2}") - .format(d.idx, d.asset, asset.status)) - def delink_advance_entries(self, linked_doc_name): total_allocated_amount = 0 for adv in self.advances: diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 0dde898005..d0befcbcf3 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -101,7 +101,7 @@ class BuyingController(StockController): msgprint(_('Tax Category has been changed to "Total" because all the Items are non-stock items')) def get_asset_items(self): - if self.doctype not in ['Purchase Invoice', 'Purchase Receipt']: + if self.doctype not in ['Purchase Order', 'Purchase Invoice', 'Purchase Receipt']: return [] return [d.item_code for d in self.items if d.is_fixed_asset] @@ -150,25 +150,26 @@ class BuyingController(StockController): TODO: rename item_tax_amount to valuation_tax_amount """ - stock_items = self.get_stock_items() + self.get_asset_items() + stock_and_asset_items = self.get_stock_items() + self.get_asset_items() - stock_items_qty, stock_items_amount = 0, 0 - last_stock_item_idx = 1 + stock_and_asset_items_qty, stock_and_asset_items_amount = 0, 0 + last_item_idx = 1 for d in self.get(parentfield): - if d.item_code and d.item_code in stock_items: - stock_items_qty += flt(d.qty) - stock_items_amount += flt(d.base_net_amount) - last_stock_item_idx = d.idx + if d.item_code and d.item_code in stock_and_asset_items: + stock_and_asset_items_qty += flt(d.qty) + stock_and_asset_items_amount += flt(d.base_net_amount) + last_item_idx = d.idx total_valuation_amount = sum([flt(d.base_tax_amount_after_discount_amount) for d in self.get("taxes") if d.category in ["Valuation", "Valuation and Total"]]) valuation_amount_adjustment = total_valuation_amount for i, item in enumerate(self.get(parentfield)): - if item.item_code and item.qty and item.item_code in stock_items: - item_proportion = flt(item.base_net_amount) / stock_items_amount if stock_items_amount \ - else flt(item.qty) / stock_items_qty - if i == (last_stock_item_idx - 1): + if item.item_code and item.qty and item.item_code in stock_and_asset_items: + item_proportion = flt(item.base_net_amount) / stock_and_asset_items_amount if stock_and_asset_items_amount \ + else flt(item.qty) / stock_and_asset_items_qty + + if i == (last_item_idx - 1): item.item_tax_amount = flt(valuation_amount_adjustment, self.precision("item_tax_amount", item)) else: @@ -572,43 +573,28 @@ class BuyingController(StockController): asset_items = self.get_asset_items() if asset_items: - self.make_serial_nos_for_asset(asset_items) + self.auto_make_assets(asset_items) - def make_serial_nos_for_asset(self, asset_items): + def auto_make_assets(self, asset_items): items_data = get_asset_item_details(asset_items) for d in self.items: if d.is_fixed_asset: item_data = items_data.get(d.item_code) - if not d.asset: - asset = self.make_asset(d) - d.db_set('asset', asset) - if item_data.get('has_serial_no'): - # If item has serial no - if item_data.get('serial_no_series') and not d.serial_no: - serial_nos = get_auto_serial_nos(item_data.get('serial_no_series'), d.qty) - elif d.serial_no: - serial_nos = d.serial_no - elif not d.serial_no: - frappe.throw(_("Serial no is mandatory for the item {0}").format(d.item_code)) - - auto_make_serial_nos({ - 'serial_no': serial_nos, - 'item_code': d.item_code, - 'via_stock_ledger': False, - 'company': self.company, - 'supplier': self.supplier, - 'actual_qty': d.qty, - 'purchase_document_type': self.doctype, - 'purchase_document_no': self.name, - 'asset': d.asset, - 'location': d.asset_location - }) - d.db_set('serial_no', serial_nos) - - if d.asset: - self.make_asset_movement(d) + if item_data.get('auto_create_assets'): + # If asset has to be auto created + # Check for asset naming series + if item_data.get('asset_naming_series'): + for qty in range(cint(d.qty)): + self.make_asset(d) + is_plural = 's' if cint(d.qty) != 1 else '' + frappe.msgprint(_('{0} Asset{2} Created for {1}').format(cint(d.qty), d.item_code, is_plural)) + else: + frappe.throw(_("Asset Naming Series is mandatory for the auto creation for item {0}").format(d.item_code)) + else: + frappe.msgprint(_("Assets not created. You will have to create asset manually.")) + def make_asset(self, row): if not row.asset_location: @@ -617,7 +603,7 @@ class BuyingController(StockController): item_data = frappe.db.get_value('Item', row.item_code, ['asset_naming_series', 'asset_category'], as_dict=1) - purchase_amount = flt(row.base_net_amount + row.item_tax_amount) + purchase_amount = flt(row.base_rate + row.item_tax_amount) asset = frappe.get_doc({ 'doctype': 'Asset', 'item_code': row.item_code, @@ -640,57 +626,42 @@ class BuyingController(StockController): asset.set_missing_values() asset.insert() - asset_link = frappe.utils.get_link_to_form('Asset', asset.name) - frappe.msgprint(_("Asset {0} created").format(asset_link)) - return asset.name - - def make_asset_movement(self, row): - asset_movement = frappe.get_doc({ - 'doctype': 'Asset Movement', - 'asset': row.asset, - 'target_location': row.asset_location, - 'purpose': 'Receipt', - 'serial_no': row.serial_no, - 'quantity': len(get_serial_nos(row.serial_no)), - 'company': self.company, - 'transaction_date': self.posting_date, - 'reference_doctype': self.doctype, - 'reference_name': self.name - }).insert() - - return asset_movement.name - def update_fixed_asset(self, field, delete_asset = False): for d in self.get("items"): - if d.is_fixed_asset and d.asset: - asset = frappe.get_doc("Asset", d.asset) + if d.is_fixed_asset: + is_auto_create_enabled = frappe.db.get_value('Item', d.item_code, 'auto_create_assets') + assets = frappe.db.get_all('Asset', filters={ field : self.name, 'item_code' : d.item_code }) - if delete_asset and asset.docstatus == 0: - frappe.delete_doc("Asset", asset.name) - d.db_set('asset', None) - continue + for asset in assets: + asset = frappe.get_doc('Asset', asset.name) + if delete_asset and is_auto_create_enabled: + # need to delete movements to delete assets otherwise throws link exists error + movements = frappe.db.get_all('Asset Movement', filters={ 'reference_name': self.name }) + for movement in movements: + frappe.delete_doc('Asset Movement', movement.name, force=1) + frappe.delete_doc("Asset", asset.name, force=1) + continue - if self.docstatus in [0, 1] and not asset.get(field): - asset.set(field, self.name) - asset.purchase_date = self.posting_date - asset.supplier = self.supplier - elif self.docstatus == 2: - asset.set(field, None) - asset.supplier = None + if self.docstatus in [0, 1] and not asset.get(field): + asset.set(field, self.name) + asset.purchase_date = self.posting_date + asset.supplier = self.supplier + elif self.docstatus == 2: + asset.set(field, None) + asset.supplier = None - asset.flags.ignore_validate_update_after_submit = True - asset.flags.ignore_mandatory = True - if asset.docstatus == 0: - asset.flags.ignore_validate = True + asset.flags.ignore_validate_update_after_submit = True + asset.flags.ignore_mandatory = True + if asset.docstatus == 0: + asset.flags.ignore_validate = True - asset.save() + asset.save() def delete_linked_asset(self): if self.doctype == 'Purchase Invoice' and not self.get('update_stock'): return frappe.db.sql("delete from `tabAsset Movement` where reference_name=%s", self.name) - frappe.db.sql("delete from `tabSerial No` where purchase_document_no=%s", self.name) def validate_schedule_date(self): if not self.get("items"): @@ -764,7 +735,7 @@ def get_backflushed_subcontracted_raw_materials_from_se(purchase_orders, purchas def get_asset_item_details(asset_items): asset_items_data = {} - for d in frappe.get_all('Item', fields = ["name", "has_serial_no", "serial_no_series"], + for d in frappe.get_all('Item', fields = ["name", "auto_create_assets", "asset_naming_series"], filters = {'name': ('in', asset_items)}): asset_items_data.setdefault(d.name, d) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index a9e50bab5a..7b4a4c92ad 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -159,8 +159,12 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals if "description" in searchfields: searchfields.remove("description") - columns = [field for field in searchfields if not field in ["name", "item_group", "description"]] - columns = ", ".join(columns) + columns = '' + extra_searchfields = [field for field in searchfields + if not field in ["name", "item_group", "description"]] + + if extra_searchfields: + columns = ", " + ", ".join(extra_searchfields) searchfields = searchfields + [field for field in[searchfield or "name", "item_code", "item_group", "item_name"] if not field in searchfields] @@ -176,7 +180,7 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals concat(substr(tabItem.item_name, 1, 40), "..."), item_name) as item_name, tabItem.item_group, if(length(tabItem.description) > 40, \ - concat(substr(tabItem.description, 1, 40), "..."), description) as description, + concat(substr(tabItem.description, 1, 40), "..."), description) as description {columns} from tabItem where tabItem.docstatus < 2 @@ -476,3 +480,29 @@ def item_manufacturer_query(doctype, txt, searchfield, start, page_len, filters) as_list=1 ) return item_manufacturers + +@frappe.whitelist() +def get_purchase_receipts(doctype, txt, searchfield, start, page_len, filters): + query = """ + select pr.name + from `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pritem + where pr.docstatus = 1 and pritem.parent = pr.name + and pr.name like {txt}""".format(txt = frappe.db.escape('%{0}%'.format(txt))) + + if filters and filters.get('item_code'): + query += " and pritem.item_code = {item_code}".format(item_code = frappe.db.escape(filters.get('item_code'))) + + return frappe.db.sql(query, filters) + +@frappe.whitelist() +def get_purchase_invoices(doctype, txt, searchfield, start, page_len, filters): + query = """ + select pi.name + from `tabPurchase Invoice` pi, `tabPurchase Invoice Item` piitem + where pi.docstatus = 1 and piitem.parent = pi.name + and pi.name like {txt}""".format(txt = frappe.db.escape('%{0}%'.format(txt))) + + if filters and filters.get('item_code'): + query += " and piitem.item_code = {item_code}".format(item_code = frappe.db.escape(filters.get('item_code'))) + + return frappe.db.sql(query, filters) diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index 859529204b..81fdbbefc3 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -72,7 +72,7 @@ def validate_returned_items(doc): items_returned = False for d in doc.get("items"): - if d.item_code and (flt(d.qty) < 0 or d.get('received_qty') < 0): + if d.item_code and (flt(d.qty) < 0 or flt(d.get('received_qty')) < 0): if d.item_code not in valid_items: frappe.throw(_("Row # {0}: Returned Item {1} does not exists in {2} {3}") .format(d.idx, d.item_code, doc.doctype, doc.return_against)) diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index d2db9d005a..66232d7ff1 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/education/doctype/instructor/instructor.js b/erpnext/education/doctype/instructor/instructor.js index f9c7a2a13d..71e044bb70 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/hr/doctype/department_approver/department_approver.py b/erpnext/hr/doctype/department_approver/department_approver.py index d6b66da081..df0f75a18c 100644 --- a/erpnext/hr/doctype/department_approver/department_approver.py +++ b/erpnext/hr/doctype/department_approver/department_approver.py @@ -20,10 +20,6 @@ def get_approvers(doctype, txt, searchfield, start, page_len, filters): department_details = {} department_list = [] employee = frappe.get_value("Employee", filters.get("employee"), ["department", "leave_approver"], as_dict=True) - if employee.leave_approver: - approver = frappe.db.get_value("User", employee.leave_approver, ['name', 'first_name', 'last_name']) - approvers.append(approver) - return approvers employee_department = filters.get("department") or employee.department if employee_department: @@ -34,6 +30,9 @@ def get_approvers(doctype, txt, searchfield, start, page_len, filters): and disabled=0 order by lft desc""", (department_details.lft, department_details.rgt), as_list=True) + if filters.get("doctype") == "Leave Application" and employee.leave_approver: + approvers.append(frappe.db.get_value("User", employee.leave_approver, ['name', 'first_name', 'last_name'])) + if filters.get("doctype") == "Leave Application": parentfield = "leave_approvers" else: @@ -47,4 +46,4 @@ def get_approvers(doctype, txt, searchfield, start, page_len, filters): and approver.parentfield = %s and approver.approver=user.name""",(d, "%" + txt + "%", parentfield), as_list=True) - return approvers + return set(tuple(approver) for approver in approvers) diff --git a/erpnext/hr/doctype/employee/test_employee.py b/erpnext/hr/doctype/employee/test_employee.py index 5a63beb283..d3410de2eb 100644 --- a/erpnext/hr/doctype/employee/test_employee.py +++ b/erpnext/hr/doctype/employee/test_employee.py @@ -45,7 +45,7 @@ class TestEmployee(unittest.TestCase): employee1_doc.status = 'Left' self.assertRaises(EmployeeLeftValidationError, employee1_doc.save) -def make_employee(user): +def make_employee(user, company=None): if not frappe.db.get_value("User", user): frappe.get_doc({ "doctype": "User", @@ -55,12 +55,12 @@ def make_employee(user): "roles": [{"doctype": "Has Role", "role": "Employee"}] }).insert() - if not frappe.db.get_value("Employee", {"user_id": user}): + if not frappe.db.get_value("Employee", { "user_id": user, "company": company or erpnext.get_default_company() }): employee = frappe.get_doc({ "doctype": "Employee", "naming_series": "EMP-", "first_name": user, - "company": erpnext.get_default_company(), + "company": company or erpnext.get_default_company(), "user_id": user, "date_of_birth": "1990-05-08", "date_of_joining": "2013-01-01", diff --git a/erpnext/manufacturing/doctype/blanket_order/blanket_order.json b/erpnext/manufacturing/doctype/blanket_order/blanket_order.json index 260e0b8a73..0330e5c85c 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 099eed4aec..977ad547f5 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/patches/v11_0/make_asset_finance_book_against_old_entries.py b/erpnext/patches/v11_0/make_asset_finance_book_against_old_entries.py index 1c8bd68932..ee709ac2d4 100644 --- a/erpnext/patches/v11_0/make_asset_finance_book_against_old_entries.py +++ b/erpnext/patches/v11_0/make_asset_finance_book_against_old_entries.py @@ -17,10 +17,6 @@ def execute(): frappe.db.sql(""" update `tabAsset` ast, `tabWarehouse` wh set ast.location = wh.warehouse_name where ast.warehouse = wh.name""") - frappe.db.sql(""" update `tabAsset Movement` ast_mv - set ast_mv.source_location = (select warehouse_name from `tabWarehouse` where name = ast_mv.source_warehouse), - ast_mv.target_location = (select warehouse_name from `tabWarehouse` where name = ast_mv.target_warehouse)""") - for d in frappe.get_all('Asset'): doc = frappe.get_doc('Asset', d.name) if doc.calculate_depreciation: diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index ca492baf5a..5da949320a 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -516,7 +516,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 +1174,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 +1283,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_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 +1375,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) { - 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/selling/page/point_of_sale/point_of_sale.js b/erpnext/selling/page/point_of_sale/point_of_sale.js index d2c2d70dbe..b213a29ae7 100644 --- a/erpnext/selling/page/point_of_sale/point_of_sale.js +++ b/erpnext/selling/page/point_of_sale/point_of_sale.js @@ -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/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js index 313de677fc..81c5f027a7 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/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py index 5603f17a54..22375ae22c 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/stock/doctype/bin/bin.json b/erpnext/stock/doctype/bin/bin.json index e17429bc0b..04d624ec0b 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/item/item.js b/erpnext/stock/doctype/item/item.js index bfc5e6d438..2f4abbcea6 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -25,7 +25,7 @@ frappe.ui.form.on("Item", { }, refresh: function(frm) { - if(frm.doc.is_stock_item) { + if (frm.doc.is_stock_item) { frm.add_custom_button(__("Balance"), function() { frappe.route_options = { "item_code": frm.doc.name @@ -46,10 +46,15 @@ frappe.ui.form.on("Item", { }, __("View")); } - if(!frm.doc.is_fixed_asset) { + if (!frm.doc.is_fixed_asset) { erpnext.item.make_dashboard(frm); } + if (frm.doc.is_fixed_asset) { + frm.trigger('is_fixed_asset'); + frm.trigger('auto_create_assets'); + } + // clear intro frm.set_intro(); @@ -132,6 +137,11 @@ frappe.ui.form.on("Item", { }, is_fixed_asset: function(frm) { + // set serial no to false & toggles its visibility + frm.set_value('has_serial_no', 0); + frm.toggle_enable(['has_serial_no', 'serial_no_series'], !frm.doc.is_fixed_asset); + frm.toggle_display(['has_serial_no', 'serial_no_series'], !frm.doc.is_fixed_asset); + frm.call({ method: "set_asset_naming_series", doc: frm.doc, @@ -139,7 +149,7 @@ frappe.ui.form.on("Item", { frm.set_value("is_stock_item", frm.doc.is_fixed_asset ? 0 : 1); frm.trigger("set_asset_naming_series"); } - }) + }); }, set_asset_naming_series: function(frm) { @@ -148,6 +158,11 @@ frappe.ui.form.on("Item", { } }, + auto_create_assets: function(frm) { + frm.toggle_reqd(['asset_category', 'asset_naming_series'], frm.doc.auto_create_assets); + frm.toggle_display(['asset_category', 'asset_naming_series'], frm.doc.auto_create_assets); + }, + page_name: frappe.utils.warn_page_name_change, item_code: function(frm) { diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json index 46efd4ee26..a2aab3f69e 100644 --- a/erpnext/stock/doctype/item/item.json +++ b/erpnext/stock/doctype/item/item.json @@ -1,1105 +1,1114 @@ { - "allow_guest_to_view": 1, - "allow_import": 1, - "allow_rename": 1, - "autoname": "field:item_code", - "creation": "2013-05-03 10:45:46", - "description": "A Product or a Service that is bought, sold or kept in stock.", - "doctype": "DocType", - "document_type": "Setup", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "name_and_description_section", - "naming_series", - "item_code", - "variant_of", - "item_name", - "item_group", - "is_item_from_hub", - "stock_uom", - "column_break0", - "disabled", - "allow_alternative_item", - "is_stock_item", - "include_item_in_manufacturing", - "opening_stock", - "valuation_rate", - "standard_rate", - "is_fixed_asset", - "asset_category", - "asset_naming_series", - "over_delivery_receipt_allowance", - "over_billing_allowance", - "image", - "section_break_11", - "brand", - "description", - "sb_barcodes", - "barcodes", - "inventory_section", - "shelf_life_in_days", - "end_of_life", - "default_material_request_type", - "valuation_method", - "column_break1", - "warranty_period", - "weight_per_unit", - "weight_uom", - "reorder_section", - "reorder_levels", - "unit_of_measure_conversion", - "uoms", - "serial_nos_and_batches", - "has_batch_no", - "create_new_batch", - "batch_number_series", - "has_expiry_date", - "retain_sample", - "sample_quantity", - "column_break_37", - "has_serial_no", - "serial_no_series", - "variants_section", - "has_variants", - "variant_based_on", - "attributes", - "defaults", - "item_defaults", - "purchase_details", - "is_purchase_item", - "purchase_uom", - "min_order_qty", - "safety_stock", - "purchase_details_cb", - "lead_time_days", - "last_purchase_rate", - "is_customer_provided_item", - "customer", - "supplier_details", - "delivered_by_supplier", - "column_break2", - "supplier_items", - "foreign_trade_details", - "country_of_origin", - "column_break_59", - "customs_tariff_number", - "sales_details", - "sales_uom", - "is_sales_item", - "column_break3", - "max_discount", - "deferred_revenue", - "deferred_revenue_account", - "enable_deferred_revenue", - "column_break_85", - "no_of_months", - "deferred_expense_section", - "deferred_expense_account", - "enable_deferred_expense", - "column_break_88", - "no_of_months_exp", - "customer_details", - "customer_items", - "item_tax_section_break", - "taxes", - "inspection_criteria", - "inspection_required_before_purchase", - "inspection_required_before_delivery", - "quality_inspection_template", - "manufacturing", - "default_bom", - "is_sub_contracted_item", - "column_break_74", - "customer_code", - "website_section", - "show_in_website", - "show_variant_in_website", - "route", - "weightage", - "slideshow", - "website_image", - "thumbnail", - "cb72", - "website_warehouse", - "website_item_groups", - "set_meta_tags", - "sb72", - "copy_from_item_group", - "website_specifications", - "web_long_description", - "website_content", - "total_projected_qty", - "hub_publishing_sb", - "publish_in_hub", - "hub_category_to_publish", - "hub_warehouse", - "synced_with_hub" - ], - "fields": [ - { - "fieldname": "name_and_description_section", - "fieldtype": "Section Break", - "oldfieldtype": "Section Break", - "options": "fa fa-flag" - }, - { - "fieldname": "naming_series", - "fieldtype": "Select", - "label": "Series", - "options": "STO-ITEM-.YYYY.-", - "set_only_once": 1 - }, - { - "bold": 1, - "fieldname": "item_code", - "fieldtype": "Data", - "in_global_search": 1, - "label": "Item Code", - "oldfieldname": "item_code", - "oldfieldtype": "Data", - "unique": 1, - "reqd": 1 - }, - { - "depends_on": "variant_of", - "description": "If item is a variant of another item then description, image, pricing, taxes etc will be set from the template unless explicitly specified", - "fieldname": "variant_of", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "in_standard_filter": 1, - "label": "Variant Of", - "options": "Item", - "read_only": 1, - "search_index": 1, - "set_only_once": 1 - }, - { - "bold": 1, - "fieldname": "item_name", - "fieldtype": "Data", - "in_global_search": 1, - "label": "Item Name", - "oldfieldname": "item_name", - "oldfieldtype": "Data", - "search_index": 1 - }, - { - "fieldname": "item_group", - "fieldtype": "Link", - "in_list_view": 1, - "in_preview": 1, - "in_standard_filter": 1, - "label": "Item Group", - "oldfieldname": "item_group", - "oldfieldtype": "Link", - "options": "Item Group", - "reqd": 1, - "search_index": 1 - }, - { - "default": "0", - "fieldname": "is_item_from_hub", - "fieldtype": "Check", - "label": "Is Item from Hub", - "read_only": 1 - }, - { - "fieldname": "stock_uom", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Default Unit of Measure", - "oldfieldname": "stock_uom", - "oldfieldtype": "Link", - "options": "UOM", - "reqd": 1 - }, - { - "fieldname": "column_break0", - "fieldtype": "Column Break" - }, - { - "default": "0", - "fieldname": "disabled", - "fieldtype": "Check", - "label": "Disabled" - }, - { - "default": "0", - "fieldname": "allow_alternative_item", - "fieldtype": "Check", - "label": "Allow Alternative Item" - }, - { - "bold": 1, - "default": "1", - "fieldname": "is_stock_item", - "fieldtype": "Check", - "label": "Maintain Stock", - "oldfieldname": "is_stock_item", - "oldfieldtype": "Select" - }, - { - "default": "1", - "fieldname": "include_item_in_manufacturing", - "fieldtype": "Check", - "label": "Include Item In Manufacturing" - }, - { - "bold": 1, - "depends_on": "eval:(doc.__islocal&&doc.is_stock_item && !doc.has_serial_no && !doc.has_batch_no)", - "fieldname": "opening_stock", - "fieldtype": "Float", - "label": "Opening Stock" - }, - { - "depends_on": "is_stock_item", - "fieldname": "valuation_rate", - "fieldtype": "Currency", - "label": "Valuation Rate" - }, - { - "bold": 1, - "depends_on": "eval:doc.__islocal", - "fieldname": "standard_rate", - "fieldtype": "Currency", - "label": "Standard Selling Rate" - }, - { - "default": "0", - "fieldname": "is_fixed_asset", - "fieldtype": "Check", - "label": "Is Fixed Asset", - "set_only_once": 1 - }, - { - "depends_on": "is_fixed_asset", - "fieldname": "asset_category", - "fieldtype": "Link", - "label": "Asset Category", - "options": "Asset Category" - }, - { - "depends_on": "is_fixed_asset", - "fieldname": "asset_naming_series", - "fieldtype": "Select", - "label": "Asset Naming Series" - }, - { - "fieldname": "image", - "fieldtype": "Attach Image", - "hidden": 1, - "in_preview": 1, - "label": "Image", - "options": "image", - "print_hide": 1 - }, - { - "collapsible": 1, - "fieldname": "section_break_11", - "fieldtype": "Section Break", - "label": "Description" - }, - { - "fieldname": "brand", - "fieldtype": "Link", - "label": "Brand", - "oldfieldname": "brand", - "oldfieldtype": "Link", - "options": "Brand", - "print_hide": 1 - }, - { - "fieldname": "description", - "fieldtype": "Text Editor", - "in_preview": 1, - "label": "Description", - "oldfieldname": "description", - "oldfieldtype": "Text" - }, - { - "fieldname": "sb_barcodes", - "fieldtype": "Section Break", - "label": "Barcodes" - }, - { - "fieldname": "barcodes", - "fieldtype": "Table", - "label": "Barcodes", - "options": "Item Barcode" - }, - { - "collapsible": 1, - "collapsible_depends_on": "is_stock_item", - "depends_on": "is_stock_item", - "fieldname": "inventory_section", - "fieldtype": "Section Break", - "label": "Inventory", - "oldfieldtype": "Section Break", - "options": "fa fa-truck" - }, - { - "fieldname": "shelf_life_in_days", - "fieldtype": "Int", - "label": "Shelf Life In Days" - }, - { - "default": "2099-12-31", - "depends_on": "is_stock_item", - "fieldname": "end_of_life", - "fieldtype": "Date", - "label": "End of Life", - "oldfieldname": "end_of_life", - "oldfieldtype": "Date" - }, - { - "default": "Purchase", - "fieldname": "default_material_request_type", - "fieldtype": "Select", - "label": "Default Material Request Type", - "options": "Purchase\nMaterial Transfer\nMaterial Issue\nManufacture\nCustomer Provided" - }, - { - "depends_on": "is_stock_item", - "fieldname": "valuation_method", - "fieldtype": "Select", - "label": "Valuation Method", - "options": "\nFIFO\nMoving Average", - "set_only_once": 1 - }, - { - "depends_on": "is_stock_item", - "fieldname": "column_break1", - "fieldtype": "Column Break", - "oldfieldtype": "Column Break", - "width": "50%" - }, - { - "depends_on": "eval:doc.is_stock_item", - "fieldname": "warranty_period", - "fieldtype": "Data", - "label": "Warranty Period (in days)", - "oldfieldname": "warranty_period", - "oldfieldtype": "Data" - }, - { - "depends_on": "is_stock_item", - "fieldname": "weight_per_unit", - "fieldtype": "Float", - "label": "Weight Per Unit" - }, - { - "depends_on": "eval:doc.is_stock_item", - "fieldname": "weight_uom", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Weight UOM", - "options": "UOM" - }, - { - "collapsible": 1, - "depends_on": "is_stock_item", - "fieldname": "reorder_section", - "fieldtype": "Section Break", - "label": "Auto re-order", - "options": "fa fa-rss" - }, - { - "description": "Will also apply for variants unless overrridden", - "fieldname": "reorder_levels", - "fieldtype": "Table", - "label": "Reorder level based on Warehouse", - "options": "Item Reorder" - }, - { - "collapsible": 1, - "fieldname": "unit_of_measure_conversion", - "fieldtype": "Section Break", - "label": "Units of Measure" - }, - { - "description": "Will also apply for variants", - "fieldname": "uoms", - "fieldtype": "Table", - "label": "UOMs", - "oldfieldname": "uom_conversion_details", - "oldfieldtype": "Table", - "options": "UOM Conversion Detail" - }, - { - "collapsible": 1, - "collapsible_depends_on": "eval:doc.has_batch_no || doc.has_serial_no || doc.is_fixed_asset", - "depends_on": "eval:doc.is_stock_item || doc.is_fixed_asset", - "fieldname": "serial_nos_and_batches", - "fieldtype": "Section Break", - "label": "Serial Nos and Batches" - }, - { - "default": "0", - "depends_on": "eval:doc.is_stock_item", - "fieldname": "has_batch_no", - "fieldtype": "Check", - "label": "Has Batch No", - "no_copy": 1, - "oldfieldname": "has_batch_no", - "oldfieldtype": "Select" - }, - { - "default": "0", - "depends_on": "has_batch_no", - "fieldname": "create_new_batch", - "fieldtype": "Check", - "label": "Automatically Create New Batch" - }, - { - "depends_on": "eval:doc.has_batch_no==1 && doc.create_new_batch==1", - "description": "Example: ABCD.#####. If series is set and Batch No is not mentioned in transactions, then automatic batch number will be created based on this series. If you always want to explicitly mention Batch No for this item, leave this blank. Note: this setting will take priority over the Naming Series Prefix in Stock Settings.", - "fieldname": "batch_number_series", - "fieldtype": "Data", - "label": "Batch Number Series", - "translatable": 1 - }, - { - "default": "0", - "depends_on": "has_batch_no", - "fieldname": "has_expiry_date", - "fieldtype": "Check", - "label": "Has Expiry Date" - }, - { - "default": "0", - "fieldname": "retain_sample", - "fieldtype": "Check", - "label": "Retain Sample" - }, - { - "depends_on": "eval: (doc.retain_sample && doc.has_batch_no)", - "description": "Maximum sample quantity that can be retained", - "fieldname": "sample_quantity", - "fieldtype": "Int", - "label": "Max Sample Quantity" - }, - { - "fieldname": "column_break_37", - "fieldtype": "Column Break" - }, - { - "default": "0", - "depends_on": "eval:doc.is_stock_item || doc.is_fixed_asset", - "fieldname": "has_serial_no", - "fieldtype": "Check", - "label": "Has Serial No", - "no_copy": 1, - "oldfieldname": "has_serial_no", - "oldfieldtype": "Select" - }, - { - "depends_on": "eval:doc.is_stock_item || doc.is_fixed_asset", - "description": "Example: ABCD.#####\nIf series is set and Serial No is not mentioned in transactions, then automatic serial number will be created based on this series. If you always want to explicitly mention Serial Nos for this item. leave this blank.", - "fieldname": "serial_no_series", - "fieldtype": "Data", - "label": "Serial Number Series" - }, - { - "collapsible": 1, - "collapsible_depends_on": "attributes", - "fieldname": "variants_section", - "fieldtype": "Section Break", - "label": "Variants" - }, - { - "default": "0", - "depends_on": "eval:!doc.variant_of", - "description": "If this item has variants, then it cannot be selected in sales orders etc.", - "fieldname": "has_variants", - "fieldtype": "Check", - "in_standard_filter": 1, - "label": "Has Variants", - "no_copy": 1 - }, - { - "default": "Item Attribute", - "depends_on": "has_variants", - "fieldname": "variant_based_on", - "fieldtype": "Select", - "label": "Variant Based On", - "options": "Item Attribute\nManufacturer" - }, - { - "depends_on": "eval:(doc.has_variants || doc.variant_of) && doc.variant_based_on==='Item Attribute'", - "fieldname": "attributes", - "fieldtype": "Table", - "hidden": 1, - "label": "Attributes", - "no_copy": 1, - "options": "Item Variant Attribute" - }, - { - "fieldname": "defaults", - "fieldtype": "Section Break", - "label": "Sales, Purchase, Accounting Defaults" - }, - { - "fieldname": "item_defaults", - "fieldtype": "Table", - "label": "Item Defaults", - "options": "Item Default" - }, - { - "collapsible": 1, - "fieldname": "purchase_details", - "fieldtype": "Section Break", - "label": "Purchase, Replenishment Details", - "oldfieldtype": "Section Break", - "options": "fa fa-shopping-cart" - }, - { - "default": "1", - "fieldname": "is_purchase_item", - "fieldtype": "Check", - "label": "Is Purchase Item" - }, - { - "fieldname": "purchase_uom", - "fieldtype": "Link", - "label": "Default Purchase Unit of Measure", - "options": "UOM" - }, - { - "default": "0.00", - "depends_on": "is_stock_item", - "fieldname": "min_order_qty", - "fieldtype": "Float", - "label": "Minimum Order Qty", - "oldfieldname": "min_order_qty", - "oldfieldtype": "Currency" - }, - { - "fieldname": "safety_stock", - "fieldtype": "Float", - "label": "Safety Stock" - }, - { - "fieldname": "purchase_details_cb", - "fieldtype": "Column Break" - }, - { - "description": "Average time taken by the supplier to deliver", - "fieldname": "lead_time_days", - "fieldtype": "Int", - "label": "Lead Time in days", - "oldfieldname": "lead_time_days", - "oldfieldtype": "Int" - }, - { - "fieldname": "last_purchase_rate", - "fieldtype": "Float", - "label": "Last Purchase Rate", - "no_copy": 1, - "oldfieldname": "last_purchase_rate", - "oldfieldtype": "Currency", - "read_only": 1 - }, - { - "default": "0", - "fieldname": "is_customer_provided_item", - "fieldtype": "Check", - "label": "Is Customer Provided Item" - }, - { - "depends_on": "eval:doc.is_customer_provided_item==1", - "fieldname": "customer", - "fieldtype": "Link", - "label": "Customer", - "options": "Customer" - }, - { - "collapsible": 1, - "fieldname": "supplier_details", - "fieldtype": "Section Break", - "label": "Supplier Details" - }, - { - "default": "0", - "fieldname": "delivered_by_supplier", - "fieldtype": "Check", - "label": "Delivered by Supplier (Drop Ship)", - "print_hide": 1 - }, - { - "fieldname": "column_break2", - "fieldtype": "Column Break", - "oldfieldtype": "Column Break", - "width": "50%" - }, - { - "fieldname": "supplier_items", - "fieldtype": "Table", - "label": "Supplier Items", - "options": "Item Supplier" - }, - { - "collapsible": 1, - "fieldname": "foreign_trade_details", - "fieldtype": "Section Break", - "label": "Foreign Trade Details" - }, - { - "fieldname": "country_of_origin", - "fieldtype": "Link", - "label": "Country of Origin", - "options": "Country" - }, - { - "fieldname": "column_break_59", - "fieldtype": "Column Break" - }, - { - "fieldname": "customs_tariff_number", - "fieldtype": "Link", - "label": "Customs Tariff Number", - "options": "Customs Tariff Number" - }, - { - "collapsible": 1, - "fieldname": "sales_details", - "fieldtype": "Section Break", - "label": "Sales Details", - "oldfieldtype": "Section Break", - "options": "fa fa-tag" - }, - { - "fieldname": "sales_uom", - "fieldtype": "Link", - "label": "Default Sales Unit of Measure", - "options": "UOM" - }, - { - "default": "1", - "fieldname": "is_sales_item", - "fieldtype": "Check", - "label": "Is Sales Item" - }, - { - "fieldname": "column_break3", - "fieldtype": "Column Break", - "oldfieldtype": "Column Break", - "width": "50%" - }, - { - "fieldname": "max_discount", - "fieldtype": "Float", - "label": "Max Discount (%)", - "oldfieldname": "max_discount", - "oldfieldtype": "Currency" - }, - { - "collapsible": 1, - "fieldname": "deferred_revenue", - "fieldtype": "Section Break", - "label": "Deferred Revenue" - }, - { - "depends_on": "enable_deferred_revenue", - "fieldname": "deferred_revenue_account", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Deferred Revenue Account", - "options": "Account" - }, - { - "default": "0", - "fieldname": "enable_deferred_revenue", - "fieldtype": "Check", - "label": "Enable Deferred Revenue" - }, - { - "fieldname": "column_break_85", - "fieldtype": "Column Break" - }, - { - "depends_on": "enable_deferred_revenue", - "fieldname": "no_of_months", - "fieldtype": "Int", - "label": "No of Months" - }, - { - "collapsible": 1, - "fieldname": "deferred_expense_section", - "fieldtype": "Section Break", - "label": "Deferred Expense" - }, - { - "depends_on": "enable_deferred_expense", - "fieldname": "deferred_expense_account", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Deferred Expense Account", - "options": "Account" - }, - { - "default": "0", - "fieldname": "enable_deferred_expense", - "fieldtype": "Check", - "label": "Enable Deferred Expense" - }, - { - "fieldname": "column_break_88", - "fieldtype": "Column Break" - }, - { - "depends_on": "enable_deferred_expense", - "fieldname": "no_of_months_exp", - "fieldtype": "Int", - "label": "No of Months" - }, - { - "collapsible": 1, - "fieldname": "customer_details", - "fieldtype": "Section Break", - "label": "Customer Details" - }, - { - "fieldname": "customer_items", - "fieldtype": "Table", - "label": "Customer Items", - "options": "Item Customer Detail" - }, - { - "collapsible": 1, - "collapsible_depends_on": "taxes", - "fieldname": "item_tax_section_break", - "fieldtype": "Section Break", - "label": "Item Tax", - "oldfieldtype": "Section Break", - "options": "fa fa-money" - }, - { - "description": "Will also apply for variants", - "fieldname": "taxes", - "fieldtype": "Table", - "label": "Taxes", - "oldfieldname": "item_tax", - "oldfieldtype": "Table", - "options": "Item Tax" - }, - { - "collapsible": 1, - "fieldname": "inspection_criteria", - "fieldtype": "Section Break", - "label": "Inspection Criteria", - "oldfieldtype": "Section Break", - "options": "fa fa-search" - }, - { - "default": "0", - "fieldname": "inspection_required_before_purchase", - "fieldtype": "Check", - "label": "Inspection Required before Purchase", - "oldfieldname": "inspection_required", - "oldfieldtype": "Select" - }, - { - "default": "0", - "fieldname": "inspection_required_before_delivery", - "fieldtype": "Check", - "label": "Inspection Required before Delivery" - }, - { - "depends_on": "eval:(doc.inspection_required_before_purchase || doc.inspection_required_before_delivery)", - "fieldname": "quality_inspection_template", - "fieldtype": "Link", - "label": "Quality Inspection Template", - "options": "Quality Inspection Template", - "print_hide": 1 - }, - { - "collapsible": 1, - "depends_on": "is_stock_item", - "fieldname": "manufacturing", - "fieldtype": "Section Break", - "label": "Manufacturing", - "oldfieldtype": "Section Break", - "options": "fa fa-cogs" - }, - { - "fieldname": "default_bom", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Default BOM", - "no_copy": 1, - "oldfieldname": "default_bom", - "oldfieldtype": "Link", - "options": "BOM", - "read_only": 1 - }, - { - "default": "0", - "description": "If subcontracted to a vendor", - "fieldname": "is_sub_contracted_item", - "fieldtype": "Check", - "label": "Supply Raw Materials for Purchase", - "oldfieldname": "is_sub_contracted_item", - "oldfieldtype": "Select" - }, - { - "fieldname": "column_break_74", - "fieldtype": "Column Break" - }, - { - "fieldname": "customer_code", - "fieldtype": "Data", - "hidden": 1, - "label": "Customer Code", - "no_copy": 1, - "print_hide": 1 - }, - { - "collapsible": 1, - "fieldname": "website_section", - "fieldtype": "Section Break", - "label": "Website", - "options": "fa fa-globe" - }, - { - "default": "0", - "depends_on": "eval:!doc.variant_of", - "fieldname": "show_in_website", - "fieldtype": "Check", - "label": "Show in Website", - "search_index": 1 - }, - { - "default": "0", - "depends_on": "variant_of", - "fieldname": "show_variant_in_website", - "fieldtype": "Check", - "label": "Show in Website (Variant)", - "search_index": 1 - }, - { - "depends_on": "eval: doc.show_in_website || doc.show_variant_in_website", - "fieldname": "route", - "fieldtype": "Small Text", - "label": "Route", - "no_copy": 1 - }, - { - "depends_on": "eval: doc.show_in_website || doc.show_variant_in_website", - "description": "Items with higher weightage will be shown higher", - "fieldname": "weightage", - "fieldtype": "Int", - "label": "Weightage" - }, - { - "depends_on": "eval: doc.show_in_website || doc.show_variant_in_website", - "description": "Show a slideshow at the top of the page", - "fieldname": "slideshow", - "fieldtype": "Link", - "label": "Slideshow", - "options": "Website Slideshow" - }, - { - "depends_on": "eval: doc.show_in_website || doc.show_variant_in_website", - "description": "Item Image (if not slideshow)", - "fieldname": "website_image", - "fieldtype": "Attach", - "label": "Website Image" - }, - { - "fieldname": "thumbnail", - "fieldtype": "Data", - "label": "Thumbnail", - "read_only": 1 - }, - { - "fieldname": "cb72", - "fieldtype": "Column Break" - }, - { - "depends_on": "eval: doc.show_in_website || doc.show_variant_in_website", - "description": "Show \"In Stock\" or \"Not in Stock\" based on stock available in this warehouse.", - "fieldname": "website_warehouse", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Website Warehouse", - "options": "Warehouse" - }, - { - "depends_on": "eval: doc.show_in_website || doc.show_variant_in_website", - "description": "List this Item in multiple groups on the website.", - "fieldname": "website_item_groups", - "fieldtype": "Table", - "label": "Website Item Groups", - "options": "Website Item Group" - }, - { - "depends_on": "eval: doc.show_in_website || doc.show_variant_in_website", - "fieldname": "set_meta_tags", - "fieldtype": "Button", - "label": "Set Meta Tags" - }, - { - "collapsible": 1, - "collapsible_depends_on": "website_specifications", - "depends_on": "eval: doc.show_in_website || doc.show_variant_in_website", - "fieldname": "sb72", - "fieldtype": "Section Break", - "label": "Website Specifications" - }, - { - "depends_on": "eval: doc.show_in_website || doc.show_variant_in_website", - "fieldname": "copy_from_item_group", - "fieldtype": "Button", - "label": "Copy From Item Group" - }, - { - "depends_on": "eval: doc.show_in_website || doc.show_variant_in_website", - "fieldname": "website_specifications", - "fieldtype": "Table", - "label": "Website Specifications", - "options": "Item Website Specification" - }, - { - "depends_on": "eval: doc.show_in_website || doc.show_variant_in_website", - "fieldname": "web_long_description", - "fieldtype": "Text Editor", - "label": "Website Description" - }, - { - "description": "You can use any valid Bootstrap 4 markup in this field. It will be shown on your Item Page.", - "fieldname": "website_content", - "fieldtype": "HTML Editor", - "label": "Website Content" - }, - { - "fieldname": "total_projected_qty", - "fieldtype": "Float", - "hidden": 1, - "label": "Total Projected Qty", - "print_hide": 1, - "read_only": 1 - }, - { - "depends_on": "eval:(!doc.is_item_from_hub)", - "fieldname": "hub_publishing_sb", - "fieldtype": "Section Break", - "label": "Hub Publishing Details" - }, - { - "default": "0", - "description": "Publish Item to hub.erpnext.com", - "fieldname": "publish_in_hub", - "fieldtype": "Check", - "label": "Publish in Hub" - }, - { - "fieldname": "hub_category_to_publish", - "fieldtype": "Data", - "label": "Hub Category to Publish", - "read_only": 1 - }, - { - "description": "Publish \"In Stock\" or \"Not in Stock\" on Hub based on stock available in this warehouse.", - "fieldname": "hub_warehouse", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Hub Warehouse", - "options": "Warehouse" - }, - { - "default": "0", - "fieldname": "synced_with_hub", - "fieldtype": "Check", - "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", - "fieldtype": "Float", - "label": "Over Delivery/Receipt Allowance (%)", - "oldfieldname": "tolerance", - "oldfieldtype": "Currency" - }, - { - "fieldname": "over_billing_allowance", - "fieldtype": "Float", - "label": "Over Billing Allowance (%)", - "depends_on": "eval:!doc.__islocal" - } - ], - "has_web_view": 1, - "icon": "fa fa-tag", - "idx": 2, - "image_field": "image", - "max_attachments": 1, - "modified": "2019-09-03 18:34:13.977931", - "modified_by": "Administrator", - "module": "Stock", - "name": "Item", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "import": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Item Manager", - "share": 1, - "write": 1 - }, - { - "email": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Stock Manager" - }, - { - "email": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Stock User" - }, - { - "read": 1, - "role": "Sales User" - }, - { - "read": 1, - "role": "Purchase User" - }, - { - "read": 1, - "role": "Maintenance User" - }, - { - "read": 1, - "role": "Accounts User" - }, - { - "read": 1, - "role": "Manufacturing User" - } - ], - "quick_entry": 1, - "search_fields": "item_name,description,item_group,customer_code", - "show_name_in_global_search": 1, - "show_preview_popup": 1, - "sort_field": "idx desc,modified desc", - "sort_order": "DESC", - "title_field": "item_name", - "track_changes": 1 - } + "allow_guest_to_view": 1, + "allow_import": 1, + "allow_rename": 1, + "autoname": "field:item_code", + "creation": "2013-05-03 10:45:46", + "description": "A Product or a Service that is bought, sold or kept in stock.", + "doctype": "DocType", + "document_type": "Setup", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "name_and_description_section", + "naming_series", + "item_code", + "variant_of", + "item_name", + "item_group", + "is_item_from_hub", + "stock_uom", + "column_break0", + "disabled", + "allow_alternative_item", + "is_stock_item", + "include_item_in_manufacturing", + "opening_stock", + "valuation_rate", + "standard_rate", + "is_fixed_asset", + "auto_create_assets", + "asset_category", + "asset_naming_series", + "over_delivery_receipt_allowance", + "over_billing_allowance", + "image", + "section_break_11", + "brand", + "description", + "sb_barcodes", + "barcodes", + "inventory_section", + "shelf_life_in_days", + "end_of_life", + "default_material_request_type", + "valuation_method", + "column_break1", + "warranty_period", + "weight_per_unit", + "weight_uom", + "reorder_section", + "reorder_levels", + "unit_of_measure_conversion", + "uoms", + "serial_nos_and_batches", + "has_batch_no", + "create_new_batch", + "batch_number_series", + "has_expiry_date", + "retain_sample", + "sample_quantity", + "column_break_37", + "has_serial_no", + "serial_no_series", + "variants_section", + "has_variants", + "variant_based_on", + "attributes", + "defaults", + "item_defaults", + "purchase_details", + "is_purchase_item", + "purchase_uom", + "min_order_qty", + "safety_stock", + "purchase_details_cb", + "lead_time_days", + "last_purchase_rate", + "is_customer_provided_item", + "customer", + "supplier_details", + "delivered_by_supplier", + "column_break2", + "supplier_items", + "foreign_trade_details", + "country_of_origin", + "column_break_59", + "customs_tariff_number", + "sales_details", + "sales_uom", + "is_sales_item", + "column_break3", + "max_discount", + "deferred_revenue", + "deferred_revenue_account", + "enable_deferred_revenue", + "column_break_85", + "no_of_months", + "deferred_expense_section", + "deferred_expense_account", + "enable_deferred_expense", + "column_break_88", + "no_of_months_exp", + "customer_details", + "customer_items", + "item_tax_section_break", + "taxes", + "inspection_criteria", + "inspection_required_before_purchase", + "inspection_required_before_delivery", + "quality_inspection_template", + "manufacturing", + "default_bom", + "is_sub_contracted_item", + "column_break_74", + "customer_code", + "website_section", + "show_in_website", + "show_variant_in_website", + "route", + "weightage", + "slideshow", + "website_image", + "thumbnail", + "cb72", + "website_warehouse", + "website_item_groups", + "set_meta_tags", + "sb72", + "copy_from_item_group", + "website_specifications", + "web_long_description", + "website_content", + "total_projected_qty", + "hub_publishing_sb", + "publish_in_hub", + "hub_category_to_publish", + "hub_warehouse", + "synced_with_hub", + "manufacturers" + ], + "fields": [ + { + "fieldname": "name_and_description_section", + "fieldtype": "Section Break", + "oldfieldtype": "Section Break", + "options": "fa fa-flag" + }, + { + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Series", + "options": "STO-ITEM-.YYYY.-", + "set_only_once": 1 + }, + { + "bold": 1, + "fieldname": "item_code", + "fieldtype": "Data", + "in_global_search": 1, + "label": "Item Code", + "oldfieldname": "item_code", + "oldfieldtype": "Data", + "reqd": 1, + "unique": 1 + }, + { + "depends_on": "variant_of", + "description": "If item is a variant of another item then description, image, pricing, taxes etc will be set from the template unless explicitly specified", + "fieldname": "variant_of", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "in_standard_filter": 1, + "label": "Variant Of", + "options": "Item", + "read_only": 1, + "search_index": 1, + "set_only_once": 1 + }, + { + "bold": 1, + "fieldname": "item_name", + "fieldtype": "Data", + "in_global_search": 1, + "label": "Item Name", + "oldfieldname": "item_name", + "oldfieldtype": "Data", + "search_index": 1 + }, + { + "fieldname": "item_group", + "fieldtype": "Link", + "in_list_view": 1, + "in_preview": 1, + "in_standard_filter": 1, + "label": "Item Group", + "oldfieldname": "item_group", + "oldfieldtype": "Link", + "options": "Item Group", + "reqd": 1, + "search_index": 1 + }, + { + "default": "0", + "fieldname": "is_item_from_hub", + "fieldtype": "Check", + "label": "Is Item from Hub", + "read_only": 1 + }, + { + "fieldname": "stock_uom", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Default Unit of Measure", + "oldfieldname": "stock_uom", + "oldfieldtype": "Link", + "options": "UOM", + "reqd": 1 + }, + { + "fieldname": "column_break0", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "disabled", + "fieldtype": "Check", + "label": "Disabled" + }, + { + "default": "0", + "fieldname": "allow_alternative_item", + "fieldtype": "Check", + "label": "Allow Alternative Item" + }, + { + "bold": 1, + "default": "1", + "fieldname": "is_stock_item", + "fieldtype": "Check", + "label": "Maintain Stock", + "oldfieldname": "is_stock_item", + "oldfieldtype": "Select" + }, + { + "default": "1", + "fieldname": "include_item_in_manufacturing", + "fieldtype": "Check", + "label": "Include Item In Manufacturing" + }, + { + "bold": 1, + "depends_on": "eval:(doc.__islocal&&doc.is_stock_item && !doc.has_serial_no && !doc.has_batch_no)", + "fieldname": "opening_stock", + "fieldtype": "Float", + "label": "Opening Stock" + }, + { + "depends_on": "is_stock_item", + "fieldname": "valuation_rate", + "fieldtype": "Currency", + "label": "Valuation Rate" + }, + { + "bold": 1, + "depends_on": "eval:doc.__islocal", + "fieldname": "standard_rate", + "fieldtype": "Currency", + "label": "Standard Selling Rate" + }, + { + "default": "0", + "fieldname": "is_fixed_asset", + "fieldtype": "Check", + "label": "Is Fixed Asset", + "set_only_once": 1 + }, + { + "depends_on": "is_fixed_asset", + "fieldname": "asset_category", + "fieldtype": "Link", + "label": "Asset Category", + "options": "Asset Category" + }, + { + "depends_on": "is_fixed_asset", + "fieldname": "asset_naming_series", + "fieldtype": "Select", + "label": "Asset Naming Series" + }, + { + "fieldname": "image", + "fieldtype": "Attach Image", + "hidden": 1, + "in_preview": 1, + "label": "Image", + "options": "image", + "print_hide": 1 + }, + { + "collapsible": 1, + "fieldname": "section_break_11", + "fieldtype": "Section Break", + "label": "Description" + }, + { + "fieldname": "brand", + "fieldtype": "Link", + "label": "Brand", + "oldfieldname": "brand", + "oldfieldtype": "Link", + "options": "Brand", + "print_hide": 1 + }, + { + "fieldname": "description", + "fieldtype": "Text Editor", + "in_preview": 1, + "label": "Description", + "oldfieldname": "description", + "oldfieldtype": "Text" + }, + { + "fieldname": "sb_barcodes", + "fieldtype": "Section Break", + "label": "Barcodes" + }, + { + "fieldname": "barcodes", + "fieldtype": "Table", + "label": "Barcodes", + "options": "Item Barcode" + }, + { + "collapsible": 1, + "collapsible_depends_on": "is_stock_item", + "depends_on": "is_stock_item", + "fieldname": "inventory_section", + "fieldtype": "Section Break", + "label": "Inventory", + "oldfieldtype": "Section Break", + "options": "fa fa-truck" + }, + { + "fieldname": "shelf_life_in_days", + "fieldtype": "Int", + "label": "Shelf Life In Days" + }, + { + "default": "2099-12-31", + "depends_on": "is_stock_item", + "fieldname": "end_of_life", + "fieldtype": "Date", + "label": "End of Life", + "oldfieldname": "end_of_life", + "oldfieldtype": "Date" + }, + { + "default": "Purchase", + "fieldname": "default_material_request_type", + "fieldtype": "Select", + "label": "Default Material Request Type", + "options": "Purchase\nMaterial Transfer\nMaterial Issue\nManufacture\nCustomer Provided" + }, + { + "depends_on": "is_stock_item", + "fieldname": "valuation_method", + "fieldtype": "Select", + "label": "Valuation Method", + "options": "\nFIFO\nMoving Average", + "set_only_once": 1 + }, + { + "depends_on": "is_stock_item", + "fieldname": "column_break1", + "fieldtype": "Column Break", + "oldfieldtype": "Column Break", + "width": "50%" + }, + { + "depends_on": "eval:doc.is_stock_item", + "fieldname": "warranty_period", + "fieldtype": "Data", + "label": "Warranty Period (in days)", + "oldfieldname": "warranty_period", + "oldfieldtype": "Data" + }, + { + "depends_on": "is_stock_item", + "fieldname": "weight_per_unit", + "fieldtype": "Float", + "label": "Weight Per Unit" + }, + { + "depends_on": "eval:doc.is_stock_item", + "fieldname": "weight_uom", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Weight UOM", + "options": "UOM" + }, + { + "collapsible": 1, + "depends_on": "is_stock_item", + "fieldname": "reorder_section", + "fieldtype": "Section Break", + "label": "Auto re-order", + "options": "fa fa-rss" + }, + { + "description": "Will also apply for variants unless overrridden", + "fieldname": "reorder_levels", + "fieldtype": "Table", + "label": "Reorder level based on Warehouse", + "options": "Item Reorder" + }, + { + "collapsible": 1, + "fieldname": "unit_of_measure_conversion", + "fieldtype": "Section Break", + "label": "Units of Measure" + }, + { + "description": "Will also apply for variants", + "fieldname": "uoms", + "fieldtype": "Table", + "label": "UOMs", + "oldfieldname": "uom_conversion_details", + "oldfieldtype": "Table", + "options": "UOM Conversion Detail" + }, + { + "collapsible": 1, + "collapsible_depends_on": "eval:doc.has_batch_no || doc.has_serial_no || doc.is_fixed_asset", + "depends_on": "eval:doc.is_stock_item || doc.is_fixed_asset", + "fieldname": "serial_nos_and_batches", + "fieldtype": "Section Break", + "label": "Serial Nos and Batches" + }, + { + "default": "0", + "depends_on": "eval:doc.is_stock_item", + "fieldname": "has_batch_no", + "fieldtype": "Check", + "label": "Has Batch No", + "no_copy": 1, + "oldfieldname": "has_batch_no", + "oldfieldtype": "Select" + }, + { + "default": "0", + "depends_on": "has_batch_no", + "fieldname": "create_new_batch", + "fieldtype": "Check", + "label": "Automatically Create New Batch" + }, + { + "depends_on": "eval:doc.has_batch_no==1 && doc.create_new_batch==1", + "description": "Example: ABCD.#####. If series is set and Batch No is not mentioned in transactions, then automatic batch number will be created based on this series. If you always want to explicitly mention Batch No for this item, leave this blank. Note: this setting will take priority over the Naming Series Prefix in Stock Settings.", + "fieldname": "batch_number_series", + "fieldtype": "Data", + "label": "Batch Number Series", + "translatable": 1 + }, + { + "default": "0", + "depends_on": "has_batch_no", + "fieldname": "has_expiry_date", + "fieldtype": "Check", + "label": "Has Expiry Date" + }, + { + "default": "0", + "fieldname": "retain_sample", + "fieldtype": "Check", + "label": "Retain Sample" + }, + { + "depends_on": "eval: (doc.retain_sample && doc.has_batch_no)", + "description": "Maximum sample quantity that can be retained", + "fieldname": "sample_quantity", + "fieldtype": "Int", + "label": "Max Sample Quantity" + }, + { + "fieldname": "column_break_37", + "fieldtype": "Column Break" + }, + { + "default": "0", + "depends_on": "eval:doc.is_stock_item || doc.is_fixed_asset", + "fieldname": "has_serial_no", + "fieldtype": "Check", + "label": "Has Serial No", + "no_copy": 1, + "oldfieldname": "has_serial_no", + "oldfieldtype": "Select" + }, + { + "depends_on": "eval:doc.is_stock_item || doc.is_fixed_asset", + "description": "Example: ABCD.#####\nIf series is set and Serial No is not mentioned in transactions, then automatic serial number will be created based on this series. If you always want to explicitly mention Serial Nos for this item. leave this blank.", + "fieldname": "serial_no_series", + "fieldtype": "Data", + "label": "Serial Number Series" + }, + { + "collapsible": 1, + "collapsible_depends_on": "attributes", + "fieldname": "variants_section", + "fieldtype": "Section Break", + "label": "Variants" + }, + { + "default": "0", + "depends_on": "eval:!doc.variant_of", + "description": "If this item has variants, then it cannot be selected in sales orders etc.", + "fieldname": "has_variants", + "fieldtype": "Check", + "in_standard_filter": 1, + "label": "Has Variants", + "no_copy": 1 + }, + { + "default": "Item Attribute", + "depends_on": "has_variants", + "fieldname": "variant_based_on", + "fieldtype": "Select", + "label": "Variant Based On", + "options": "Item Attribute\nManufacturer" + }, + { + "depends_on": "eval:(doc.has_variants || doc.variant_of) && doc.variant_based_on==='Item Attribute'", + "fieldname": "attributes", + "fieldtype": "Table", + "hidden": 1, + "label": "Attributes", + "no_copy": 1, + "options": "Item Variant Attribute" + }, + { + "fieldname": "defaults", + "fieldtype": "Section Break", + "label": "Sales, Purchase, Accounting Defaults" + }, + { + "fieldname": "item_defaults", + "fieldtype": "Table", + "label": "Item Defaults", + "options": "Item Default" + }, + { + "collapsible": 1, + "fieldname": "purchase_details", + "fieldtype": "Section Break", + "label": "Purchase, Replenishment Details", + "oldfieldtype": "Section Break", + "options": "fa fa-shopping-cart" + }, + { + "default": "1", + "fieldname": "is_purchase_item", + "fieldtype": "Check", + "label": "Is Purchase Item" + }, + { + "fieldname": "purchase_uom", + "fieldtype": "Link", + "label": "Default Purchase Unit of Measure", + "options": "UOM" + }, + { + "default": "0.00", + "depends_on": "is_stock_item", + "fieldname": "min_order_qty", + "fieldtype": "Float", + "label": "Minimum Order Qty", + "oldfieldname": "min_order_qty", + "oldfieldtype": "Currency" + }, + { + "fieldname": "safety_stock", + "fieldtype": "Float", + "label": "Safety Stock" + }, + { + "fieldname": "purchase_details_cb", + "fieldtype": "Column Break" + }, + { + "description": "Average time taken by the supplier to deliver", + "fieldname": "lead_time_days", + "fieldtype": "Int", + "label": "Lead Time in days", + "oldfieldname": "lead_time_days", + "oldfieldtype": "Int" + }, + { + "fieldname": "last_purchase_rate", + "fieldtype": "Float", + "label": "Last Purchase Rate", + "no_copy": 1, + "oldfieldname": "last_purchase_rate", + "oldfieldtype": "Currency", + "read_only": 1 + }, + { + "default": "0", + "fieldname": "is_customer_provided_item", + "fieldtype": "Check", + "label": "Is Customer Provided Item" + }, + { + "depends_on": "eval:doc.is_customer_provided_item==1", + "fieldname": "customer", + "fieldtype": "Link", + "label": "Customer", + "options": "Customer" + }, + { + "collapsible": 1, + "fieldname": "supplier_details", + "fieldtype": "Section Break", + "label": "Supplier Details" + }, + { + "default": "0", + "fieldname": "delivered_by_supplier", + "fieldtype": "Check", + "label": "Delivered by Supplier (Drop Ship)", + "print_hide": 1 + }, + { + "fieldname": "column_break2", + "fieldtype": "Column Break", + "oldfieldtype": "Column Break", + "width": "50%" + }, + { + "fieldname": "supplier_items", + "fieldtype": "Table", + "label": "Supplier Items", + "options": "Item Supplier" + }, + { + "collapsible": 1, + "fieldname": "foreign_trade_details", + "fieldtype": "Section Break", + "label": "Foreign Trade Details" + }, + { + "fieldname": "country_of_origin", + "fieldtype": "Link", + "label": "Country of Origin", + "options": "Country" + }, + { + "fieldname": "column_break_59", + "fieldtype": "Column Break" + }, + { + "fieldname": "customs_tariff_number", + "fieldtype": "Link", + "label": "Customs Tariff Number", + "options": "Customs Tariff Number" + }, + { + "collapsible": 1, + "fieldname": "sales_details", + "fieldtype": "Section Break", + "label": "Sales Details", + "oldfieldtype": "Section Break", + "options": "fa fa-tag" + }, + { + "fieldname": "sales_uom", + "fieldtype": "Link", + "label": "Default Sales Unit of Measure", + "options": "UOM" + }, + { + "default": "1", + "fieldname": "is_sales_item", + "fieldtype": "Check", + "label": "Is Sales Item" + }, + { + "fieldname": "column_break3", + "fieldtype": "Column Break", + "oldfieldtype": "Column Break", + "width": "50%" + }, + { + "fieldname": "max_discount", + "fieldtype": "Float", + "label": "Max Discount (%)", + "oldfieldname": "max_discount", + "oldfieldtype": "Currency" + }, + { + "collapsible": 1, + "fieldname": "deferred_revenue", + "fieldtype": "Section Break", + "label": "Deferred Revenue" + }, + { + "depends_on": "enable_deferred_revenue", + "fieldname": "deferred_revenue_account", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Deferred Revenue Account", + "options": "Account" + }, + { + "default": "0", + "fieldname": "enable_deferred_revenue", + "fieldtype": "Check", + "label": "Enable Deferred Revenue" + }, + { + "fieldname": "column_break_85", + "fieldtype": "Column Break" + }, + { + "depends_on": "enable_deferred_revenue", + "fieldname": "no_of_months", + "fieldtype": "Int", + "label": "No of Months" + }, + { + "collapsible": 1, + "fieldname": "deferred_expense_section", + "fieldtype": "Section Break", + "label": "Deferred Expense" + }, + { + "depends_on": "enable_deferred_expense", + "fieldname": "deferred_expense_account", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Deferred Expense Account", + "options": "Account" + }, + { + "default": "0", + "fieldname": "enable_deferred_expense", + "fieldtype": "Check", + "label": "Enable Deferred Expense" + }, + { + "fieldname": "column_break_88", + "fieldtype": "Column Break" + }, + { + "depends_on": "enable_deferred_expense", + "fieldname": "no_of_months_exp", + "fieldtype": "Int", + "label": "No of Months" + }, + { + "collapsible": 1, + "fieldname": "customer_details", + "fieldtype": "Section Break", + "label": "Customer Details" + }, + { + "fieldname": "customer_items", + "fieldtype": "Table", + "label": "Customer Items", + "options": "Item Customer Detail" + }, + { + "collapsible": 1, + "collapsible_depends_on": "taxes", + "fieldname": "item_tax_section_break", + "fieldtype": "Section Break", + "label": "Item Tax", + "oldfieldtype": "Section Break", + "options": "fa fa-money" + }, + { + "description": "Will also apply for variants", + "fieldname": "taxes", + "fieldtype": "Table", + "label": "Taxes", + "oldfieldname": "item_tax", + "oldfieldtype": "Table", + "options": "Item Tax" + }, + { + "collapsible": 1, + "fieldname": "inspection_criteria", + "fieldtype": "Section Break", + "label": "Inspection Criteria", + "oldfieldtype": "Section Break", + "options": "fa fa-search" + }, + { + "default": "0", + "fieldname": "inspection_required_before_purchase", + "fieldtype": "Check", + "label": "Inspection Required before Purchase", + "oldfieldname": "inspection_required", + "oldfieldtype": "Select" + }, + { + "default": "0", + "fieldname": "inspection_required_before_delivery", + "fieldtype": "Check", + "label": "Inspection Required before Delivery" + }, + { + "depends_on": "eval:(doc.inspection_required_before_purchase || doc.inspection_required_before_delivery)", + "fieldname": "quality_inspection_template", + "fieldtype": "Link", + "label": "Quality Inspection Template", + "options": "Quality Inspection Template", + "print_hide": 1 + }, + { + "collapsible": 1, + "depends_on": "is_stock_item", + "fieldname": "manufacturing", + "fieldtype": "Section Break", + "label": "Manufacturing", + "oldfieldtype": "Section Break", + "options": "fa fa-cogs" + }, + { + "fieldname": "default_bom", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Default BOM", + "no_copy": 1, + "oldfieldname": "default_bom", + "oldfieldtype": "Link", + "options": "BOM", + "read_only": 1 + }, + { + "default": "0", + "description": "If subcontracted to a vendor", + "fieldname": "is_sub_contracted_item", + "fieldtype": "Check", + "label": "Supply Raw Materials for Purchase", + "oldfieldname": "is_sub_contracted_item", + "oldfieldtype": "Select" + }, + { + "fieldname": "column_break_74", + "fieldtype": "Column Break" + }, + { + "fieldname": "customer_code", + "fieldtype": "Data", + "hidden": 1, + "label": "Customer Code", + "no_copy": 1, + "print_hide": 1 + }, + { + "collapsible": 1, + "fieldname": "website_section", + "fieldtype": "Section Break", + "label": "Website", + "options": "fa fa-globe" + }, + { + "default": "0", + "depends_on": "eval:!doc.variant_of", + "fieldname": "show_in_website", + "fieldtype": "Check", + "label": "Show in Website", + "search_index": 1 + }, + { + "default": "0", + "depends_on": "variant_of", + "fieldname": "show_variant_in_website", + "fieldtype": "Check", + "label": "Show in Website (Variant)", + "search_index": 1 + }, + { + "depends_on": "eval: doc.show_in_website || doc.show_variant_in_website", + "fieldname": "route", + "fieldtype": "Small Text", + "label": "Route", + "no_copy": 1 + }, + { + "depends_on": "eval: doc.show_in_website || doc.show_variant_in_website", + "description": "Items with higher weightage will be shown higher", + "fieldname": "weightage", + "fieldtype": "Int", + "label": "Weightage" + }, + { + "depends_on": "eval: doc.show_in_website || doc.show_variant_in_website", + "description": "Show a slideshow at the top of the page", + "fieldname": "slideshow", + "fieldtype": "Link", + "label": "Slideshow", + "options": "Website Slideshow" + }, + { + "depends_on": "eval: doc.show_in_website || doc.show_variant_in_website", + "description": "Item Image (if not slideshow)", + "fieldname": "website_image", + "fieldtype": "Attach", + "label": "Website Image" + }, + { + "fieldname": "thumbnail", + "fieldtype": "Data", + "label": "Thumbnail", + "read_only": 1 + }, + { + "fieldname": "cb72", + "fieldtype": "Column Break" + }, + { + "depends_on": "eval: doc.show_in_website || doc.show_variant_in_website", + "description": "Show \"In Stock\" or \"Not in Stock\" based on stock available in this warehouse.", + "fieldname": "website_warehouse", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Website Warehouse", + "options": "Warehouse" + }, + { + "depends_on": "eval: doc.show_in_website || doc.show_variant_in_website", + "description": "List this Item in multiple groups on the website.", + "fieldname": "website_item_groups", + "fieldtype": "Table", + "label": "Website Item Groups", + "options": "Website Item Group" + }, + { + "depends_on": "eval: doc.show_in_website || doc.show_variant_in_website", + "fieldname": "set_meta_tags", + "fieldtype": "Button", + "label": "Set Meta Tags" + }, + { + "collapsible": 1, + "collapsible_depends_on": "website_specifications", + "depends_on": "eval: doc.show_in_website || doc.show_variant_in_website", + "fieldname": "sb72", + "fieldtype": "Section Break", + "label": "Website Specifications" + }, + { + "depends_on": "eval: doc.show_in_website || doc.show_variant_in_website", + "fieldname": "copy_from_item_group", + "fieldtype": "Button", + "label": "Copy From Item Group" + }, + { + "depends_on": "eval: doc.show_in_website || doc.show_variant_in_website", + "fieldname": "website_specifications", + "fieldtype": "Table", + "label": "Website Specifications", + "options": "Item Website Specification" + }, + { + "depends_on": "eval: doc.show_in_website || doc.show_variant_in_website", + "fieldname": "web_long_description", + "fieldtype": "Text Editor", + "label": "Website Description" + }, + { + "description": "You can use any valid Bootstrap 4 markup in this field. It will be shown on your Item Page.", + "fieldname": "website_content", + "fieldtype": "HTML Editor", + "label": "Website Content" + }, + { + "fieldname": "total_projected_qty", + "fieldtype": "Float", + "hidden": 1, + "label": "Total Projected Qty", + "print_hide": 1, + "read_only": 1 + }, + { + "depends_on": "eval:(!doc.is_item_from_hub)", + "fieldname": "hub_publishing_sb", + "fieldtype": "Section Break", + "label": "Hub Publishing Details" + }, + { + "default": "0", + "description": "Publish Item to hub.erpnext.com", + "fieldname": "publish_in_hub", + "fieldtype": "Check", + "label": "Publish in Hub" + }, + { + "fieldname": "hub_category_to_publish", + "fieldtype": "Data", + "label": "Hub Category to Publish", + "read_only": 1 + }, + { + "description": "Publish \"In Stock\" or \"Not in Stock\" on Hub based on stock available in this warehouse.", + "fieldname": "hub_warehouse", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Hub Warehouse", + "options": "Warehouse" + }, + { + "default": "0", + "fieldname": "synced_with_hub", + "fieldtype": "Check", + "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", + "fieldtype": "Float", + "label": "Over Delivery/Receipt Allowance (%)", + "oldfieldname": "tolerance", + "oldfieldtype": "Currency" + }, + { + "depends_on": "eval:!doc.__islocal", + "fieldname": "over_billing_allowance", + "fieldtype": "Float", + "label": "Over Billing Allowance (%)" + }, + { + "default": "0", + "depends_on": "is_fixed_asset", + "fieldname": "auto_create_assets", + "fieldtype": "Check", + "label": "Auto Create Assets on Purchase" + } + ], + "has_web_view": 1, + "icon": "fa fa-tag", + "idx": 2, + "image_field": "image", + "max_attachments": 1, + "modified": "2019-10-09 17:05:59.576119", + "modified_by": "Administrator", + "module": "Stock", + "name": "Item", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "import": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Item Manager", + "share": 1, + "write": 1 + }, + { + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Stock Manager" + }, + { + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Stock User" + }, + { + "read": 1, + "role": "Sales User" + }, + { + "read": 1, + "role": "Purchase User" + }, + { + "read": 1, + "role": "Maintenance User" + }, + { + "read": 1, + "role": "Accounts User" + }, + { + "read": 1, + "role": "Manufacturing User" + } + ], + "quick_entry": 1, + "search_fields": "item_name,description,item_group,customer_code", + "show_name_in_global_search": 1, + "show_preview_popup": 1, + "sort_field": "idx desc,modified desc", + "sort_order": "DESC", + "title_field": "item_name", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/stock/doctype/landed_cost_item/landed_cost_item.json b/erpnext/stock/doctype/landed_cost_item/landed_cost_item.json index 66c33a1c9d..90a392c145 100644 --- a/erpnext/stock/doctype/landed_cost_item/landed_cost_item.json +++ b/erpnext/stock/doctype/landed_cost_item/landed_cost_item.json @@ -13,6 +13,7 @@ "qty", "rate", "amount", + "is_fixed_asset", "applicable_charges", "purchase_receipt_item", "accounting_dimensions_section", @@ -119,14 +120,25 @@ { "fieldname": "dimension_col_break", "fieldtype": "Column Break" + }, + { + "default": "0", + "fetch_from": "item_code.is_fixed_asset", + "fieldname": "is_fixed_asset", + "fieldtype": "Check", + "hidden": 1, + "label": "Is Fixed Asset", + "read_only": 1 } ], "idx": 1, "istable": 1, - "modified": "2019-05-26 09:48:15.569956", + "modified": "2019-11-12 15:41:21.053462", "modified_by": "Administrator", "module": "Stock", "name": "Landed Cost Item", "owner": "wasim@webnotestech.com", - "permissions": [] + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC" } \ No newline at end of file diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js index c9a3fd976f..5de1352518 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js +++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js @@ -129,6 +129,10 @@ erpnext.stock.LandedCostVoucher = erpnext.stock.StockController.extend({ }, distribute_charges_based_on: function (frm) { this.set_applicable_charges_for_item(); + }, + + items_remove: () => { + this.trigger('set_applicable_charges_for_item'); } }); diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.json b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.json index c2c669211a..46fdc8fc10 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.json +++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.json @@ -1,545 +1,149 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "naming_series:", - "beta": 0, - "creation": "2014-07-11 11:33:42.547339", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Document", - "editable_grid": 0, - "engine": "InnoDB", + "autoname": "naming_series:", + "creation": "2014-07-11 11:33:42.547339", + "doctype": "DocType", + "document_type": "Document", + "engine": "InnoDB", + "field_order": [ + "naming_series", + "company", + "purchase_receipts", + "sec_break1", + "taxes", + "purchase_receipt_items", + "get_items_from_purchase_receipts", + "items", + "section_break_9", + "total_taxes_and_charges", + "col_break1", + "distribute_charges_based_on", + "amended_from", + "sec_break2", + "landed_cost_help" + ], "fields": [ { - "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, - "options": "MAT-LCV-.YYYY.-", - "permlevel": 0, - "precision": "", - "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 - }, + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Series", + "no_copy": 1, + "options": "MAT-LCV-.YYYY.-", + "print_hide": 1, + "reqd": 1, + "set_only_once": 1 + }, { - "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": 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, - "translatable": 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_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "purchase_receipts", - "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": "Purchase Receipts", - "length": 0, - "no_copy": 0, - "options": "Landed Cost Purchase Receipt", - "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 - }, + "fieldname": "purchase_receipts", + "fieldtype": "Table", + "label": "Purchase Receipts", + "options": "Landed Cost Purchase Receipt", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "purchase_receipt_items", - "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": "Purchase Receipt Items", - "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": "purchase_receipt_items", + "fieldtype": "Section Break", + "label": "Purchase Receipt Items" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "get_items_from_purchase_receipts", - "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": "Get Items From Purchase Receipts", - "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": "get_items_from_purchase_receipts", + "fieldtype": "Button", + "label": "Get Items From Purchase Receipts" + }, { - "allow_bulk_edit": 0, - "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": "Purchase Receipt Items", - "length": 0, - "no_copy": 1, - "options": "Landed Cost 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 - }, + "fieldname": "items", + "fieldtype": "Table", + "label": "Purchase Receipt Items", + "no_copy": 1, + "options": "Landed Cost Item", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "sec_break1", - "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 Charges", - "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 - }, + "fieldname": "sec_break1", + "fieldtype": "Section Break", + "label": "Applicable Charges" + }, { - "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": "Taxes and Charges", - "length": 0, - "no_copy": 0, - "options": "Landed Cost 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 - }, + "fieldname": "taxes", + "fieldtype": "Table", + "label": "Taxes and Charges", + "options": "Landed Cost Taxes and Charges", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_9", - "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_9", + "fieldtype": "Section Break" + }, { - "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": "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": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "total_taxes_and_charges", + "fieldtype": "Currency", + "label": "Total Taxes and Charges", + "options": "Company:company:default_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": "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, - "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": "col_break1", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", - "fieldname": "distribute_charges_based_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": "Distribute Charges Based On", - "length": 0, - "no_copy": 0, - "options": "\nQty\nAmount", - "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": "distribute_charges_based_on", + "fieldtype": "Select", + "label": "Distribute Charges Based On", + "options": "Qty\nAmount", + "reqd": 1 + }, { - "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": 0, - "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, - "options": "Landed Cost Voucher", - "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 - }, + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Landed Cost Voucher", + "print_hide": 1, + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "sec_break2", - "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": "sec_break2", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "landed_cost_help", - "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": "Landed Cost Help", - "length": 0, - "no_copy": 0, - "options": "", - "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": "landed_cost_help", + "fieldtype": "HTML", + "label": "Landed Cost Help" } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "icon": "icon-usd", - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 1, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-08-21 14:44:30.850736", - "modified_by": "Administrator", - "module": "Stock", - "name": "Landed Cost Voucher", - "name_case": "", - "owner": "Administrator", + ], + "icon": "icon-usd", + "is_submittable": 1, + "modified": "2019-10-09 13:39:36.082777", + "modified_by": "Administrator", + "module": "Stock", + "name": "Landed Cost Voucher", + "owner": "Administrator", "permissions": [ { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 0, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 0, - "read": 1, - "report": 1, - "role": "Stock Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 1, + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "export": 1, + "read": 1, + "report": 1, + "role": "Stock Manager", + "share": 1, + "submit": 1, "write": 1 } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 1, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 + ], + "show_name_in_global_search": 1, + "sort_field": "modified", + "sort_order": "DESC" } \ No newline at end of file diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py index 3f370935ef..173b394f79 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py @@ -16,16 +16,13 @@ class LandedCostVoucher(Document): if pr.receipt_document_type and pr.receipt_document: pr_items = frappe.db.sql("""select pr_item.item_code, pr_item.description, pr_item.qty, pr_item.base_rate, pr_item.base_amount, pr_item.name, - pr_item.cost_center, pr_item.asset + pr_item.cost_center, pr_item.is_fixed_asset from `tab{doctype} Item` pr_item where parent = %s and exists(select name from tabItem where name = pr_item.item_code and (is_stock_item = 1 or is_fixed_asset=1)) """.format(doctype=pr.receipt_document_type), pr.receipt_document, as_dict=True) for d in pr_items: - if d.asset and frappe.db.get_value("Asset", d.asset, 'docstatus') == 1: - continue - item = self.append("items") item.item_code = d.item_code item.description = d.description @@ -37,15 +34,16 @@ class LandedCostVoucher(Document): item.receipt_document_type = pr.receipt_document_type item.receipt_document = pr.receipt_document item.purchase_receipt_item = d.name + item.is_fixed_asset = d.is_fixed_asset def validate(self): self.check_mandatory() - self.validate_purchase_receipts() - self.set_total_taxes_and_charges() if not self.get("items"): self.get_items_from_purchase_receipts() else: self.validate_applicable_charges_for_item() + self.validate_purchase_receipts() + self.set_total_taxes_and_charges() def check_mandatory(self): if not self.get("purchase_receipts"): @@ -64,6 +62,7 @@ class LandedCostVoucher(Document): for item in self.get("items"): if not item.receipt_document: frappe.throw(_("Item must be added using 'Get Items from Purchase Receipts' button")) + elif item.receipt_document not in receipt_documents: frappe.throw(_("Item Row {0}: {1} {2} does not exist in above '{1}' table") .format(item.idx, item.receipt_document_type, item.receipt_document)) @@ -96,8 +95,6 @@ class LandedCostVoucher(Document): else: frappe.throw(_("Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges")) - - def on_submit(self): self.update_landed_cost() @@ -107,6 +104,9 @@ class LandedCostVoucher(Document): def update_landed_cost(self): for d in self.get("purchase_receipts"): doc = frappe.get_doc(d.receipt_document_type, d.receipt_document) + + # check if there are {qty} assets created and linked to this receipt document + self.validate_asset_qty_and_status(d.receipt_document_type, doc) # set landed cost voucher amount in pr item doc.set_landed_cost_voucher_amount() @@ -118,23 +118,42 @@ class LandedCostVoucher(Document): for item in doc.get("items"): item.db_update() + # asset rate will be updated while creating asset gl entries from PI or PY + # update latest valuation rate in serial no - self.update_rate_in_serial_no(doc) + self.update_rate_in_serial_no_for_non_asset_items(doc) # update stock & gl entries for cancelled state of PR doc.docstatus = 2 doc.update_stock_ledger(allow_negative_stock=True, via_landed_cost_voucher=True) doc.make_gl_entries_on_cancel(repost_future_gle=False) - # update stock & gl entries for submit state of PR doc.docstatus = 1 doc.update_stock_ledger(allow_negative_stock=True, via_landed_cost_voucher=True) doc.make_gl_entries() - def update_rate_in_serial_no(self, receipt_document): + def validate_asset_qty_and_status(self, receipt_document_type, receipt_document): + for item in self.get('items'): + if item.is_fixed_asset: + receipt_document_type = 'purchase_invoice' if item.receipt_document_type == 'Purchase Invoice' \ + else 'purchase_receipt' + docs = frappe.db.get_all('Asset', filters={ receipt_document_type: item.receipt_document }, + fields=['name', 'docstatus']) + if not docs or len(docs) != item.qty: + frappe.throw(_('There are not enough asset created or linked to {0}. \ + Please create or link {1} Assets with respective document.').format(item.receipt_document, item.qty)) + if docs: + for d in docs: + if d.docstatus == 1: + frappe.throw(_('{2} {0} has submitted Assets.\ + Remove Item {1} from table to continue.').format( + item.receipt_document, item.item_code, item.receipt_document_type) + ) + + def update_rate_in_serial_no_for_non_asset_items(self, receipt_document): for item in receipt_document.get("items"): - if item.serial_no: + if not item.is_fixed_asset and item.serial_no: serial_nos = get_serial_nos(item.serial_no) if serial_nos: frappe.db.sql("update `tabSerial No` set purchase_rate=%s where name in ({0})" diff --git a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py index fe5d3ed6df..988cf52ed0 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py @@ -54,9 +54,10 @@ class TestLandedCostVoucher(unittest.TestCase): expected_values = { stock_in_hand_account: [800.0, 0.0], "Stock Received But Not Billed - TCP1": [0.0, 500.0], - "Expenses Included In Valuation - TCP1": [0.0, 300.0] + "Expenses Included In Valuation - TCP1": [0.0, 50.0], + "_Test Account Customs Duty - TCP1": [0.0, 150], + "_Test Account Shipping Charges - TCP1": [0.0, 100.00] } - else: expected_values = { stock_in_hand_account: [400.0, 0.0], diff --git a/erpnext/stock/doctype/price_list/price_list.py b/erpnext/stock/doctype/price_list/price_list.py index 8773b9c33f..33713faf69 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.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js index aef53ed74b..d5914f9b28 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js @@ -6,27 +6,35 @@ frappe.provide("erpnext.stock"); frappe.ui.form.on("Purchase Receipt", { - setup: function(frm) { + setup: (frm) => { + frm.make_methods = { + 'Landed Cost Voucher': () => { + let lcv = frappe.model.get_new_doc('Landed Cost Voucher'); + lcv.company = frm.doc.company; + + let lcv_receipt = frappe.model.get_new_doc('Landed Cost Purchase Receipt'); + lcv_receipt.receipt_document_type = 'Purchase Receipt'; + lcv_receipt.receipt_document = frm.doc.name; + lcv_receipt.supplier = frm.doc.supplier; + lcv_receipt.grand_total = frm.doc.grand_total; + lcv.purchase_receipts = [lcv_receipt]; + + frappe.set_route("Form", lcv.doctype, lcv.name); + }, + } + frm.custom_make_buttons = { 'Stock Entry': 'Return', 'Purchase Invoice': 'Invoice' }; - frm.set_query("asset", "items", function() { - return { - filters: { - "purchase_receipt": frm.doc.name - } - } - }); - frm.set_query("expense_account", "items", function() { return { query: "erpnext.controllers.queries.get_expense_account", - filters: {'company': frm.doc.company} + filters: {'company': frm.doc.company } } }); - + }, onload: function(frm) { erpnext.queries.setup_queries(frm, "Warehouse", function() { @@ -57,7 +65,7 @@ frappe.ui.form.on("Purchase Receipt", { toggle_display_account_head: function(frm) { var enabled = erpnext.is_perpetual_inventory_enabled(frm.doc.company) frm.fields_dict["items"].grid.set_column_disp(["cost_center"], enabled); - }, + } }); erpnext.stock.PurchaseReceiptController = erpnext.buying.BuyingController.extend({ diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 1bfdca50ea..0cb21d73f9 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -281,15 +281,15 @@ class PurchaseReceipt(BuyingController): d.rejected_warehouse not in warehouse_with_no_account: warehouse_with_no_account.append(d.warehouse) - self.get_asset_gl_entry(gl_entries, expenses_included_in_valuation) + self.get_asset_gl_entry(gl_entries) # Cost center-wise amount breakup for other charges included for valuation valuation_tax = {} for tax in self.get("taxes"): if tax.category in ("Valuation", "Valuation and Total") and flt(tax.base_tax_amount_after_discount_amount): if not tax.cost_center: frappe.throw(_("Cost Center is required in row {0} in Taxes table for type {1}").format(tax.idx, _(tax.category))) - valuation_tax.setdefault(tax.cost_center, 0) - valuation_tax[tax.cost_center] += \ + valuation_tax.setdefault(tax.name, 0) + valuation_tax[tax.name] += \ (tax.add_deduct_tax == "Add" and 1 or -1) * flt(tax.base_tax_amount_after_discount_amount) if negative_expense_to_be_booked and valuation_tax: @@ -297,37 +297,42 @@ class PurchaseReceipt(BuyingController): # If expenses_included_in_valuation account has been credited in against PI # and charges added via Landed Cost Voucher, # post valuation related charges on "Stock Received But Not Billed" + # introduced in 2014 for backward compatibility of expenses already booked in expenses_included_in_valuation account negative_expense_booked_in_pi = frappe.db.sql("""select name from `tabPurchase Invoice Item` pi where docstatus = 1 and purchase_receipt=%s and exists(select name from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=pi.parent and account=%s)""", (self.name, expenses_included_in_valuation)) - if negative_expense_booked_in_pi: - expenses_included_in_valuation = stock_rbnb - against_account = ", ".join([d.account for d in gl_entries if flt(d.debit) > 0]) total_valuation_amount = sum(valuation_tax.values()) amount_including_divisional_loss = negative_expense_to_be_booked i = 1 - for cost_center, amount in iteritems(valuation_tax): - if i == len(valuation_tax): - applicable_amount = amount_including_divisional_loss - else: - applicable_amount = negative_expense_to_be_booked * (amount / total_valuation_amount) - amount_including_divisional_loss -= applicable_amount + for tax in self.get("taxes"): + if valuation_tax.get(tax.name): - gl_entries.append( - self.get_gl_dict({ - "account": expenses_included_in_valuation, - "cost_center": cost_center, - "credit": applicable_amount, - "remarks": self.remarks or _("Accounting Entry for Stock"), - "against": against_account - }) - ) + if negative_expense_booked_in_pi: + account = stock_rbnb + else: + account = tax.account_head - i += 1 + if i == len(valuation_tax): + applicable_amount = amount_including_divisional_loss + else: + applicable_amount = negative_expense_to_be_booked * (valuation_tax[tax.name] / total_valuation_amount) + amount_including_divisional_loss -= applicable_amount + + gl_entries.append( + self.get_gl_dict({ + "account": account, + "cost_center": tax.cost_center, + "credit": applicable_amount, + "remarks": self.remarks or _("Accounting Entry for Stock"), + "against": against_account + }, item=tax) + ) + + i += 1 if warehouse_with_no_account: frappe.msgprint(_("No accounting entries for the following warehouses") + ": \n" + @@ -335,81 +340,85 @@ class PurchaseReceipt(BuyingController): return process_gl_map(gl_entries) - def get_asset_gl_entry(self, gl_entries, expenses_included_in_valuation=None): - arbnb_account, cwip_account = None, None - - if not expenses_included_in_valuation: - expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation") - - for d in self.get("items"): - asset_category = frappe.get_cached_value("Item", d.item_code, "asset_category") - cwip_enabled = is_cwip_accounting_enabled(self.company, asset_category) - - if d.is_fixed_asset and not (arbnb_account and cwip_account): - arbnb_account = self.get_company_default("asset_received_but_not_billed") - - # CWIP entry - cwip_account = get_asset_account("capital_work_in_progress_account", d.asset, - company = self.company) - - if d.is_fixed_asset and cwip_enabled: - asset_amount = flt(d.net_amount) + flt(d.item_tax_amount/self.conversion_rate) - base_asset_amount = flt(d.base_net_amount + d.item_tax_amount) - - cwip_account_currency = get_account_currency(cwip_account) - gl_entries.append(self.get_gl_dict({ - "account": cwip_account, - "against": arbnb_account, - "cost_center": d.cost_center, - "remarks": self.get("remarks") or _("Accounting Entry for Asset"), - "debit": base_asset_amount, - "debit_in_account_currency": (base_asset_amount - if cwip_account_currency == self.company_currency else asset_amount) - }, item=d)) - - # Asset received but not billed - asset_rbnb_currency = get_account_currency(arbnb_account) - gl_entries.append(self.get_gl_dict({ - "account": arbnb_account, - "against": cwip_account, - "cost_center": d.cost_center, - "remarks": self.get("remarks") or _("Accounting Entry for Asset"), - "credit": base_asset_amount, - "credit_in_account_currency": (base_asset_amount - if asset_rbnb_currency == self.company_currency else asset_amount) - }, item=d)) - - if d.is_fixed_asset and flt(d.landed_cost_voucher_amount): - asset_account = (get_asset_category_account(d.asset, 'fixed_asset_account', - company = self.company) if not cwip_enabled else cwip_account) - - gl_entries.append(self.get_gl_dict({ - "account": expenses_included_in_valuation, - "against": asset_account, - "cost_center": d.cost_center, - "remarks": self.get("remarks") or _("Accounting Entry for Stock"), - "credit": flt(d.landed_cost_voucher_amount), - "project": d.project - }, item=d)) - - gl_entries.append(self.get_gl_dict({ - "account": asset_account, - "against": expenses_included_in_valuation, - "cost_center": d.cost_center, - "remarks": self.get("remarks") or _("Accounting Entry for Stock"), - "debit": flt(d.landed_cost_voucher_amount), - "project": d.project - }, item=d)) - - if d.asset: - doc = frappe.get_doc("Asset", d.asset) - frappe.db.set_value("Asset", d.asset, "gross_purchase_amount", - doc.gross_purchase_amount + flt(d.landed_cost_voucher_amount)) - - frappe.db.set_value("Asset", d.asset, "purchase_receipt_amount", - doc.purchase_receipt_amount + flt(d.landed_cost_voucher_amount)) - + def get_asset_gl_entry(self, gl_entries): + for item in self.get("items"): + if item.is_fixed_asset: + if is_cwip_accounting_enabled(self.company, item.asset_category): + self.add_asset_gl_entries(item, gl_entries) + if flt(item.landed_cost_voucher_amount): + self.add_lcv_gl_entries(item, gl_entries) + # update assets gross amount by its valuation rate + # valuation rate is total of net rate, raw mat supp cost, tax amount, lcv amount per item + self.update_assets(item, item.valuation_rate) return gl_entries + + 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) + + 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) + + cwip_account_currency = get_account_currency(cwip_account) + # debit cwip account + gl_entries.append(self.get_gl_dict({ + "account": cwip_account, + "against": arbnb_account, + "cost_center": item.cost_center, + "remarks": self.get("remarks") or _("Accounting Entry for Asset"), + "debit": base_asset_amount, + "debit_in_account_currency": (base_asset_amount + if cwip_account_currency == self.company_currency else asset_amount) + }, item=item)) + + asset_rbnb_currency = get_account_currency(arbnb_account) + # credit arbnb account + gl_entries.append(self.get_gl_dict({ + "account": arbnb_account, + "against": cwip_account, + "cost_center": item.cost_center, + "remarks": self.get("remarks") or _("Accounting Entry for Asset"), + "credit": base_asset_amount, + "credit_in_account_currency": (base_asset_amount + if asset_rbnb_currency == self.company_currency else asset_amount) + }, item=item)) + + def add_lcv_gl_entries(self, item, gl_entries): + expenses_included_in_asset_valuation = self.get_company_default("expenses_included_in_asset_valuation") + if not is_cwip_accounting_enabled(self.company, item.asset_category): + asset_account = get_asset_category_account(asset_category=item.asset_category, \ + fieldname='fixed_asset_account', company=self.company) + else: + # This returns company's default cwip account + asset_account = get_asset_account("capital_work_in_progress_account", company=self.company) + + gl_entries.append(self.get_gl_dict({ + "account": expenses_included_in_asset_valuation, + "against": asset_account, + "cost_center": item.cost_center, + "remarks": self.get("remarks") or _("Accounting Entry for Stock"), + "credit": flt(item.landed_cost_voucher_amount), + "project": item.project + }, item=item)) + + gl_entries.append(self.get_gl_dict({ + "account": asset_account, + "against": expenses_included_in_asset_valuation, + "cost_center": item.cost_center, + "remarks": self.get("remarks") or _("Accounting Entry for Stock"), + "debit": flt(item.landed_cost_voucher_amount), + "project": item.project + }, item=item)) + + def update_assets(self, item, valuation_rate): + assets = frappe.db.get_all('Asset', + filters={ 'purchase_receipt': self.name, 'item_code': item.item_code } + ) + + for asset in assets: + frappe.db.set_value("Asset", asset.name, "gross_purchase_amount", flt(valuation_rate)) + frappe.db.set_value("Asset", asset.name, "purchase_receipt_amount", flt(valuation_rate)) def update_status(self, status): self.set_status(update=True, status = status) @@ -517,7 +526,8 @@ def make_purchase_invoice(source_name, target_doc=None): "purchase_order_item": "po_detail", "purchase_order": "purchase_order", "is_fixed_asset": "is_fixed_asset", - "asset": "asset", + "asset_location": "asset_location", + "asset_category": 'asset_category' }, "postprocess": update_item, "filter": lambda d: get_pending_qty(d)[0] <= 0 if not doc.get("is_return") else get_pending_qty(d)[0] > 0 diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index e9ddf9d976..c80b9bd04b 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -66,14 +66,15 @@ class TestPurchaseReceipt(unittest.TestCase): expected_values = { stock_in_hand_account: [750.0, 0.0], "Stock Received But Not Billed - TCP1": [0.0, 500.0], - "Expenses Included In Valuation - TCP1": [0.0, 250.0] + "_Test Account Shipping Charges - TCP1": [0.0, 100.0], + "_Test Account Customs Duty - TCP1": [0.0, 150.0] } else: expected_values = { stock_in_hand_account: [375.0, 0.0], fixed_asset_account: [375.0, 0.0], "Stock Received But Not Billed - TCP1": [0.0, 500.0], - "Expenses Included In Valuation - TCP1": [0.0, 250.0] + "_Test Account Shipping Charges - TCP1": [0.0, 250.0] } for gle in gl_entries: self.assertEqual(expected_values[gle.account][0], gle.debit) @@ -281,8 +282,8 @@ class TestPurchaseReceipt(unittest.TestCase): serial_no=serial_no, basic_rate=100, do_not_submit=True) self.assertRaises(SerialNoDuplicateError, se.submit) - def test_serialized_asset_item(self): - asset_item = "Test Serialized Asset Item" + def test_auto_asset_creation(self): + asset_item = "Test Asset Item" if not frappe.db.exists('Item', asset_item): asset_category = frappe.get_all('Asset Category') @@ -308,30 +309,18 @@ class TestPurchaseReceipt(unittest.TestCase): asset_category = doc.name item_data = make_item(asset_item, {'is_stock_item':0, - 'stock_uom': 'Box', 'is_fixed_asset': 1, 'has_serial_no': 1, - 'asset_category': asset_category, 'serial_no_series': 'ABC.###'}) + 'stock_uom': 'Box', 'is_fixed_asset': 1, 'auto_create_assets': 1, + 'asset_category': asset_category, 'asset_naming_series': 'ABC.###'}) asset_item = item_data.item_code pr = make_purchase_receipt(item_code=asset_item, qty=3) - asset = frappe.db.get_value('Asset', {'purchase_receipt': pr.name}, 'name') - asset_movement = frappe.db.get_value('Asset Movement', {'reference_name': pr.name}, 'name') - serial_nos = frappe.get_all('Serial No', {'asset': asset}, 'name') + assets = frappe.db.get_all('Asset', filters={'purchase_receipt': pr.name}) - self.assertEquals(len(serial_nos), 3) + self.assertEquals(len(assets), 3) - location = frappe.db.get_value('Serial No', serial_nos[0].name, 'location') + location = frappe.db.get_value('Asset', assets[0].name, 'location') self.assertEquals(location, "Test Location") - frappe.db.set_value("Asset", asset, "purchase_receipt", "") - frappe.db.set_value("Purchase Receipt Item", pr.items[0].name, "asset", "") - - pr.load_from_db() - - pr.cancel() - serial_nos = frappe.get_all('Serial No', {'asset': asset}, 'name') or [] - self.assertEquals(len(serial_nos), 0) - frappe.db.sql("delete from `tabAsset`") - def test_purchase_receipt_for_enable_allow_cost_center_in_entry_of_bs_account(self): from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings') @@ -534,8 +523,10 @@ def make_purchase_receipt(**args): received_qty = args.received_qty or qty rejected_qty = args.rejected_qty or flt(received_qty) - flt(qty) + item_code = args.item or args.item_code or "_Test Item" + uom = args.uom or frappe.db.get_value("Item", item_code, "stock_uom") or "_Test UOM" pr.append("items", { - "item_code": args.item or args.item_code or "_Test Item", + "item_code": item_code, "warehouse": args.warehouse or "_Test Warehouse - _TC", "qty": qty, "received_qty": received_qty, @@ -545,7 +536,7 @@ def make_purchase_receipt(**args): "conversion_factor": args.conversion_factor or 1.0, "serial_no": args.serial_no, "stock_uom": args.stock_uom or "_Test UOM", - "uom": args.uom or "_Test UOM", + "uom": uom, "cost_center": args.cost_center or frappe.get_cached_value('Company', pr.company, 'cost_center'), "asset_location": args.location or "Test Location" }) diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json index 446a488a7e..16ec8db335 100644 --- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json +++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json @@ -67,26 +67,26 @@ "warehouse_and_reference", "warehouse", "rejected_warehouse", - "quality_inspection", "purchase_order", "material_request", - "purchase_order_item", - "material_request_item", "column_break_40", "is_fixed_asset", - "asset", "asset_location", + "asset_category", "schedule_date", + "quality_inspection", "stock_qty", + "purchase_order_item", + "material_request_item", "section_break_45", + "allow_zero_valuation_rate", + "bom", + "col_break5", "serial_no", "batch_no", "column_break_48", "rejected_serial_no", "expense_account", - "col_break5", - "allow_zero_valuation_rate", - "bom", "include_exploded_items", "item_tax_rate", "accounting_dimensions_section", @@ -500,21 +500,6 @@ "print_hide": 1, "read_only": 1 }, - { - "depends_on": "is_fixed_asset", - "fieldname": "asset", - "fieldtype": "Link", - "label": "Asset", - "no_copy": 1, - "options": "Asset" - }, - { - "depends_on": "is_fixed_asset", - "fieldname": "asset_location", - "fieldtype": "Link", - "label": "Asset Location", - "options": "Location" - }, { "fieldname": "purchase_order", "fieldtype": "Link", @@ -553,6 +538,7 @@ "fieldtype": "Section Break" }, { + "depends_on": "eval:!doc.is_fixed_asset", "fieldname": "serial_no", "fieldtype": "Small Text", "in_list_view": 1, @@ -562,10 +548,11 @@ "oldfieldtype": "Text" }, { + "depends_on": "eval:!doc.is_fixed_asset", "fieldname": "batch_no", "fieldtype": "Link", "in_list_view": 1, - "label": "Batch No", + "label": "Batch No!", "no_copy": 1, "oldfieldname": "batch_no", "oldfieldtype": "Link", @@ -577,6 +564,7 @@ "fieldtype": "Column Break" }, { + "depends_on": "eval:!doc.is_fixed_asset", "fieldname": "rejected_serial_no", "fieldtype": "Small Text", "label": "Rejected Serial No", @@ -814,11 +802,28 @@ "fieldtype": "Data", "label": "Manufacturer Part Number", "read_only": 1 + }, + { + "depends_on": "is_fixed_asset", + "fieldname": "asset_location", + "fieldtype": "Link", + "label": "Asset Location", + "options": "Location" + }, + { + "depends_on": "is_fixed_asset", + "fetch_from": "item_code.asset_category", + "fieldname": "asset_category", + "fieldtype": "Link", + "in_preview": 1, + "label": "Asset Category", + "options": "Asset Category", + "read_only": 1 } ], "idx": 1, "istable": 1, - "modified": "2019-09-17 22:33:01.109004", + "modified": "2019-10-14 16:03:25.499557", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt Item", diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 7c2e09e463..9f47edc774 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) @@ -479,7 +482,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) @@ -634,14 +636,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 @@ -905,27 +899,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 {} @@ -935,8 +908,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)