Merge branch 'develop' into scheduling-ui-rewrite

This commit is contained in:
Pranav Nachnekar 2019-11-25 12:24:32 +00:00 committed by GitHub
commit fc5d8fcd9f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
93 changed files with 3552 additions and 4284 deletions

View File

@ -93,7 +93,8 @@ def get_gl_entries(account, to_date):
fields = ['posting_date', 'debit', 'credit'], fields = ['posting_date', 'debit', 'credit'],
filters = [ filters = [
dict(posting_date = ('<', to_date)), dict(posting_date = ('<', to_date)),
dict(account = ('in', child_accounts)) dict(account = ('in', child_accounts)),
dict(voucher_type = ('!=', 'Period Closing Voucher'))
], ],
order_by = 'posting_date asc') order_by = 'posting_date asc')

View File

@ -398,7 +398,7 @@ cur_frm.cscript.voucher_type = function(doc, cdt, cdn) {
method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_default_bank_cash_account", method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_default_bank_cash_account",
args: { args: {
"account_type": (doc.voucher_type=="Bank Entry" ? "account_type": (doc.voucher_type=="Bank Entry" ?
"Bank" : (doc.voucher_type=="Cash" ? "Cash" : null)), "Bank" : (doc.voucher_type=="Cash Entry" ? "Cash" : null)),
"company": doc.company "company": doc.company
}, },
callback: function(r) { callback: function(r) {

View File

@ -931,9 +931,9 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
grand_total = doc.rounded_total or doc.grand_total grand_total = doc.rounded_total or doc.grand_total
outstanding_amount = doc.outstanding_amount outstanding_amount = doc.outstanding_amount
elif dt in ("Expense Claim"): elif dt in ("Expense Claim"):
grand_total = doc.total_sanctioned_amount grand_total = doc.total_sanctioned_amount + doc.total_taxes_and_charges
outstanding_amount = doc.total_sanctioned_amount \ outstanding_amount = doc.grand_total \
- doc.total_amount_reimbursed - flt(doc.total_advance_amount) - doc.total_amount_reimbursed
elif dt == "Employee Advance": elif dt == "Employee Advance":
grand_total = doc.advance_amount grand_total = doc.advance_amount
outstanding_amount = flt(doc.advance_amount) - flt(doc.paid_amount) outstanding_amount = flt(doc.advance_amount) - flt(doc.paid_amount)

View File

@ -181,8 +181,9 @@ def get_serial_no_for_item(args):
item_details.serial_no = get_serial_no(args) item_details.serial_no = get_serial_no(args)
return item_details return item_details
def get_pricing_rule_for_item(args, price_list_rate=0, doc=None): 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 from erpnext.accounts.doctype.pricing_rule.utils import (get_pricing_rules,
get_applied_pricing_rules, get_pricing_rule_items)
if isinstance(doc, string_types): if isinstance(doc, string_types):
doc = json.loads(doc) 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')) item_details, args.get('item_code'))
return item_details 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): if not (args.item_group and args.brand):
try: try:
args.item_group, args.brand = frappe.get_cached_value("Item", args.item_code, ["item_group", "brand"]) 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.supplier_group = frappe.get_cached_value("Supplier", args.supplier, "supplier_group")
args.customer = args.customer_group = args.territory = None 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): def get_pricing_rule_details(args, pricing_rule):
return frappe._dict({ return frappe._dict({
'pricing_rule': pricing_rule.name, 'pricing_rule': pricing_rule.name,
'rate_or_discount': pricing_rule.rate_or_discount, 'rate_or_discount': pricing_rule.rate_or_discount,
'margin_type': pricing_rule.margin_type, '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') 'child_docname': args.get('child_docname')
}) })
@ -327,10 +337,10 @@ def set_discount_amount(rate, item_details):
item_details.rate = rate item_details.rate = rate
def remove_pricing_rule_for_item(pricing_rules, item_details, item_code=None): 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(','): for d in pricing_rules.split(','):
if not d or not frappe.db.exists("Pricing Rule", d): continue 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.price_or_product_discount == 'Price':
if pricing_rule.rate_or_discount == 'Discount Percentage': 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')) else pricing_rule.get('free_item'))
if pricing_rule.get("mixed_conditions") or pricing_rule.get("apply_rule_on_other"): 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) items = get_pricing_rule_items(pricing_rule)
item_details.apply_on = apply_on 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.applied_on_items = ','.join(items)
item_details.pricing_rules = '' item_details.pricing_rules = ''

View File

@ -8,6 +8,7 @@ import frappe, copy, json
from frappe import throw, _ from frappe import throw, _
from six import string_types from six import string_types
from frappe.utils import flt, cint, get_datetime 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.doctype.warehouse.warehouse import get_child_warehouses
from erpnext.stock.get_item_details import get_conversion_factor 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 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: 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: elif pricing_rules[0].is_cumulative:
items = [args.get(frappe.scrub(pr_doc.get('apply_on')))] items = [args.get(frappe.scrub(pr_doc.get('apply_on')))]
@ -339,16 +341,18 @@ def get_qty_and_rate_for_mixed_conditions(doc, pr_doc, args):
sum_qty += data[0] sum_qty += data[0]
sum_amt += data[1] 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): def get_qty_and_rate_for_other_item(doc, pr_doc, pricing_rules):
for d in get_pricing_rule_items(pr_doc): items = get_pricing_rule_items(pr_doc)
for row in doc.items: for row in doc.items:
if d == row.get(frappe.scrub(pr_doc.apply_on)): 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"), pricing_rules = filter_pricing_rules_for_qty_amount(row.get("stock_qty"),
row.get("amount"), pricing_rules, row) row.get("amount"), pricing_rules, row)
if pricing_rules and pricing_rules[0]: if pricing_rules and pricing_rules[0]:
pricing_rules[0].apply_rule_on_other_items = items
return pricing_rules return pricing_rules
def get_qty_amount_data_for_cumulative(pr_doc, doc, items=[]): def get_qty_amount_data_for_cumulative(pr_doc, doc, items=[]):
@ -397,31 +401,7 @@ def get_qty_amount_data_for_cumulative(pr_doc, doc, items=[]):
return [sum_qty, sum_amt] return [sum_qty, sum_amt]
def validate_pricing_rules(doc): def apply_pricing_rule_on_transaction(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):
conditions = "apply_on = 'Transaction'" conditions = "apply_on = 'Transaction'"
values = {} values = {}
@ -453,7 +433,7 @@ def validate_pricing_rule_on_transactions(doc):
elif d.price_or_product_discount == 'Product': elif d.price_or_product_discount == 'Product':
apply_pricing_rule_for_free_items(doc, d) 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(',') return (item_row.get("pricing_rules").split(',')
if item_row.get("pricing_rules") else []) 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'), 'item_code': pricing_rule.get('free_item'),
'qty': pricing_rule.get('free_qty'), 'qty': pricing_rule.get('free_qty'),
'uom': pricing_rule.get('free_item_uom'), '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 'is_free_item': 1
}) })
doc.set_missing_values() 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 <b>{1}</b> on the item <b>{2}</b>")
.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): def get_pricing_rule_items(pr_doc):
apply_on_data = []
apply_on = frappe.scrub(pr_doc.get('apply_on')) apply_on = frappe.scrub(pr_doc.get('apply_on'))
pricing_rule_apply_on = apply_on_table.get(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() if pr_doc.apply_rule_on_other:
def validate_pricing_rule_for_different_cond(doc): apply_on = frappe.scrub(pr_doc.apply_rule_on_other)
if isinstance(doc, string_types): apply_on_data.append(pr_doc.get(apply_on))
doc = json.loads(doc)
doc = frappe.get_doc(doc) return list(set(apply_on_data))
for d in doc.get("items"):
validate_pricing_rule_on_items(doc, d, True)
return doc
def validate_coupon_code(coupon_name): def validate_coupon_code(coupon_name):
from frappe.utils import today,getdate from frappe.utils import today,getdate

View File

@ -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]; var row = locals[cdt][cdn];
if(row.asset) { if(row.item_code) {
frappe.call({ frappe.call({
method: "erpnext.assets.doctype.asset_category.asset_category.get_asset_category_account", method: "erpnext.assets.doctype.asset_category.asset_category.get_asset_category_account",
args: { args: {
"asset": row.asset, "item": row.item_code,
"fieldname": "fixed_asset_account", "fieldname": "fixed_asset_account",
"account": row.expense_account "company": frm.doc.company
}, },
callback: function(r, rt) { callback: function(r, rt) {
frappe.model.set_value(cdt, cdn, "expense_account", r.message); 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) { cur_frm.set_query("expense_account", "items", function(doc) {
return { return {
query: "erpnext.controllers.queries.get_expense_account", query: "erpnext.controllers.queries.get_expense_account",
filters: {'company': doc.company} 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'
}
} }
}); });

View File

@ -98,7 +98,6 @@ class PurchaseInvoice(BuyingController):
self.set_against_expense_account() self.set_against_expense_account()
self.validate_write_off_account() self.validate_write_off_account()
self.validate_multiple_billing("Purchase Receipt", "pr_detail", "amount", "items") self.validate_multiple_billing("Purchase Receipt", "pr_detail", "amount", "items")
self.validate_fixed_asset()
self.create_remarks() self.create_remarks()
self.set_status() self.set_status()
self.validate_purchase_receipt_if_update_stock() self.validate_purchase_receipt_if_update_stock()
@ -238,13 +237,8 @@ class PurchaseInvoice(BuyingController):
item.expense_account = warehouse_account[item.warehouse]["account"] item.expense_account = warehouse_account[item.warehouse]["account"]
else: else:
item.expense_account = stock_not_billed_account item.expense_account = stock_not_billed_account
elif item.is_fixed_asset and not is_cwip_accounting_enabled(asset_category):
elif item.is_fixed_asset and not is_cwip_accounting_enabled(self.company, asset_category): item.expense_account = get_asset_category_account('fixed_asset_account', item=item.item_code,
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',
company = self.company) company = self.company)
elif item.is_fixed_asset and item.pr_detail: elif item.is_fixed_asset and item.pr_detail:
item.expense_account = asset_received_but_not_billed item.expense_account = asset_received_but_not_billed
@ -414,7 +408,7 @@ class PurchaseInvoice(BuyingController):
for item in self.get("items"): for item in self.get("items"):
if item.item_code and item.is_fixed_asset: if item.item_code and item.is_fixed_asset:
asset_category = frappe.get_cached_value("Item", item.item_code, "asset_category") asset_category = frappe.get_cached_value("Item", item.item_code, "asset_category")
if is_cwip_accounting_enabled(self.company, asset_category): if is_cwip_accounting_enabled(asset_category):
return 1 return 1
return 0 return 0
@ -458,6 +452,10 @@ class PurchaseInvoice(BuyingController):
fields = ["voucher_detail_no", "stock_value_difference"], filters={'voucher_no': self.name}): fields = ["voucher_detail_no", "stock_value_difference"], filters={'voucher_no': self.name}):
voucher_wise_stock_value.setdefault(d.voucher_detail_no, d.stock_value_difference) voucher_wise_stock_value.setdefault(d.voucher_detail_no, d.stock_value_difference)
valuation_tax_accounts = [d.account_head for d in self.get("taxes")
if d.category in ('Valuation', 'Total and Valuation')
and flt(d.base_tax_amount_after_discount_amount)]
for item in self.get("items"): for item in self.get("items"):
if flt(item.base_net_amount): if flt(item.base_net_amount):
account_currency = get_account_currency(item.expense_account) account_currency = get_account_currency(item.expense_account)
@ -506,31 +504,60 @@ class PurchaseInvoice(BuyingController):
"credit": flt(item.rm_supp_cost) "credit": flt(item.rm_supp_cost)
}, warehouse_account[self.supplier_warehouse]["account_currency"], item=item)) }, warehouse_account[self.supplier_warehouse]["account_currency"], item=item))
elif not item.is_fixed_asset or (item.is_fixed_asset and not is_cwip_accounting_enabled(self.company, elif not item.is_fixed_asset or (item.is_fixed_asset and not is_cwip_accounting_enabled(asset_category)):
asset_category)):
expense_account = (item.expense_account expense_account = (item.expense_account
if (not item.enable_deferred_expense or self.is_return) else item.deferred_expense_account) if (not item.enable_deferred_expense or self.is_return) else item.deferred_expense_account)
gl_entries.append( if not item.is_fixed_asset:
self.get_gl_dict({ 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({
"account": expense_account, "account": expense_account,
"against": self.supplier, "against": self.supplier,
"debit": flt(item.base_net_amount, item.precision("base_net_amount")), "debit": 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"))),
"cost_center": item.cost_center, "cost_center": item.cost_center,
"project": item.project "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 \ if self.auto_accounting_for_stock and self.is_opening == "No" and \
item.item_code in stock_items and item.item_tax_amount: item.item_code in stock_items and item.item_tax_amount:
# Post reverse entry for Stock-Received-But-Not-Billed if it is booked in Purchase Receipt # Post reverse entry for Stock-Received-But-Not-Billed if it is booked in Purchase Receipt
if item.purchase_receipt: if item.purchase_receipt and valuation_tax_accounts:
negative_expense_booked_in_pr = frappe.db.sql("""select name from `tabGL Entry` negative_expense_booked_in_pr = frappe.db.sql("""select name from `tabGL Entry`
where voucher_type='Purchase Receipt' and voucher_no=%s and account=%s""", where voucher_type='Purchase Receipt' and voucher_no=%s and account in %s""",
(item.purchase_receipt, self.expenses_included_in_valuation)) (item.purchase_receipt, valuation_tax_accounts))
if not negative_expense_booked_in_pr: if not negative_expense_booked_in_pr:
gl_entries.append( gl_entries.append(
@ -547,30 +574,27 @@ class PurchaseInvoice(BuyingController):
item.precision("item_tax_amount")) item.precision("item_tax_amount"))
def get_asset_gl_entry(self, gl_entries): def get_asset_gl_entry(self, gl_entries):
for item in self.get("items"): arbnb_account = self.get_company_default("asset_received_but_not_billed")
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") eiiav_account = self.get_company_default("expenses_included_in_asset_valuation")
for item in self.get("items"):
if item.is_fixed_asset:
asset_amount = flt(item.net_amount) + flt(item.item_tax_amount/self.conversion_rate) 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) base_asset_amount = flt(item.base_net_amount + item.item_tax_amount)
if (not item.expense_account or frappe.db.get_value('Account', item_exp_acc_type = frappe.db.get_value('Account', item.expense_account, 'account_type')
item.expense_account, 'account_type') not in ['Asset Received But Not Billed', 'Fixed Asset']): if (not item.expense_account or item_exp_acc_type not in ['Asset Received But Not Billed', 'Fixed Asset']):
arbnb_account = self.get_company_default("asset_received_but_not_billed")
item.expense_account = arbnb_account item.expense_account = arbnb_account
if not self.update_stock: 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({ gl_entries.append(self.get_gl_dict({
"account": item.expense_account, "account": item.expense_account,
"against": self.supplier, "against": self.supplier,
"remarks": self.get("remarks") or _("Accounting Entry for Asset"), "remarks": self.get("remarks") or _("Accounting Entry for Asset"),
"debit": base_asset_amount, "debit": base_asset_amount,
"debit_in_account_currency": (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 "cost_center": item.cost_center
}, item=item)) }, item=item))
@ -587,8 +611,7 @@ class PurchaseInvoice(BuyingController):
item.item_tax_amount / self.conversion_rate) item.item_tax_amount / self.conversion_rate)
}, item=item)) }, item=item))
else: else:
cwip_account = get_asset_account("capital_work_in_progress_account", cwip_account = get_asset_account("capital_work_in_progress_account", company = self.company)
item.asset, company = self.company)
cwip_account_currency = get_account_currency(cwip_account) cwip_account_currency = get_account_currency(cwip_account)
gl_entries.append(self.get_gl_dict({ gl_entries.append(self.get_gl_dict({
@ -614,6 +637,36 @@ class PurchaseInvoice(BuyingController):
item.item_tax_amount / self.conversion_rate) item.item_tax_amount / self.conversion_rate)
}, item=item)) }, 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 return gl_entries
def make_stock_adjustment_entry(self, gl_entries, item, voucher_wise_stock_value, account_currency): def make_stock_adjustment_entry(self, gl_entries, item, voucher_wise_stock_value, account_currency):
@ -664,14 +717,14 @@ class PurchaseInvoice(BuyingController):
if account_currency==self.company_currency \ if account_currency==self.company_currency \
else tax.tax_amount_after_discount_amount, else tax.tax_amount_after_discount_amount,
"cost_center": tax.cost_center "cost_center": tax.cost_center
}, account_currency) }, account_currency, item=tax)
) )
# accumulate valuation 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.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: 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))) 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.setdefault(tax.name, 0)
valuation_tax[tax.cost_center] += \ valuation_tax[tax.name] += \
(tax.add_deduct_tax == "Add" and 1 or -1) * flt(tax.base_tax_amount_after_discount_amount) (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: if self.is_opening == "No" and self.negative_expense_to_be_booked and valuation_tax:
@ -681,35 +734,37 @@ class PurchaseInvoice(BuyingController):
total_valuation_amount = sum(valuation_tax.values()) total_valuation_amount = sum(valuation_tax.values())
amount_including_divisional_loss = self.negative_expense_to_be_booked amount_including_divisional_loss = self.negative_expense_to_be_booked
i = 1 i = 1
for cost_center, amount in iteritems(valuation_tax): for tax in self.get("taxes"):
if valuation_tax.get(tax.name):
if i == len(valuation_tax): if i == len(valuation_tax):
applicable_amount = amount_including_divisional_loss applicable_amount = amount_including_divisional_loss
else: else:
applicable_amount = self.negative_expense_to_be_booked * (amount / total_valuation_amount) applicable_amount = self.negative_expense_to_be_booked * (valuation_tax[tax.name] / total_valuation_amount)
amount_including_divisional_loss -= applicable_amount amount_including_divisional_loss -= applicable_amount
gl_entries.append( gl_entries.append(
self.get_gl_dict({ self.get_gl_dict({
"account": self.expenses_included_in_valuation, "account": tax.account_head,
"cost_center": cost_center, "cost_center": tax.cost_center,
"against": self.supplier, "against": self.supplier,
"credit": applicable_amount, "credit": applicable_amount,
"remarks": self.remarks or "Accounting Entry for Stock" "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: if self.auto_accounting_for_stock and self.update_stock and valuation_tax:
for cost_center, amount in iteritems(valuation_tax): for tax in self.get("taxes"):
if valuation_tax.get(tax.name):
gl_entries.append( gl_entries.append(
self.get_gl_dict({ self.get_gl_dict({
"account": self.expenses_included_in_valuation, "account": tax.account_head,
"cost_center": cost_center, "cost_center": tax.cost_center,
"against": self.supplier, "against": self.supplier,
"credit": amount, "credit": valuation_tax[tax.name],
"remarks": self.remarks or "Accounting Entry for Stock" "remarks": self.remarks or "Accounting Entry for Stock"
}) }, item=tax)
) )
def make_payment_gl_entries(self, gl_entries): def make_payment_gl_entries(self, gl_entries):

View File

@ -207,16 +207,17 @@ class TestPurchaseInvoice(unittest.TestCase):
self.check_gle_for_pi(pi.name) self.check_gle_for_pi(pi.name)
def check_gle_for_pi(self, pi): 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 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) self.assertTrue(gl_entries)
expected_values = dict((d[0], d) for d in [ expected_values = dict((d[0], d) for d in [
["Creditors - TCP1", 0, 720], ["Creditors - TCP1", 0, 720],
["Stock Received But Not Billed - TCP1", 500.0, 0], ["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], ["_Test Account VAT - TCP1", 120.0, 0]
]) ])
for i, gle in enumerate(gl_entries): for i, gle in enumerate(gl_entries):

View File

@ -71,8 +71,8 @@
"expense_account", "expense_account",
"col_break5", "col_break5",
"is_fixed_asset", "is_fixed_asset",
"asset",
"asset_location", "asset_location",
"asset_category",
"deferred_expense_section", "deferred_expense_section",
"deferred_expense_account", "deferred_expense_account",
"service_stop_date", "service_stop_date",
@ -116,6 +116,8 @@
"fieldtype": "Column Break" "fieldtype": "Column Break"
}, },
{ {
"fetch_from": "item_code.item_name",
"fetch_if_empty": 1,
"fieldname": "item_name", "fieldname": "item_name",
"fieldtype": "Data", "fieldtype": "Data",
"in_global_search": 1, "in_global_search": 1,
@ -414,6 +416,7 @@
"print_hide": 1 "print_hide": 1
}, },
{ {
"depends_on": "eval:!doc.is_fixed_asset",
"fieldname": "batch_no", "fieldname": "batch_no",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Batch No", "label": "Batch No",
@ -425,12 +428,14 @@
"fieldtype": "Column Break" "fieldtype": "Column Break"
}, },
{ {
"depends_on": "eval:!doc.is_fixed_asset",
"fieldname": "serial_no", "fieldname": "serial_no",
"fieldtype": "Text", "fieldtype": "Text",
"label": "Serial No", "label": "Serial No",
"no_copy": 1 "no_copy": 1
}, },
{ {
"depends_on": "eval:!doc.is_fixed_asset",
"fieldname": "rejected_serial_no", "fieldname": "rejected_serial_no",
"fieldtype": "Text", "fieldtype": "Text",
"label": "Rejected Serial No", "label": "Rejected Serial No",
@ -615,6 +620,7 @@
}, },
{ {
"default": "0", "default": "0",
"fetch_from": "item_code.is_fixed_asset",
"fieldname": "is_fixed_asset", "fieldname": "is_fixed_asset",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 1, "hidden": 1,
@ -623,14 +629,6 @@
"print_hide": 1, "print_hide": 1,
"read_only": 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", "depends_on": "is_fixed_asset",
"fieldname": "asset_location", "fieldname": "asset_location",
@ -676,7 +674,7 @@
"fieldname": "pr_detail", "fieldname": "pr_detail",
"fieldtype": "Data", "fieldtype": "Data",
"hidden": 1, "hidden": 1,
"label": "PR Detail", "label": "Purchase Receipt Detail",
"no_copy": 1, "no_copy": 1,
"oldfieldname": "pr_detail", "oldfieldname": "pr_detail",
"oldfieldtype": "Data", "oldfieldtype": "Data",
@ -754,11 +752,21 @@
"fieldtype": "Data", "fieldtype": "Data",
"label": "Manufacturer Part Number", "label": "Manufacturer Part Number",
"read_only": 1 "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, "idx": 1,
"istable": 1, "istable": 1,
"modified": "2019-09-17 22:32:05.984240", "modified": "2019-11-21 16:27:52.043744",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Purchase Invoice Item", "name": "Purchase Invoice Item",

View File

@ -357,14 +357,11 @@ def get_customer_wise_price_list():
def get_bin_data(pos_profile): def get_bin_data(pos_profile):
itemwise_bin_data = {} itemwise_bin_data = {}
cond = "1=1" filters = { 'actual_qty': ['>', 0] }
if pos_profile.get('warehouse'): 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` bin_data = frappe.db.get_all('Bin', fields = ['item_code', 'warehouse', 'actual_qty'], filters=filters)
where actual_qty > 0 and {cond}""".format(cond=cond), {
'warehouse': frappe.db.escape(pos_profile.get('warehouse'))
}, as_dict=1)
for bins in bin_data: for bins in bin_data:
if bins.item_code not in itemwise_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): def make_email_queue(email_queue):
name_list = [] name_list = []
for key, data in iteritems(email_queue): for key, data in iteritems(email_queue):
name = frappe.db.get_value('Sales Invoice', {'offline_pos_name': key}, 'name') name = frappe.db.get_value('Sales Invoice', {'offline_pos_name': key}, 'name')
if not name: continue
data = json.loads(data) data = json.loads(data)
sender = frappe.session.user sender = frappe.session.user
print_format = "POS Invoice" if not cint(frappe.db.get_value('Print Format', 'POS Invoice', 'disabled')) else None 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)] attachments = [frappe.attach_print('Sales Invoice', name, print_format=print_format)]
make(subject=data.get('subject'), content=data.get('content'), recipients=data.get('recipients'), make(subject=data.get('subject'), content=data.get('content'), recipients=data.get('recipients'),

View File

@ -136,6 +136,16 @@ class SalesInvoice(SellingController):
if self.redeem_loyalty_points and self.loyalty_program and self.loyalty_points: if self.redeem_loyalty_points and self.loyalty_program and self.loyalty_points:
validate_loyalty_points(self, 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): def before_save(self):
set_account_for_mode_of_payment(self) set_account_for_mode_of_payment(self)
@ -991,10 +1001,8 @@ class SalesInvoice(SellingController):
continue continue
for serial_no in item.serial_no.split("\n"): for serial_no in item.serial_no.split("\n"):
if serial_no and frappe.db.exists('Serial No', serial_no): if serial_no and frappe.db.get_value('Serial No', serial_no, 'item_code') == item.item_code:
sno = frappe.get_doc('Serial No', serial_no) frappe.db.set_value('Serial No', serial_no, 'sales_invoice', invoice)
sno.sales_invoice = invoice
sno.db_update()
def validate_serial_numbers(self): def validate_serial_numbers(self):
""" """
@ -1040,8 +1048,9 @@ class SalesInvoice(SellingController):
continue continue
for serial_no in item.serial_no.split("\n"): for serial_no in item.serial_no.split("\n"):
sales_invoice = frappe.db.get_value("Serial No", serial_no, "sales_invoice") sales_invoice, item_code = frappe.db.get_value("Serial No", serial_no,
if sales_invoice and self.name != sales_invoice: ["sales_invoice", "item_code"])
if sales_invoice and item_code == item.item_code and self.name != sales_invoice:
sales_invoice_company = frappe.db.get_value("Sales Invoice", sales_invoice, "company") sales_invoice_company = frappe.db.get_value("Sales Invoice", sales_invoice, "company")
if sales_invoice_company == self.company: if sales_invoice_company == self.company:
frappe.throw(_("Serial Number: {0} is already referenced in Sales Invoice: {1}" frappe.throw(_("Serial Number: {0} is already referenced in Sales Invoice: {1}"

View File

@ -163,15 +163,34 @@ def validate_account_for_perpetual_inventory(gl_map):
.format(account), StockAccountInvalidTransaction) .format(account), StockAccountInvalidTransaction)
elif account_bal != stock_bal: elif account_bal != stock_bal:
frappe.throw(_("Account Balance ({0}) and Stock Value ({1}) is out of sync for account {2} and linked warehouse ({3}). Please create adjustment Journal Entry for amount {4}.") precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"),
.format(account_bal, stock_bal, account, comma_and(warehouse_list), stock_bal - account_bal), currency=frappe.get_cached_value('Company', gl_map[0].company, "default_currency"))
StockValueAndAccountBalanceOutOfSync)
diff = flt(stock_bal - account_bal, precision)
error_reason = _("Stock Value ({0}) and Account Balance ({1}) are out of sync for account {2} and it's linked warehouses.").format(
stock_bal, account_bal, frappe.bold(account))
error_resolution = _("Please create adjustment Journal Entry for amount {0} ").format(frappe.bold(diff))
stock_adjustment_account = frappe.db.get_value("Company",gl_map[0].company,"stock_adjustment_account")
db_or_cr_warehouse_account =('credit_in_account_currency' if diff < 0 else 'debit_in_account_currency')
db_or_cr_stock_adjustment_account = ('debit_in_account_currency' if diff < 0 else 'credit_in_account_currency')
journal_entry_args = {
'accounts':[
{'account': account, db_or_cr_warehouse_account : abs(diff)},
{'account': stock_adjustment_account, db_or_cr_stock_adjustment_account : abs(diff) }]
}
frappe.msgprint(msg="""{0}<br></br>{1}<br></br>""".format(error_reason, error_resolution),
raise_exception=StockValueAndAccountBalanceOutOfSync,
title=_('Values Out Of Sync'),
primary_action={
'label': 'Make Journal Entry',
'client_action': 'erpnext.route_to_adjustment_jv',
'args': journal_entry_args
})
def validate_cwip_accounts(gl_map): def validate_cwip_accounts(gl_map):
cwip_enabled = cint(frappe.get_cached_value("Company",
gl_map[0].company, "enable_cwip_accounting"))
if not cwip_enabled:
cwip_enabled = any([cint(ac.enable_cwip_accounting) for ac in frappe.db.get_all("Asset Category","enable_cwip_accounting")]) cwip_enabled = any([cint(ac.enable_cwip_accounting) for ac in frappe.db.get_all("Asset Category","enable_cwip_accounting")])
if cwip_enabled and gl_map[0].voucher_type == "Journal Entry": if cwip_enabled and gl_map[0].voucher_type == "Journal Entry":

View File

@ -188,7 +188,11 @@ class ReceivablePayableReport(object):
self.data.append(row) self.data.append(row)
def set_invoice_details(self, row): def set_invoice_details(self, row):
row.update(self.invoice_details.get(row.voucher_no, {})) invoice_details = self.invoice_details.get(row.voucher_no, {})
if row.due_date:
invoice_details.pop("due_date", None)
row.update(invoice_details)
if row.voucher_type == 'Sales Invoice': if row.voucher_type == 'Sales Invoice':
if self.filters.show_delivery_notes: if self.filters.show_delivery_notes:
self.set_delivery_notes(row) self.set_delivery_notes(row)

View File

@ -36,6 +36,9 @@ class AccountsReceivableSummary(ReceivablePayableReport):
self.filters.report_date) or {} self.filters.report_date) or {}
for party, party_dict in iteritems(self.party_total): for party, party_dict in iteritems(self.party_total):
if party_dict.outstanding <= 0:
continue
row = frappe._dict() row = frappe._dict()
row.party = party row.party = party

View File

@ -2,7 +2,7 @@
// License: GNU General Public License v3. See license.txt // License: GNU General Public License v3. See license.txt
frappe.require("assets/erpnext/js/financial_statements.js", function() { frappe.require("assets/erpnext/js/financial_statements.js", function() {
frappe.query_reports["Balance Sheet"] = erpnext.financial_statements; frappe.query_reports["Balance Sheet"] = $.extend({}, erpnext.financial_statements);
frappe.query_reports["Balance Sheet"]["filters"].push({ frappe.query_reports["Balance Sheet"]["filters"].push({
"fieldname": "accumulated_values", "fieldname": "accumulated_values",

View File

@ -51,27 +51,25 @@ class CropCycle(Document):
self.create_task(disease_doc.treatment_task, self.name, start_date) self.create_task(disease_doc.treatment_task, self.name, start_date)
def create_project(self, period, crop_tasks): def create_project(self, period, crop_tasks):
project = frappe.new_doc("Project") project = frappe.get_doc({
project.update({ "doctype": "Project",
"project_name": self.title, "project_name": self.title,
"expected_start_date": self.start_date, "expected_start_date": self.start_date,
"expected_end_date": add_days(self.start_date, period - 1) "expected_end_date": add_days(self.start_date, period - 1)
}) }).insert()
project.insert()
return project.name return project.name
def create_task(self, crop_tasks, project_name, start_date): def create_task(self, crop_tasks, project_name, start_date):
for crop_task in crop_tasks: for crop_task in crop_tasks:
task = frappe.new_doc("Task") frappe.get_doc({
task.update({ "doctype": "Task",
"subject": crop_task.get("task_name"), "subject": crop_task.get("task_name"),
"priority": crop_task.get("priority"), "priority": crop_task.get("priority"),
"project": project_name, "project": project_name,
"exp_start_date": add_days(start_date, crop_task.get("start_day") - 1), "exp_start_date": add_days(start_date, crop_task.get("start_day") - 1),
"exp_end_date": add_days(start_date, crop_task.get("end_day") - 1) "exp_end_date": add_days(start_date, crop_task.get("end_day") - 1)
}) }).insert()
task.insert()
def reload_linked_analysis(self): def reload_linked_analysis(self):
linked_doctypes = ['Soil Texture', 'Soil Analysis', 'Plant Analysis'] linked_doctypes = ['Soil Texture', 'Soil Analysis', 'Plant Analysis']

View File

@ -41,6 +41,39 @@ frappe.ui.form.on('Asset', {
}); });
}, },
setup: function(frm) {
frm.make_methods = {
'Asset Movement': () => {
frappe.call({
method: "erpnext.assets.doctype.asset.asset.make_asset_movement",
freeze: true,
args:{
"assets": [{ name: cur_frm.doc.name }]
},
callback: function (r) {
if (r.message) {
var doc = frappe.model.sync(r.message)[0];
frappe.set_route("Form", doc.doctype, doc.name);
}
}
});
},
}
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) { refresh: function(frm) {
frappe.ui.form.trigger("Asset", "is_existing_asset"); frappe.ui.form.trigger("Asset", "is_existing_asset");
frm.toggle_display("next_depreciation_date", frm.doc.docstatus < 1); frm.toggle_display("next_depreciation_date", frm.doc.docstatus < 1);
@ -78,11 +111,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) { if (frm.doc.maintenance_required && !frm.doc.maintenance_schedule) {
frm.add_custom_button(__("Asset Maintenance"), function() { frm.add_custom_button(__("Asset Maintenance"), function() {
frm.trigger("create_asset_maintenance"); frm.trigger("create_asset_maintenance");
@ -104,11 +132,36 @@ frappe.ui.form.on('Asset', {
frm.trigger("setup_chart"); frm.trigger("setup_chart");
} }
frm.trigger("toggle_reference_doc");
if (frm.doc.docstatus == 0) { if (frm.doc.docstatus == 0) {
frm.toggle_reqd("finance_books", frm.doc.calculate_depreciation); 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) { make_journal_entry: function(frm) {
frappe.call({ frappe.call({
method: "erpnext.assets.doctype.asset.asset.make_journal_entry", method: "erpnext.assets.doctype.asset.asset.make_journal_entry",
@ -176,6 +229,11 @@ frappe.ui.form.on('Asset', {
item_code: function(frm) { item_code: function(frm) {
if(frm.doc.item_code) { if(frm.doc.item_code) {
frm.trigger('set_finance_book');
}
},
set_finance_book: function(frm) {
frappe.call({ frappe.call({
method: "erpnext.assets.doctype.asset.asset.get_item_details", method: "erpnext.assets.doctype.asset.asset.get_item_details",
args: { args: {
@ -188,7 +246,6 @@ frappe.ui.form.on('Asset', {
} }
} }
}) })
}
}, },
available_for_use_date: function(frm) { available_for_use_date: function(frm) {
@ -207,29 +264,14 @@ frappe.ui.form.on('Asset', {
}, },
make_schedules_editable: function(frm) { make_schedules_editable: function(frm) {
if (frm.doc.finance_books) {
var is_editable = frm.doc.finance_books.filter(d => d.depreciation_method == "Manual").length > 0 var is_editable = frm.doc.finance_books.filter(d => d.depreciation_method == "Manual").length > 0
? true : false; ? true : false;
frm.toggle_enable("schedules", is_editable); frm.toggle_enable("schedules", is_editable);
frm.fields_dict["schedules"].grid.toggle_enable("schedule_date", is_editable); frm.fields_dict["schedules"].grid.toggle_enable("schedule_date", is_editable);
frm.fields_dict["schedules"].grid.toggle_enable("depreciation_amount", 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);
} }
})
}, },
make_sales_invoice: function(frm) { make_sales_invoice: function(frm) {
@ -291,6 +333,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) { set_depreciation_rate: function(frm, row) {
if (row.total_number_of_depreciations && row.frequency_of_depreciation if (row.total_number_of_depreciations && row.frequency_of_depreciation
&& row.expected_value_after_useful_life) { && row.expected_value_after_useful_life) {
@ -404,92 +505,19 @@ erpnext.asset.restore_asset = function(frm) {
}) })
}; };
erpnext.asset.transfer_asset = function(frm) { erpnext.asset.transfer_asset = function() {
var dialog = new frappe.ui.Dialog({ frappe.call({
title: __("Transfer Asset"), method: "erpnext.assets.doctype.asset.asset.make_asset_movement",
fields: [
{
"label": __("Target Location"),
"fieldname": "target_location",
"fieldtype": "Link",
"options": "Location",
"get_query": function () {
return {
filters: [
["Location", "is_group", "=", 0]
]
}
},
"reqd": 1
},
{
"label": __("Select Serial No"),
"fieldname": "serial_nos",
"fieldtype": "Link",
"options": "Serial No",
"get_query": function () {
return {
filters: {
'asset': frm.doc.name
}
}
},
"onchange": function() {
let val = this.get_value();
if (val) {
let serial_nos = dialog.get_value("serial_no") || val;
if (serial_nos) {
serial_nos = serial_nos.split('\n');
serial_nos.push(val);
const unique_sn = serial_nos.filter(function(elem, index, self) {
return index === self.indexOf(elem);
});
dialog.set_value("serial_no", unique_sn.join('\n'));
dialog.set_value("serial_nos", "");
}
}
}
},
{
"label": __("Serial No"),
"fieldname": "serial_no",
"read_only": 1,
"fieldtype": "Small Text"
},
{
"label": __("Date"),
"fieldname": "transfer_date",
"fieldtype": "Datetime",
"reqd": 1,
"default": frappe.datetime.now_datetime()
}
]
});
dialog.set_primary_action(__("Transfer"), function() {
var args = dialog.get_values();
if(!args) return;
dialog.hide();
return frappe.call({
type: "GET",
method: "erpnext.assets.doctype.asset.asset.transfer_asset",
args: {
args: {
"asset": frm.doc.name,
"transaction_date": args.transfer_date,
"source_location": frm.doc.location,
"target_location": args.target_location,
"serial_no": args.serial_no,
"company": frm.doc.company
}
},
freeze: true, freeze: true,
callback: function(r) { args:{
cur_frm.reload_doc(); "assets": [{ name: cur_frm.doc.name }],
"purpose": "Transfer"
},
callback: function (r) {
if (r.message) {
var doc = frappe.model.sync(r.message)[0];
frappe.set_route("Form", doc.doctype, doc.name);
}
} }
})
}); });
dialog.show();
}; };

View File

@ -17,6 +17,7 @@
"supplier", "supplier",
"customer", "customer",
"image", "image",
"purchase_invoice",
"column_break_3", "column_break_3",
"company", "company",
"location", "location",
@ -25,6 +26,7 @@
"purchase_date", "purchase_date",
"disposal_date", "disposal_date",
"journal_entry_for_scrap", "journal_entry_for_scrap",
"purchase_receipt",
"accounting_dimensions_section", "accounting_dimensions_section",
"cost_center", "cost_center",
"dimension_col_break", "dimension_col_break",
@ -33,6 +35,7 @@
"available_for_use_date", "available_for_use_date",
"column_break_18", "column_break_18",
"calculate_depreciation", "calculate_depreciation",
"allow_monthly_depreciation",
"is_existing_asset", "is_existing_asset",
"opening_accumulated_depreciation", "opening_accumulated_depreciation",
"number_of_depreciations_booked", "number_of_depreciations_booked",
@ -61,9 +64,8 @@
"status", "status",
"booked_fixed_asset", "booked_fixed_asset",
"column_break_51", "column_break_51",
"purchase_receipt",
"purchase_receipt_amount", "purchase_receipt_amount",
"purchase_invoice",
"default_finance_book", "default_finance_book",
"amended_from" "amended_from"
], ],
@ -216,8 +218,7 @@
{ {
"fieldname": "available_for_use_date", "fieldname": "available_for_use_date",
"fieldtype": "Date", "fieldtype": "Date",
"label": "Available-for-use Date", "label": "Available-for-use Date"
"reqd": 1
}, },
{ {
"fieldname": "column_break_18", "fieldname": "column_break_18",
@ -403,8 +404,7 @@
"label": "Purchase Receipt", "label": "Purchase Receipt",
"no_copy": 1, "no_copy": 1,
"options": "Purchase Receipt", "options": "Purchase Receipt",
"print_hide": 1, "print_hide": 1
"read_only": 1
}, },
{ {
"fieldname": "purchase_receipt_amount", "fieldname": "purchase_receipt_amount",
@ -420,8 +420,7 @@
"fieldtype": "Link", "fieldtype": "Link",
"label": "Purchase Invoice", "label": "Purchase Invoice",
"no_copy": 1, "no_copy": 1,
"options": "Purchase Invoice", "options": "Purchase Invoice"
"read_only": 1
}, },
{ {
"fetch_from": "company.default_finance_book", "fetch_from": "company.default_finance_book",
@ -450,12 +449,19 @@
{ {
"fieldname": "dimension_col_break", "fieldname": "dimension_col_break",
"fieldtype": "Column Break" "fieldtype": "Column Break"
},
{
"default": "0",
"depends_on": "calculate_depreciation",
"fieldname": "allow_monthly_depreciation",
"fieldtype": "Check",
"label": "Allow Monthly Depreciation"
} }
], ],
"idx": 72, "idx": 72,
"image_field": "image", "image_field": "image",
"is_submittable": 1, "is_submittable": 1,
"modified": "2019-10-07 15:34:30.976208", "modified": "2019-10-22 15:47:36.050828",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Assets", "module": "Assets",
"name": "Asset", "name": "Asset",

View File

@ -6,7 +6,7 @@ from __future__ import unicode_literals
import frappe, erpnext, math, json import frappe, erpnext, math, json
from frappe import _ from frappe import _
from six import string_types from six import string_types
from frappe.utils import flt, add_months, cint, nowdate, getdate, today, date_diff, add_days from frappe.utils import flt, add_months, cint, nowdate, getdate, today, date_diff, month_diff, add_days
from frappe.model.document import Document from frappe.model.document import Document
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
from erpnext.assets.doctype.asset.depreciation \ from erpnext.assets.doctype.asset.depreciation \
@ -18,6 +18,7 @@ from erpnext.controllers.accounts_controller import AccountsController
class Asset(AccountsController): class Asset(AccountsController):
def validate(self): def validate(self):
self.validate_asset_values() self.validate_asset_values()
self.validate_asset_and_reference()
self.validate_item() self.validate_item()
self.set_missing_values() self.set_missing_values()
self.prepare_depreciation_data() self.prepare_depreciation_data()
@ -29,11 +30,13 @@ class Asset(AccountsController):
def on_submit(self): def on_submit(self):
self.validate_in_use_date() self.validate_in_use_date()
self.set_status() self.set_status()
self.update_stock_movement() self.make_asset_movement()
if not self.booked_fixed_asset and is_cwip_accounting_enabled(self.company, if not self.booked_fixed_asset and is_cwip_accounting_enabled(self.asset_category):
self.asset_category):
self.make_gl_entries() self.make_gl_entries()
def before_cancel(self):
self.cancel_auto_gen_movement()
def on_cancel(self): def on_cancel(self):
self.validate_cancellation() self.validate_cancellation()
self.delete_depreciation_entries() self.delete_depreciation_entries()
@ -41,6 +44,18 @@ class Asset(AccountsController):
delete_gl_entries(voucher_type='Asset', voucher_no=self.name) delete_gl_entries(voucher_type='Asset', voucher_no=self.name)
self.db_set('booked_fixed_asset', 0) 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): def prepare_depreciation_data(self):
if self.calculate_depreciation: if self.calculate_depreciation:
self.value_after_depreciation = 0 self.value_after_depreciation = 0
@ -83,7 +98,7 @@ class Asset(AccountsController):
if not flt(self.gross_purchase_amount): if not flt(self.gross_purchase_amount):
frappe.throw(_("Gross Purchase Amount is mandatory"), frappe.MandatoryError) frappe.throw(_("Gross Purchase Amount is mandatory"), frappe.MandatoryError)
if is_cwip_accounting_enabled(self.company, self.asset_category): if is_cwip_accounting_enabled(self.asset_category):
if not self.is_existing_asset and not (self.purchase_receipt or self.purchase_invoice): if not self.is_existing_asset and not (self.purchase_receipt or self.purchase_invoice):
frappe.throw(_("Please create purchase receipt or purchase invoice for the item {0}"). frappe.throw(_("Please create purchase receipt or purchase invoice for the item {0}").
format(self.item_code)) format(self.item_code))
@ -109,6 +124,36 @@ class Asset(AccountsController):
if self.available_for_use_date and getdate(self.available_for_use_date) < getdate(self.purchase_date): 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")) 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): def set_depreciation_rate(self):
for d in self.get("finance_books"): for d in self.get("finance_books"):
d.rate_of_depreciation = flt(self.get_depreciation_rate(d, on_validate=True), d.rate_of_depreciation = flt(self.get_depreciation_rate(d, on_validate=True),
@ -149,19 +194,31 @@ class Asset(AccountsController):
schedule_date = add_months(d.depreciation_start_date, schedule_date = add_months(d.depreciation_start_date,
n * cint(d.frequency_of_depreciation)) n * cint(d.frequency_of_depreciation))
# schedule date will be a year later from start date
# so monthly schedule date is calculated by removing 11 months from it
monthly_schedule_date = add_months(schedule_date, - d.frequency_of_depreciation + 1)
# For first row # For first row
if has_pro_rata and n==0: if has_pro_rata and n==0:
depreciation_amount, days = get_pro_rata_amt(d, depreciation_amount, depreciation_amount, days, months = get_pro_rata_amt(d, depreciation_amount,
self.available_for_use_date, d.depreciation_start_date) self.available_for_use_date, d.depreciation_start_date)
# For first depr schedule date will be the start date
# so monthly schedule date is calculated by removing month difference between use date and start date
monthly_schedule_date = add_months(d.depreciation_start_date, - months + 1)
# For last row # For last row
elif has_pro_rata and n == cint(number_of_pending_depreciations) - 1: elif has_pro_rata and n == cint(number_of_pending_depreciations) - 1:
to_date = add_months(self.available_for_use_date, to_date = add_months(self.available_for_use_date,
n * cint(d.frequency_of_depreciation)) n * cint(d.frequency_of_depreciation))
depreciation_amount, days = get_pro_rata_amt(d, depreciation_amount, days, months = get_pro_rata_amt(d,
depreciation_amount, schedule_date, to_date) depreciation_amount, schedule_date, to_date)
monthly_schedule_date = add_months(schedule_date, 1)
schedule_date = add_days(schedule_date, days) schedule_date = add_days(schedule_date, days)
last_schedule_date = schedule_date
if not depreciation_amount: continue if not depreciation_amount: continue
value_after_depreciation -= flt(depreciation_amount, value_after_depreciation -= flt(depreciation_amount,
@ -175,6 +232,43 @@ class Asset(AccountsController):
skip_row = True skip_row = True
if depreciation_amount > 0: if depreciation_amount > 0:
# With monthly depreciation, each depreciation is divided by months remaining until next date
if self.allow_monthly_depreciation:
# month range is 1 to 12
# In pro rata case, for first and last depreciation, month range would be different
month_range = months \
if (has_pro_rata and n==0) or (has_pro_rata and n == cint(number_of_pending_depreciations) - 1) \
else d.frequency_of_depreciation
for r in range(month_range):
if (has_pro_rata and n == 0):
# For first entry of monthly depr
if r == 0:
days_until_first_depr = date_diff(monthly_schedule_date, self.available_for_use_date)
per_day_amt = depreciation_amount / days
depreciation_amount_for_current_month = per_day_amt * days_until_first_depr
depreciation_amount -= depreciation_amount_for_current_month
date = monthly_schedule_date
amount = depreciation_amount_for_current_month
else:
date = add_months(monthly_schedule_date, r)
amount = depreciation_amount / (month_range - 1)
elif (has_pro_rata and n == cint(number_of_pending_depreciations) - 1) and r == cint(month_range) - 1:
# For last entry of monthly depr
date = last_schedule_date
amount = depreciation_amount / month_range
else:
date = add_months(monthly_schedule_date, r)
amount = depreciation_amount / month_range
self.append("schedules", {
"schedule_date": date,
"depreciation_amount": amount,
"depreciation_method": d.depreciation_method,
"finance_book": d.finance_book,
"finance_book_id": d.idx
})
else:
self.append("schedules", { self.append("schedules", {
"schedule_date": schedule_date, "schedule_date": schedule_date,
"depreciation_amount": depreciation_amount, "depreciation_amount": depreciation_amount,
@ -200,7 +294,9 @@ class Asset(AccountsController):
.format(row.idx)) .format(row.idx))
if not row.depreciation_start_date: if not row.depreciation_start_date:
if not self.available_for_use_date:
frappe.throw(_("Row {0}: Depreciation Start Date is required").format(row.idx)) frappe.throw(_("Row {0}: Depreciation Start Date is required").format(row.idx))
row.depreciation_start_date = self.available_for_use_date
if not self.is_existing_asset: if not self.is_existing_asset:
self.opening_accumulated_depreciation = 0 self.opening_accumulated_depreciation = 0
@ -349,22 +445,13 @@ class Asset(AccountsController):
if d.finance_book == self.default_finance_book: if d.finance_book == self.default_finance_book:
return cint(d.idx) - 1 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): def make_gl_entries(self):
gl_entries = [] gl_entries = []
if ((self.purchase_receipt or (self.purchase_invoice and if ((self.purchase_receipt \
frappe.db.get_value('Purchase Invoice', self.purchase_invoice, 'update_stock'))) 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()): 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) asset_category = self.asset_category, company = self.company)
cwip_account = get_asset_account("capital_work_in_progress_account", cwip_account = get_asset_account("capital_work_in_progress_account",
@ -372,7 +459,7 @@ class Asset(AccountsController):
gl_entries.append(self.get_gl_dict({ gl_entries.append(self.get_gl_dict({
"account": cwip_account, "account": cwip_account,
"against": fixed_aseet_account, "against": fixed_asset_account,
"remarks": self.get("remarks") or _("Accounting Entry for Asset"), "remarks": self.get("remarks") or _("Accounting Entry for Asset"),
"posting_date": self.available_for_use_date, "posting_date": self.available_for_use_date,
"credit": self.purchase_receipt_amount, "credit": self.purchase_receipt_amount,
@ -381,7 +468,7 @@ class Asset(AccountsController):
})) }))
gl_entries.append(self.get_gl_dict({ gl_entries.append(self.get_gl_dict({
"account": fixed_aseet_account, "account": fixed_asset_account,
"against": cwip_account, "against": cwip_account,
"remarks": self.get("remarks") or _("Accounting Entry for Asset"), "remarks": self.get("remarks") or _("Accounting Entry for Asset"),
"posting_date": self.available_for_use_date, "posting_date": self.available_for_use_date,
@ -428,7 +515,7 @@ def update_maintenance_status():
asset.set_status('Out of Order') asset.set_status('Out of Order')
def make_post_gl_entry(): def make_post_gl_entry():
if not is_cwip_accounting_enabled(self.company, self.asset_category): if not is_cwip_accounting_enabled(self.asset_category):
return return
assets = frappe.db.sql_list(""" select name from `tabAsset` assets = frappe.db.sql_list(""" select name from `tabAsset`
@ -442,25 +529,6 @@ def get_asset_naming_series():
meta = frappe.get_meta('Asset') meta = frappe.get_meta('Asset')
return meta.get_field("naming_series").options 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() @frappe.whitelist()
def make_sales_invoice(asset, item_code, company, serial_no=None): def make_sales_invoice(asset, item_code, company, serial_no=None):
si = frappe.new_doc("Sales Invoice") si = frappe.new_doc("Sales Invoice")
@ -535,7 +603,7 @@ def get_item_details(item_code, asset_category):
def get_asset_account(account_name, asset=None, asset_category=None, company=None): def get_asset_account(account_name, asset=None, asset_category=None, company=None):
account = None account = None
if asset: 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) asset_category = asset_category, company = company)
if not account: if not account:
@ -578,19 +646,53 @@ def make_journal_entry(asset_name):
return je return je
def is_cwip_accounting_enabled(company, asset_category=None): @frappe.whitelist()
enable_cwip_in_company = cint(frappe.db.get_value("Company", company, "enable_cwip_accounting")) def make_asset_movement(assets, purpose=None):
import json
from six import string_types
if enable_cwip_in_company or not asset_category: if isinstance(assets, string_types):
return enable_cwip_in_company 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.purpose = purpose
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(asset_category):
return cint(frappe.db.get_value("Asset Category", asset_category, "enable_cwip_accounting")) return cint(frappe.db.get_value("Asset Category", asset_category, "enable_cwip_accounting"))
def get_pro_rata_amt(row, depreciation_amount, from_date, to_date): def get_pro_rata_amt(row, depreciation_amount, from_date, to_date):
days = date_diff(to_date, from_date) days = date_diff(to_date, from_date)
months = month_diff(to_date, from_date)
total_days = get_total_days(to_date, row.frequency_of_depreciation) total_days = get_total_days(to_date, row.frequency_of_depreciation)
return (depreciation_amount * flt(days)) / flt(total_days), days return (depreciation_amount * flt(days)) / flt(total_days), days, months
def get_total_days(date, frequency): def get_total_days(date, frequency):
period_start_date = add_months(date, period_start_date = add_months(date,

View File

@ -30,8 +30,24 @@ frappe.listview_settings['Asset'] = {
} else if (doc.status === "Draft") { } else if (doc.status === "Draft") {
return [__("Draft"), "red", "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",
freeze: true,
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);
}
}
});
});
}, },
} }

View File

@ -7,7 +7,7 @@ import frappe
import unittest import unittest
from frappe.utils import cstr, nowdate, getdate, flt, get_last_day, add_days, add_months 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.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.test_purchase_receipt import make_purchase_receipt
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice as make_invoice 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() asset.submit()
pi = make_purchase_invoice(asset.name, asset.item_code, asset.gross_purchase_amount, pi = make_invoice(pr.name)
asset.company, asset.purchase_date)
pi.supplier = "_Test Supplier" pi.supplier = "_Test Supplier"
pi.insert() pi.insert()
pi.submit() pi.submit()
asset.load_from_db() asset.load_from_db()
self.assertEqual(asset.supplier, "_Test Supplier") self.assertEqual(asset.supplier, "_Test Supplier")
self.assertEqual(asset.purchase_date, getdate(purchase_date)) 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 = ( expected_gle = (
("Asset Received But Not Billed - _TC", 100000.0, 0.0), ("Asset Received But Not Billed - _TC", 100000.0, 0.0),
@ -60,10 +60,11 @@ class TestAsset(unittest.TestCase):
self.assertEqual(gle, expected_gle) self.assertEqual(gle, expected_gle)
pi.cancel() pi.cancel()
asset.cancel()
asset.load_from_db() asset.load_from_db()
self.assertEqual(asset.supplier, None) pr.load_from_db()
self.assertEqual(asset.purchase_invoice, None) pr.cancel()
self.assertEqual(asset.docstatus, 2)
self.assertFalse(frappe.db.get_value("GL Entry", self.assertFalse(frappe.db.get_value("GL Entry",
{"voucher_type": "Purchase Invoice", "voucher_no": pi.name})) {"voucher_type": "Purchase Invoice", "voucher_no": pi.name}))
@ -482,11 +483,6 @@ class TestAsset(unittest.TestCase):
self.assertTrue(asset.finance_books[0].expected_value_after_useful_life >= asset_value_after_full_schedule) self.assertTrue(asset.finance_books[0].expected_value_after_useful_life >= asset_value_after_full_schedule)
def test_cwip_accounting(self): 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", pr = make_purchase_receipt(item_code="Macbook Pro",
qty=1, rate=5000, do_not_submit=True, location="Test Location") qty=1, rate=5000, do_not_submit=True, location="Test Location")
@ -515,13 +511,13 @@ class TestAsset(unittest.TestCase):
("CWIP Account - _TC", 5250.0, 0.0) ("CWIP Account - _TC", 5250.0, 0.0)
) )
gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry` pr_gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry`
where voucher_type='Purchase Receipt' and voucher_no = %s where voucher_type='Purchase Receipt' and voucher_no = %s
order by account""", pr.name) order by account""", pr.name)
self.assertEqual(gle, expected_gle) self.assertEqual(pr_gle, expected_gle)
pi = make_purchase_invoice_from_pr(pr.name) pi = make_invoice(pr.name)
pi.submit() pi.submit()
expected_gle = ( expected_gle = (
@ -532,11 +528,11 @@ class TestAsset(unittest.TestCase):
("Expenses Included In Asset Valuation - _TC", 0.0, 250.0), ("Expenses Included In Asset Valuation - _TC", 0.0, 250.0),
) )
gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry` pi_gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry`
where voucher_type='Purchase Invoice' and voucher_no = %s where voucher_type='Purchase Invoice' and voucher_no = %s
order by account""", pi.name) order by account""", pi.name)
self.assertEqual(gle, expected_gle) self.assertEqual(pi_gle, expected_gle)
asset = frappe.db.get_value('Asset', asset = frappe.db.get_value('Asset',
{'purchase_receipt': pr.name, 'docstatus': 0}, 'name') {'purchase_receipt': pr.name, 'docstatus': 0}, 'name')
@ -636,6 +632,8 @@ def create_asset_category():
asset_category.insert() asset_category.insert()
def create_fixed_asset_item(): 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: try:
frappe.get_doc({ frappe.get_doc({
"doctype": "Item", "doctype": "Item",
@ -646,7 +644,9 @@ def create_fixed_asset_item():
"item_group": "All Item Groups", "item_group": "All Item Groups",
"stock_uom": "Nos", "stock_uom": "Nos",
"is_stock_item": 0, "is_stock_item": 0,
"is_fixed_asset": 1 "is_fixed_asset": 1,
"auto_create_assets": 1,
"asset_naming_series": naming_series
}).insert() }).insert()
except frappe.DuplicateEntryError: except frappe.DuplicateEntryError:
pass pass

View File

@ -11,7 +11,6 @@ from frappe.model.document import Document
class AssetCategory(Document): class AssetCategory(Document):
def validate(self): def validate(self):
self.validate_finance_books() self.validate_finance_books()
self.validate_enable_cwip_accounting()
def validate_finance_books(self): def validate_finance_books(self):
for d in self.finance_books: for d in self.finance_books:
@ -19,18 +18,12 @@ class AssetCategory(Document):
if cint(d.get(frappe.scrub(field)))<1: if cint(d.get(frappe.scrub(field)))<1:
frappe.throw(_("Row {0}: {1} must be greater than 0").format(d.idx, field), frappe.MandatoryError) frappe.throw(_("Row {0}: {1} must be greater than 0").format(d.idx, field), frappe.MandatoryError)
def validate_enable_cwip_accounting(self):
if self.enable_cwip_accounting :
for d in self.accounts:
cwip = frappe.db.get_value("Company",d.company_name,"enable_cwip_accounting")
if cwip:
frappe.throw(_
("CWIP is enabled globally in Company {1}. To enable it in Asset Category, first disable it in {1} ").format(
frappe.bold(d.idx), frappe.bold(d.company_name)))
@frappe.whitelist() @frappe.whitelist()
def get_asset_category_account(asset, fieldname, account=None, asset_category = None, company = None): def get_asset_category_account(fieldname, item=None, asset=None, account=None, asset_category = None, company = None):
if not asset_category and company: 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 account:
if frappe.db.get_value("Account", account, "account_type") != "Fixed Asset": if frappe.db.get_value("Account", account, "account_type") != "Fixed Asset":
account=None account=None

View File

@ -75,6 +75,8 @@ def create_asset_data():
}).insert() }).insert()
if not frappe.db.exists("Item", "Photocopier"): if not frappe.db.exists("Item", "Photocopier"):
meta = frappe.get_meta('Asset')
naming_series = meta.get_field("naming_series").options
frappe.get_doc({ frappe.get_doc({
"doctype": "Item", "doctype": "Item",
"item_code": "Photocopier", "item_code": "Photocopier",
@ -83,7 +85,9 @@ def create_asset_data():
"company": "_Test Company", "company": "_Test Company",
"is_fixed_asset": 1, "is_fixed_asset": 1,
"is_stock_item": 0, "is_stock_item": 0,
"asset_category": "Equipment" "asset_category": "Equipment",
"auto_create_assets": 1,
"asset_naming_series": naming_series
}).insert() }).insert()
def create_maintenance_team(): def create_maintenance_team():

View File

@ -2,27 +2,138 @@
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on('Asset Movement', { frappe.ui.form.on('Asset Movement', {
select_serial_no: function(frm) { setup: (frm) => {
if (frm.doc.select_serial_no) { frm.set_query("to_employee", "assets", (doc) => {
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() {
return { return {
filters: { 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); // eslint-disable-line
});
}
} }
}); });

View File

@ -1,27 +1,20 @@
{ {
"allow_import": 1, "allow_import": 1,
"autoname": "naming_series:", "autoname": "format:ACC-ASM-{YYYY}-{#####}",
"creation": "2016-04-25 18:00:23.559973", "creation": "2016-04-25 18:00:23.559973",
"doctype": "DocType", "doctype": "DocType",
"engine": "InnoDB",
"field_order": [ "field_order": [
"naming_series",
"company", "company",
"purpose", "purpose",
"asset",
"transaction_date",
"column_break_4", "column_break_4",
"quantity", "transaction_date",
"select_serial_no",
"serial_no",
"section_break_7",
"source_location",
"target_location",
"column_break_10",
"from_employee",
"to_employee",
"reference", "reference",
"reference_doctype", "reference_doctype",
"column_break_9",
"reference_name", "reference_name",
"section_break_10",
"assets",
"amended_from" "amended_from"
], ],
"fields": [ "fields": [
@ -36,23 +29,12 @@
"reqd": 1 "reqd": 1
}, },
{ {
"default": "Transfer",
"fieldname": "purpose", "fieldname": "purpose",
"fieldtype": "Select", "fieldtype": "Select",
"label": "Purpose", "label": "Purpose",
"options": "\nIssue\nReceipt\nTransfer", "options": "\nIssue\nReceipt\nTransfer",
"reqd": 1 "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", "fieldname": "transaction_date",
"fieldtype": "Datetime", "fieldtype": "Datetime",
@ -64,56 +46,6 @@
"fieldname": "column_break_4", "fieldname": "column_break_4",
"fieldtype": "Column Break" "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", "fieldname": "reference",
"fieldtype": "Section Break", "fieldtype": "Section Break",
@ -122,18 +54,18 @@
{ {
"fieldname": "reference_doctype", "fieldname": "reference_doctype",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Reference DocType", "label": "Reference Document",
"no_copy": 1, "no_copy": 1,
"options": "DocType", "options": "DocType",
"read_only": 1 "reqd": 1
}, },
{ {
"fieldname": "reference_name", "fieldname": "reference_name",
"fieldtype": "Dynamic Link", "fieldtype": "Dynamic Link",
"label": "Reference Name", "label": "Reference Document Name",
"no_copy": 1, "no_copy": 1,
"options": "reference_doctype", "options": "reference_doctype",
"read_only": 1 "reqd": 1
}, },
{ {
"fieldname": "amended_from", "fieldname": "amended_from",
@ -145,16 +77,23 @@
"read_only": 1 "read_only": 1
}, },
{ {
"default": "ACC-ASM-.YYYY.-", "fieldname": "section_break_10",
"fieldname": "naming_series", "fieldtype": "Section Break"
"fieldtype": "Select", },
"label": "Series", {
"options": "ACC-ASM-.YYYY.-", "fieldname": "assets",
"fieldtype": "Table",
"label": "Assets",
"options": "Asset Movement Item",
"reqd": 1 "reqd": 1
},
{
"fieldname": "column_break_9",
"fieldtype": "Column Break"
} }
], ],
"is_submittable": 1, "is_submittable": 1,
"modified": "2019-09-16 16:27:53.887634", "modified": "2019-11-21 14:35:51.880332",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Assets", "module": "Assets",
"name": "Asset Movement", "name": "Asset Movement",

View File

@ -5,101 +5,142 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe import _ from frappe import _
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
from frappe.model.document import Document from frappe.model.document import Document
class AssetMovement(Document): class AssetMovement(Document):
def validate(self): def validate(self):
self.validate_asset() self.validate_asset()
self.validate_location() self.validate_location()
self.validate_employee()
def validate_asset(self): def validate_asset(self):
status, company = frappe.db.get_value("Asset", self.asset, ["status", "company"]) 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"): if self.purpose == 'Transfer' and status in ("Draft", "Scrapped", "Sold"):
frappe.throw(_("{0} asset cannot be transferred").format(status)) frappe.throw(_("{0} asset cannot be transferred").format(status))
if company != self.company: if company != self.company:
frappe.throw(_("Asset {0} does not belong to company {1}").format(self.asset, 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: if not(d.source_location or d.target_location or d.from_employee or d.to_employee):
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")) 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))
def validate_location(self): def validate_location(self):
for d in self.assets:
if self.purpose in ['Transfer', 'Issue']: if self.purpose in ['Transfer', 'Issue']:
if not self.serial_no and not (self.from_employee or self.to_employee): if not d.source_location:
self.source_location = frappe.db.get_value("Asset", self.asset, "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): if not d.source_location:
frappe.throw(_("Source Location is required for the asset {0}").format(self.asset)) frappe.throw(_("Source Location is required for the Asset {0}").format(d.asset))
if self.serial_no and self.source_location: if d.source_location:
s_nos = get_serial_nos(self.serial_no) current_location = frappe.db.get_value("Asset", d.asset, "location")
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 serial_nos: if current_location != d.source_location:
frappe.throw(_("Serial nos {0} does not belongs to the location {1}"). frappe.throw(_("Asset {0} does not belongs to the location {1}").
format(','.join(serial_nos), self.source_location)) format(d.asset, d.source_location))
if self.source_location and self.source_location == self.target_location and self.purpose == 'Transfer': 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")) frappe.throw(_("Source and Target Location cannot be same"))
if self.purpose == 'Receipt' and not (self.target_location or self.to_employee): if self.purpose == 'Receipt':
frappe.throw(_("Target Location is required for the asset {0}").format(self.asset)) # 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))
def validate_employee(self):
for d in self.assets:
if d.from_employee:
current_custodian = frappe.db.get_value("Asset", d.asset, "custodian")
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): def on_submit(self):
self.set_latest_location_in_asset() self.set_latest_location_in_asset()
def before_cancel(self):
self.validate_last_movement()
def on_cancel(self): def on_cancel(self):
self.set_latest_location_in_asset() 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): def set_latest_location_in_asset(self):
location, employee = '', '' current_location, current_employee = '', ''
cond = "1=1" cond = "1=1"
for d in self.assets:
args = { args = {
'asset': self.asset, 'asset': d.asset,
'company': self.company 'company': self.company
} }
if self.serial_no: # latest entry corresponds to current document's location, employee when transaction date > previous dates
cond = "serial_no like %(txt)s" # In case of cancellation it corresponds to previous latest document's location, employee
args.update({ latest_movement_entry = frappe.db.sql(
'txt': "%%%s%%" % self.serial_no """
}) SELECT asm_item.target_location, asm_item.to_employee
FROM `tabAsset Movement Item` asm_item, `tabAsset Movement` asm
latest_movement_entry = frappe.db.sql("""select target_location, to_employee from `tabAsset Movement` WHERE
where asset=%(asset)s and docstatus=1 and company=%(company)s and {0} asm_item.parent=asm.name and
order by transaction_date desc limit 1""".format(cond), args) 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: if latest_movement_entry:
location = latest_movement_entry[0][0] current_location = latest_movement_entry[0][0]
employee = latest_movement_entry[0][1] current_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', d.asset, 'location', current_location)
frappe.db.set_value("Asset", self.asset, "location", location) frappe.db.set_value('Asset', d.asset, 'custodian', current_employee)
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)

View File

@ -5,6 +5,7 @@ from __future__ import unicode_literals
import frappe import frappe
import unittest import unittest
import erpnext
from erpnext.stock.doctype.item.test_item import make_item from erpnext.stock.doctype.item.test_item import make_item
from frappe.utils import now, nowdate, get_last_day, add_days from frappe.utils import now, nowdate, get_last_day, add_days
from erpnext.assets.doctype.asset.test_asset import create_asset_data from erpnext.assets.doctype.asset.test_asset import create_asset_data
@ -16,7 +17,6 @@ class TestAssetMovement(unittest.TestCase):
def setUp(self): def setUp(self):
create_asset_data() create_asset_data()
make_location() make_location()
make_serialized_item()
def test_movement(self): def test_movement(self):
pr = make_purchase_receipt(item_code="Macbook Pro", pr = make_purchase_receipt(item_code="Macbook Pro",
@ -38,68 +38,72 @@ class TestAssetMovement(unittest.TestCase):
if asset.docstatus == 0: if asset.docstatus == 0:
asset.submit() asset.submit()
# check asset movement is created
if not frappe.db.exists("Location", "Test Location 2"): if not frappe.db.exists("Location", "Test Location 2"):
frappe.get_doc({ frappe.get_doc({
'doctype': 'Location', 'doctype': 'Location',
'location_name': 'Test Location 2' 'location_name': 'Test Location 2'
}).insert() }).insert()
movement1 = create_asset_movement(asset= asset.name, purpose = 'Transfer', movement1 = create_asset_movement(purpose = 'Transfer', company = asset.company,
company=asset.company, source_location="Test Location", target_location="Test Location 2") 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") self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location 2")
movement2 = create_asset_movement(asset= asset.name, purpose = 'Transfer', movement2 = create_asset_movement(purpose = 'Transfer', company = asset.company,
company=asset.company, source_location = "Test Location 2", target_location="Test Location") 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") self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location")
movement1.cancel() movement1.cancel()
self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location") self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location")
movement2.cancel() employee = make_employee("testassetmovemp@example.com", company="_Test Company")
self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location") 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)
def test_movement_for_serialized_asset(self): # after issuing asset should belong to an employee not at a location
asset_item = "Test Serialized Asset Item" self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), None)
pr = make_purchase_receipt(item_code=asset_item, rate = 1000, qty=3, location = "Mumbai") self.assertEqual(frappe.db.get_value("Asset", asset.name, "custodian"), employee)
asset_name = frappe.db.get_value('Asset', {'purchase_receipt': pr.name}, 'name')
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) 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.calculate_depreciation = 1
asset.available_for_use_date = '2020-06-06'
asset.purchase_date = '2020-06-06'
asset.append("finance_books", { 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", "depreciation_method": "Straight Line",
"total_number_of_depreciations": 3, "total_number_of_depreciations": 3,
"frequency_of_depreciation": 10, "frequency_of_depreciation": 10,
"depreciation_start_date": month_end_date "depreciation_start_date": "2020-06-06"
}) })
if asset.docstatus == 0:
asset.submit() asset.submit()
serial_nos = frappe.db.get_value('Asset Movement', {'reference_name': pr.name}, 'serial_no')
mov1 = create_asset_movement(asset=asset_name, purpose = 'Transfer', if not frappe.db.exists("Location", "Test Location 2"):
company=asset.company, source_location = "Mumbai", target_location="Pune", serial_no=serial_nos) frappe.get_doc({
self.assertEqual(mov1.target_location, "Pune") 'doctype': 'Location',
'location_name': 'Test Location 2'
}).insert()
serial_no = frappe.db.get_value('Serial No', {'asset': asset_name}, 'name') movement = frappe.get_doc({'doctype': 'Asset Movement', 'reference_name': pr.name })
self.assertRaises(frappe.ValidationError, movement.cancel)
employee = make_employee("testassetemp@example.com") movement1 = create_asset_movement(purpose = 'Transfer', company = asset.company,
create_asset_movement(asset=asset_name, purpose = 'Transfer', assets = [{ 'asset': asset.name , 'source_location': 'Test Location', 'target_location': 'Test Location 2'}],
company=asset.company, serial_no=serial_no, to_employee=employee) reference_doctype = 'Purchase Receipt', reference_name = pr.name)
self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location 2")
self.assertEqual(frappe.db.get_value('Serial No', serial_no, 'employee'), employee) movement1.cancel()
self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location")
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")
def create_asset_movement(**args): def create_asset_movement(**args):
args = frappe._dict(args) args = frappe._dict(args)
@ -109,20 +113,12 @@ def create_asset_movement(**args):
movement = frappe.new_doc("Asset Movement") movement = frappe.new_doc("Asset Movement")
movement.update({ movement.update({
"asset": args.asset, "assets": args.assets,
"transaction_date": args.transaction_date, "transaction_date": args.transaction_date,
"target_location": args.target_location,
"company": args.company, "company": args.company,
'purpose': args.purpose or 'Receipt', 'purpose': args.purpose or 'Receipt',
'serial_no': args.serial_no, 'reference_doctype': args.reference_doctype,
'quantity': len(get_serial_nos(args.serial_no)) if args.serial_no else 1, 'reference_name': args.reference_name
'from_employee': "_T-Employee-00001" or args.from_employee,
'to_employee': args.to_employee
})
if args.source_location:
movement.update({
'source_location': args.source_location
}) })
movement.insert() movement.insert()
@ -137,33 +133,3 @@ def make_location():
'doctype': 'Location', 'doctype': 'Location',
'location_name': location 'location_name': location
}).insert(ignore_permissions = True) }).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.###'})

View File

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

View File

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

View File

@ -60,7 +60,8 @@
{ {
"fieldname": "date", "fieldname": "date",
"fieldtype": "Date", "fieldtype": "Date",
"label": "Date" "label": "Date",
"reqd": 1
}, },
{ {
"fieldname": "current_asset_value", "fieldname": "current_asset_value",
@ -110,7 +111,7 @@
} }
], ],
"is_submittable": 1, "is_submittable": 1,
"modified": "2019-05-26 09:46:23.613412", "modified": "2019-11-22 14:09:25.800375",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Assets", "module": "Assets",
"name": "Asset Value Adjustment", "name": "Asset Value Adjustment",

View File

@ -5,12 +5,13 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe import _ from frappe import _
from frappe.utils import flt, getdate, cint, date_diff from frappe.utils import flt, getdate, cint, date_diff, formatdate
from erpnext.assets.doctype.asset.depreciation import get_depreciation_accounts from erpnext.assets.doctype.asset.depreciation import get_depreciation_accounts
from frappe.model.document import Document from frappe.model.document import Document
class AssetValueAdjustment(Document): class AssetValueAdjustment(Document):
def validate(self): def validate(self):
self.validate_date()
self.set_difference_amount() self.set_difference_amount()
self.set_current_asset_value() self.set_current_asset_value()
@ -24,6 +25,12 @@ class AssetValueAdjustment(Document):
self.reschedule_depreciations(self.current_asset_value) self.reschedule_depreciations(self.current_asset_value)
def validate_date(self):
asset_purchase_date = frappe.db.get_value('Asset', self.asset, 'purchase_date')
if getdate(self.date) < getdate(asset_purchase_date):
frappe.throw(_("Asset Value Adjustment cannot be posted before Asset's purchase date <b>{0}</b>.")
.format(formatdate(asset_purchase_date)), title="Incorrect Date")
def set_difference_amount(self): def set_difference_amount(self):
self.difference_amount = flt(self.current_asset_value - self.new_asset_value) self.difference_amount = flt(self.current_asset_value - self.new_asset_value)

View File

@ -313,7 +313,7 @@ def item_last_purchase_rate(name, conversion_rate, item_code, conversion_factor=
last_purchase_details = get_last_purchase_details(item_code, name) last_purchase_details = get_last_purchase_details(item_code, name)
if last_purchase_details: if last_purchase_details:
last_purchase_rate = (last_purchase_details['base_rate'] * (flt(conversion_factor) or 1.0)) / conversion_rate last_purchase_rate = (last_purchase_details['base_net_rate'] * (flt(conversion_factor) or 1.0)) / conversion_rate
return last_purchase_rate return last_purchase_rate
else: else:
item_last_purchase_rate = frappe.get_cached_value("Item", item_code, "last_purchase_rate") item_last_purchase_rate = frappe.get_cached_value("Item", item_code, "last_purchase_rate")

View File

@ -43,6 +43,7 @@
"base_amount", "base_amount",
"pricing_rules", "pricing_rules",
"is_free_item", "is_free_item",
"is_fixed_asset",
"section_break_29", "section_break_29",
"net_rate", "net_rate",
"net_amount", "net_amount",
@ -699,11 +700,19 @@
"fieldtype": "Data", "fieldtype": "Data",
"label": "Manufacturer Part Number", "label": "Manufacturer Part Number",
"read_only": 1 "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, "idx": 1,
"istable": 1, "istable": 1,
"modified": "2019-09-17 22:32:34.703923", "modified": "2019-11-07 17:19:12.090355",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Buying", "module": "Buying",
"name": "Purchase Order Item", "name": "Purchase Order Item",

View File

@ -1,537 +1,168 @@
{ {
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2013-02-22 01:27:42", "creation": "2013-02-22 01:27:42",
"custom": 0,
"docstatus": 0,
"doctype": "DocType", "doctype": "DocType",
"editable_grid": 1, "editable_grid": 1,
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [
"main_item_code",
"rm_item_code",
"description",
"batch_no",
"serial_no",
"col_break1",
"required_qty",
"consumed_qty",
"stock_uom",
"rate",
"amount",
"conversion_factor",
"current_stock",
"reference_name",
"bom_detail_no"
],
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "main_item_code", "fieldname": "main_item_code",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0,
"label": "Item Code", "label": "Item Code",
"length": 0,
"no_copy": 0,
"oldfieldname": "main_item_code", "oldfieldname": "main_item_code",
"oldfieldtype": "Data", "oldfieldtype": "Data",
"options": "Item", "options": "Item",
"permlevel": 0, "read_only": 1
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "rm_item_code", "fieldname": "rm_item_code",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0,
"label": "Raw Material Item Code", "label": "Raw Material Item Code",
"length": 0,
"no_copy": 0,
"oldfieldname": "rm_item_code", "oldfieldname": "rm_item_code",
"oldfieldtype": "Data", "oldfieldtype": "Data",
"options": "Item", "options": "Item",
"permlevel": 0, "read_only": 1
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "description", "fieldname": "description",
"fieldtype": "Text Editor", "fieldtype": "Text Editor",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 1, "in_global_search": 1,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0,
"label": "Description", "label": "Description",
"length": 0,
"no_copy": 0,
"oldfieldname": "description", "oldfieldname": "description",
"oldfieldtype": "Data", "oldfieldtype": "Data",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": "300px", "print_width": "300px",
"read_only": 1, "read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0,
"width": "300px" "width": "300px"
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "batch_no", "fieldname": "batch_no",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Batch No", "label": "Batch No",
"length": 0,
"no_copy": 1, "no_copy": 1,
"options": "Batch", "options": "Batch"
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "serial_no", "fieldname": "serial_no",
"fieldtype": "Text", "fieldtype": "Text",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Serial No", "label": "Serial No",
"length": 0, "no_copy": 1
"no_copy": 1,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "col_break1", "fieldname": "col_break1",
"fieldtype": "Column Break", "fieldtype": "Column Break"
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "required_qty", "fieldname": "required_qty",
"fieldtype": "Float", "fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0,
"label": "Required Qty", "label": "Required Qty",
"length": 0,
"no_copy": 0,
"oldfieldname": "required_qty", "oldfieldname": "required_qty",
"oldfieldtype": "Currency", "oldfieldtype": "Currency",
"permlevel": 0, "read_only": 1
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "consumed_qty", "fieldname": "consumed_qty",
"fieldtype": "Float", "fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Consumed Qty", "label": "Consumed Qty",
"length": 0,
"no_copy": 0,
"oldfieldname": "consumed_qty", "oldfieldname": "consumed_qty",
"oldfieldtype": "Currency", "oldfieldtype": "Currency",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1, "read_only": 1,
"remember_last_selected_value": 0, "reqd": 1
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "stock_uom", "fieldname": "stock_uom",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Stock Uom", "label": "Stock Uom",
"length": 0,
"no_copy": 0,
"oldfieldname": "stock_uom", "oldfieldname": "stock_uom",
"oldfieldtype": "Data", "oldfieldtype": "Data",
"options": "UOM", "options": "UOM",
"permlevel": 0, "read_only": 1
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "rate", "fieldname": "rate",
"fieldtype": "Currency", "fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Rate", "label": "Rate",
"length": 0,
"no_copy": 0,
"oldfieldname": "rate", "oldfieldname": "rate",
"oldfieldtype": "Currency", "oldfieldtype": "Currency",
"options": "Company:company:default_currency", "options": "Company:company:default_currency",
"permlevel": 0, "read_only": 1
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "amount", "fieldname": "amount",
"fieldtype": "Currency", "fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Amount", "label": "Amount",
"length": 0,
"no_copy": 0,
"oldfieldname": "amount", "oldfieldname": "amount",
"oldfieldtype": "Currency", "oldfieldtype": "Currency",
"options": "Company:company:default_currency", "options": "Company:company:default_currency",
"permlevel": 0, "read_only": 1
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "conversion_factor", "fieldname": "conversion_factor",
"fieldtype": "Float", "fieldtype": "Float",
"hidden": 1, "hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Conversion Factor", "label": "Conversion Factor",
"length": 0,
"no_copy": 0,
"oldfieldname": "conversion_factor", "oldfieldname": "conversion_factor",
"oldfieldtype": "Currency", "oldfieldtype": "Currency",
"permlevel": 0, "read_only": 1
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "current_stock", "fieldname": "current_stock",
"fieldtype": "Float", "fieldtype": "Float",
"hidden": 1, "in_list_view": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Current Stock", "label": "Current Stock",
"length": 0,
"no_copy": 0,
"oldfieldname": "current_stock", "oldfieldname": "current_stock",
"oldfieldtype": "Currency", "oldfieldtype": "Currency",
"permlevel": 0, "read_only": 1
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "reference_name", "fieldname": "reference_name",
"fieldtype": "Data", "fieldtype": "Data",
"hidden": 1, "hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0,
"label": "Reference Name", "label": "Reference Name",
"length": 0,
"no_copy": 0,
"oldfieldname": "reference_name", "oldfieldname": "reference_name",
"oldfieldtype": "Data", "oldfieldtype": "Data",
"permlevel": 0, "read_only": 1
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "bom_detail_no", "fieldname": "bom_detail_no",
"fieldtype": "Data", "fieldtype": "Data",
"hidden": 1, "hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0,
"label": "BOM Detail No", "label": "BOM Detail No",
"length": 0,
"no_copy": 0,
"oldfieldname": "bom_detail_no", "oldfieldname": "bom_detail_no",
"oldfieldtype": "Data", "oldfieldtype": "Data",
"permlevel": 0, "read_only": 1
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
} }
], ],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 1, "idx": 1,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1, "istable": 1,
"max_attachments": 0, "modified": "2019-11-21 16:25:29.909112",
"modified": "2019-01-07 16:51:59.536291",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Buying", "module": "Buying",
"name": "Purchase Receipt Item Supplied", "name": "Purchase Receipt Item Supplied",
"owner": "wasim@webnotestech.com", "owner": "wasim@webnotestech.com",
"permissions": [], "permissions": [],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"track_changes": 1, "track_changes": 1
"track_seen": 0,
"track_views": 0
} }

View File

@ -134,7 +134,7 @@ frappe.ui.form.on("Request for Quotation",{
if (args.search_type === "Tag" && args.tag) { if (args.search_type === "Tag" && args.tag) {
return frappe.call({ return frappe.call({
type: "GET", type: "GET",
method: "frappe.desk.tags.get_tagged_docs", method: "frappe.desk.doctype.tag.tag.get_tagged_docs",
args: { args: {
"doctype": "Supplier", "doctype": "Supplier",
"tag": args.tag "tag": args.tag

View File

@ -344,13 +344,9 @@ def get_item_from_material_requests_based_on_supplier(source_name, target_doc =
@frappe.whitelist() @frappe.whitelist()
def get_supplier_tag(): def get_supplier_tag():
data = frappe.db.sql("select _user_tags from `tabSupplier`") if not frappe.cache().hget("Supplier", "Tags"):
filters = {"document_type": "Supplier"}
tags = [] tags = list(set([tag.tag for tag in frappe.get_all("Tag Link", filters=filters, fields=["tag"]) if tag]))
for tag in data: frappe.cache().hset("Supplier", "Tags", tags)
tags += filter(bool, tag[0].split(","))
tags = list(set(tags))
return tags
return frappe.cache().hget("Supplier", "Tags")

View File

@ -24,12 +24,12 @@ def update_last_purchase_rate(doc, is_submit):
last_purchase_rate = None last_purchase_rate = None
if last_purchase_details and \ if last_purchase_details and \
(last_purchase_details.purchase_date > this_purchase_date): (last_purchase_details.purchase_date > this_purchase_date):
last_purchase_rate = last_purchase_details['base_rate'] last_purchase_rate = last_purchase_details['base_net_rate']
elif is_submit == 1: elif is_submit == 1:
# even if this transaction is the latest one, it should be submitted # even if this transaction is the latest one, it should be submitted
# for it to be considered for latest purchase rate # for it to be considered for latest purchase rate
if flt(d.conversion_factor): if flt(d.conversion_factor):
last_purchase_rate = flt(d.base_rate) / flt(d.conversion_factor) last_purchase_rate = flt(d.base_net_rate) / flt(d.conversion_factor)
# Check if item code is present # Check if item code is present
# Conversion factor should not be mandatory for non itemized items # Conversion factor should not be mandatory for non itemized items
elif d.item_code: elif d.item_code:

View File

@ -51,6 +51,11 @@ def get_data():
"name": "Appointment", "name": "Appointment",
"description" : _("Helps you manage appointments with your leads"), "description" : _("Helps you manage appointments with your leads"),
}, },
{
"type": "doctype",
"name": "Newsletter",
"label": _("Newsletter"),
}
] ]
}, },
{ {
@ -170,6 +175,11 @@ def get_data():
"type": "doctype", "type": "doctype",
"name": "SMS Settings", "name": "SMS Settings",
"description": _("Setup SMS gateway settings") "description": _("Setup SMS gateway settings")
},
{
"type": "doctype",
"label": _("Email Group"),
"name": "Email Group",
} }
] ]
}, },

View File

@ -5,15 +5,17 @@ from __future__ import unicode_literals
import frappe, erpnext import frappe, erpnext
import json import json
from frappe import _, throw from frappe import _, throw
from frappe.utils import today, flt, cint, fmt_money, formatdate, getdate, add_days, add_months, get_last_day, nowdate from frappe.utils import (today, flt, cint, fmt_money, formatdate,
from erpnext.stock.get_item_details import get_conversion_factor 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.setup.utils import get_exchange_rate
from erpnext.accounts.utils import get_fiscal_years, validate_fiscal_year, get_account_currency from erpnext.accounts.utils import get_fiscal_years, validate_fiscal_year, get_account_currency
from erpnext.utilities.transaction_base import TransactionBase from erpnext.utilities.transaction_base import TransactionBase
from erpnext.buying.utils import update_last_purchase_rate from erpnext.buying.utils import update_last_purchase_rate
from erpnext.controllers.sales_and_purchase_return import validate_return 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.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 erpnext.exceptions import InvalidCurrency
from six import text_type from six import text_type
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions
@ -101,7 +103,7 @@ class AccountsController(TransactionBase):
validate_regional(self) validate_regional(self)
if self.doctype != 'Material Request': if self.doctype != 'Material Request':
validate_pricing_rules(self) apply_pricing_rule_on_transaction(self)
def validate_invoice_documents_schedule(self): def validate_invoice_documents_schedule(self):
self.validate_payment_schedule_dates() self.validate_payment_schedule_dates()
@ -232,7 +234,6 @@ class AccountsController(TransactionBase):
def set_missing_item_details(self, for_validate=False): def set_missing_item_details(self, for_validate=False):
"""set missing item values""" """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 from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
if hasattr(self, "items"): if hasattr(self, "items"):
@ -244,7 +245,6 @@ class AccountsController(TransactionBase):
document_type = "{} Item".format(self.doctype) document_type = "{} Item".format(self.doctype)
parent_dict.update({"document_type": document_type}) parent_dict.update({"document_type": document_type})
self.set('pricing_rules', [])
# party_name field used for customer in quotation # party_name field used for customer in quotation
if self.doctype == "Quotation" and self.quotation_to == "Customer" and parent_dict.get("party_name"): if self.doctype == "Quotation" and self.quotation_to == "Customer" and parent_dict.get("party_name"):
parent_dict.update({"customer": 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"): if self.get("is_subcontracted"):
args["is_subcontracted"] = self.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(): for fieldname, value in ret.items():
if item.meta.get_field(fieldname) and value is not None: if item.meta.get_field(fieldname) and value is not None:
@ -285,13 +285,21 @@ class AccountsController(TransactionBase):
if self.doctype in ["Purchase Invoice", "Sales Invoice"] and item.meta.get_field('is_fixed_asset'): 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)) 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 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 user changed the discount percentage then set user's discount percentage ?
item.set("pricing_rules", ret.get("pricing_rules")) if pricing_rule_args.get("price_or_product_discount") == 'Price':
item.set("discount_percentage", ret.get("discount_percentage")) item.set("pricing_rules", pricing_rule_args.get("pricing_rules"))
item.set("discount_amount", ret.get("discount_amount")) item.set("discount_percentage", pricing_rule_args.get("discount_percentage"))
if ret.get("pricing_rule_for") == "Rate": item.set("discount_amount", pricing_rule_args.get("discount_amount"))
item.set("price_list_rate", ret.get("price_list_rate")) 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"): if item.get("price_list_rate"):
item.rate = flt(item.price_list_rate * item.rate = flt(item.price_list_rate *
@ -300,8 +308,18 @@ class AccountsController(TransactionBase):
if item.get('discount_amount'): if item.get('discount_amount'):
item.rate = item.price_list_rate - item.discount_amount item.rate = item.price_list_rate - item.discount_amount
if self.doctype == "Purchase Invoice": elif pricing_rule_args.get('free_item'):
self.set_expense_account(for_validate) 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): def set_taxes(self):
if not self.meta.get_field("taxes"): if not self.meta.get_field("taxes"):
@ -718,48 +736,6 @@ class AccountsController(TransactionBase):
# at quotation / sales order level and we shouldn't stop someone # at quotation / sales order level and we shouldn't stop someone
# from creating a sales invoice if sales order is already created # 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): def delink_advance_entries(self, linked_doc_name):
total_allocated_amount = 0 total_allocated_amount = 0
for adv in self.advances: for adv in self.advances:

View File

@ -101,7 +101,7 @@ class BuyingController(StockController):
msgprint(_('Tax Category has been changed to "Total" because all the Items are non-stock items')) msgprint(_('Tax Category has been changed to "Total" because all the Items are non-stock items'))
def get_asset_items(self): 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 []
return [d.item_code for d in self.items if d.is_fixed_asset] 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 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 stock_and_asset_items_qty, stock_and_asset_items_amount = 0, 0
last_stock_item_idx = 1 last_item_idx = 1
for d in self.get(parentfield): for d in self.get(parentfield):
if d.item_code and d.item_code in stock_items: if d.item_code and d.item_code in stock_and_asset_items:
stock_items_qty += flt(d.qty) stock_and_asset_items_qty += flt(d.qty)
stock_items_amount += flt(d.base_net_amount) stock_and_asset_items_amount += flt(d.base_net_amount)
last_stock_item_idx = d.idx last_item_idx = d.idx
total_valuation_amount = sum([flt(d.base_tax_amount_after_discount_amount) for d in self.get("taxes") 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"]]) if d.category in ["Valuation", "Valuation and Total"]])
valuation_amount_adjustment = total_valuation_amount valuation_amount_adjustment = total_valuation_amount
for i, item in enumerate(self.get(parentfield)): for i, item in enumerate(self.get(parentfield)):
if item.item_code and item.qty and item.item_code in stock_items: if item.item_code and item.qty and item.item_code in stock_and_asset_items:
item_proportion = flt(item.base_net_amount) / stock_items_amount if stock_items_amount \ item_proportion = flt(item.base_net_amount) / stock_and_asset_items_amount if stock_and_asset_items_amount \
else flt(item.qty) / stock_items_qty else flt(item.qty) / stock_and_asset_items_qty
if i == (last_stock_item_idx - 1):
if i == (last_item_idx - 1):
item.item_tax_amount = flt(valuation_amount_adjustment, item.item_tax_amount = flt(valuation_amount_adjustment,
self.precision("item_tax_amount", item)) self.precision("item_tax_amount", item))
else: else:
@ -572,43 +573,33 @@ class BuyingController(StockController):
asset_items = self.get_asset_items() asset_items = self.get_asset_items()
if 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) items_data = get_asset_item_details(asset_items)
messages = []
for d in self.items: for d in self.items:
if d.is_fixed_asset: if d.is_fixed_asset:
item_data = items_data.get(d.item_code) 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_data.get('auto_create_assets'):
# If item has serial no # If asset has to be auto created
if item_data.get('serial_no_series') and not d.serial_no: # Check for asset naming series
serial_nos = get_auto_serial_nos(item_data.get('serial_no_series'), d.qty) if item_data.get('asset_naming_series'):
elif d.serial_no: for qty in range(cint(d.qty)):
serial_nos = d.serial_no self.make_asset(d)
elif not d.serial_no: is_plural = 's' if cint(d.qty) != 1 else ''
frappe.throw(_("Serial no is mandatory for the item {0}").format(d.item_code)) messages.append(_('{0} Asset{2} Created for <b>{1}</b>').format(cint(d.qty), d.item_code, is_plural))
else:
frappe.throw(_("Row {1}: Asset Naming Series is mandatory for the auto creation for item {0}")
.format(d.item_code, d.idx))
else:
messages.append(_("Assets not created for <b>{0}</b>. You will have to create asset manually.")
.format(d.item_code))
auto_make_serial_nos({ for message in messages:
'serial_no': serial_nos, frappe.msgprint(message, title="Success")
'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)
def make_asset(self, row): def make_asset(self, row):
if not row.asset_location: if not row.asset_location:
@ -617,7 +608,7 @@ class BuyingController(StockController):
item_data = frappe.db.get_value('Item', item_data = frappe.db.get_value('Item',
row.item_code, ['asset_naming_series', 'asset_category'], as_dict=1) 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({ asset = frappe.get_doc({
'doctype': 'Asset', 'doctype': 'Asset',
'item_code': row.item_code, 'item_code': row.item_code,
@ -640,34 +631,20 @@ class BuyingController(StockController):
asset.set_missing_values() asset.set_missing_values()
asset.insert() 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): def update_fixed_asset(self, field, delete_asset = False):
for d in self.get("items"): for d in self.get("items"):
if d.is_fixed_asset and d.asset: if d.is_fixed_asset:
asset = frappe.get_doc("Asset", d.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: for asset in assets:
frappe.delete_doc("Asset", asset.name) asset = frappe.get_doc('Asset', asset.name)
d.db_set('asset', None) 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 continue
if self.docstatus in [0, 1] and not asset.get(field): if self.docstatus in [0, 1] and not asset.get(field):
@ -690,7 +667,6 @@ class BuyingController(StockController):
return return
frappe.db.sql("delete from `tabAsset Movement` where reference_name=%s", self.name) 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): def validate_schedule_date(self):
if not self.get("items"): if not self.get("items"):
@ -764,7 +740,7 @@ def get_backflushed_subcontracted_raw_materials_from_se(purchase_orders, purchas
def get_asset_item_details(asset_items): def get_asset_item_details(asset_items):
asset_items_data = {} 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)}): filters = {'name': ('in', asset_items)}):
asset_items_data.setdefault(d.name, d) asset_items_data.setdefault(d.name, d)

View File

@ -159,8 +159,12 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals
if "description" in searchfields: if "description" in searchfields:
searchfields.remove("description") searchfields.remove("description")
columns = [field for field in searchfields if not field in ["name", "item_group", "description"]] columns = ''
columns = ", ".join(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"] searchfields = searchfields + [field for field in[searchfield or "name", "item_code", "item_group", "item_name"]
if not field in searchfields] 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, concat(substr(tabItem.item_name, 1, 40), "..."), item_name) as item_name,
tabItem.item_group, tabItem.item_group,
if(length(tabItem.description) > 40, \ if(length(tabItem.description) > 40, \
concat(substr(tabItem.description, 1, 40), "..."), description) as description, concat(substr(tabItem.description, 1, 40), "..."), description) as description
{columns} {columns}
from tabItem from tabItem
where tabItem.docstatus < 2 where tabItem.docstatus < 2
@ -476,3 +480,29 @@ def item_manufacturer_query(doctype, txt, searchfield, start, page_len, filters)
as_list=1 as_list=1
) )
return item_manufacturers 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)

View File

@ -72,7 +72,7 @@ def validate_returned_items(doc):
items_returned = False items_returned = False
for d in doc.get("items"): 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: if d.item_code not in valid_items:
frappe.throw(_("Row # {0}: Returned Item {1} does not exists in {2} {3}") frappe.throw(_("Row # {0}: Returned Item {1} does not exists in {2} {3}")
.format(d.idx, d.item_code, doc.doctype, doc.return_against)) .format(d.idx, d.item_code, doc.doctype, doc.return_against))

View File

@ -552,7 +552,7 @@ class calculate_taxes_and_totals(object):
if item.price_list_rate: if item.price_list_rate:
if item.pricing_rules and not self.doc.ignore_pricing_rule: if item.pricing_rules and not self.doc.ignore_pricing_rule:
for d in item.pricing_rules.split(','): 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)\ if (pricing_rule.margin_type == 'Amount' and pricing_rule.currency == self.doc.currency)\
or (pricing_rule.margin_type == 'Percentage'): or (pricing_rule.margin_type == 'Percentage'):

View File

@ -4,11 +4,11 @@ cur_frm.add_fetch("employee", "image", "image");
frappe.ui.form.on("Instructor", { frappe.ui.form.on("Instructor", {
employee: function(frm) { employee: function(frm) {
if(!frm.doc.employee) return; 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() { frm.set_query("department", function() {
return { return {
"filters": { "filters": {
"company": company, "company": d.company,
} }
}; };
}); });
@ -16,7 +16,7 @@ frappe.ui.form.on("Instructor", {
frm.set_query("department", "instructor_log", function() { frm.set_query("department", "instructor_log", function() {
return { return {
"filters": { "filters": {
"company": company, "company": d.company,
} }
}; };
}); });

View File

@ -19,14 +19,19 @@ def get_approvers(doctype, txt, searchfield, start, page_len, filters):
approvers = [] approvers = []
department_details = {} department_details = {}
department_list = [] department_list = []
employee_department = filters.get("department") or frappe.get_value("Employee", filters.get("employee"), "department") employee = frappe.get_value("Employee", filters.get("employee"), ["department", "leave_approver"], as_dict=True)
employee_department = filters.get("department") or employee.department
if employee_department: if employee_department:
department_details = frappe.db.get_value("Department", {"name": employee_department}, ["lft", "rgt"], as_dict=True) department_details = frappe.db.get_value("Department", {"name": employee_department}, ["lft", "rgt"], as_dict=True)
if department_details: if department_details:
department_list = frappe.db.sql("""select name from `tabDepartment` where lft <= %s department_list = frappe.db.sql("""select name from `tabDepartment` where lft <= %s
and rgt >= %s and rgt >= %s
and disabled=0 and disabled=0
order by lft desc""", (department_details.lft, department_details.rgt), as_list = True) 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": if filters.get("doctype") == "Leave Application":
parentfield = "leave_approvers" parentfield = "leave_approvers"
@ -41,4 +46,4 @@ def get_approvers(doctype, txt, searchfield, start, page_len, filters):
and approver.parentfield = %s and approver.parentfield = %s
and approver.approver=user.name""",(d, "%" + txt + "%", parentfield), as_list=True) and approver.approver=user.name""",(d, "%" + txt + "%", parentfield), as_list=True)
return approvers return set(tuple(approver) for approver in approvers)

View File

@ -45,7 +45,7 @@ class TestEmployee(unittest.TestCase):
employee1_doc.status = 'Left' employee1_doc.status = 'Left'
self.assertRaises(EmployeeLeftValidationError, employee1_doc.save) self.assertRaises(EmployeeLeftValidationError, employee1_doc.save)
def make_employee(user): def make_employee(user, company=None):
if not frappe.db.get_value("User", user): if not frappe.db.get_value("User", user):
frappe.get_doc({ frappe.get_doc({
"doctype": "User", "doctype": "User",
@ -55,12 +55,12 @@ def make_employee(user):
"roles": [{"doctype": "Has Role", "role": "Employee"}] "roles": [{"doctype": "Has Role", "role": "Employee"}]
}).insert() }).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({ employee = frappe.get_doc({
"doctype": "Employee", "doctype": "Employee",
"naming_series": "EMP-", "naming_series": "EMP-",
"first_name": user, "first_name": user,
"company": erpnext.get_default_company(), "company": company or erpnext.get_default_company(),
"user_id": user, "user_id": user,
"date_of_birth": "1990-05-08", "date_of_birth": "1990-05-08",
"date_of_joining": "2013-01-01", "date_of_joining": "2013-01-01",

View File

@ -6,6 +6,7 @@ from __future__ import unicode_literals
import frappe import frappe
import json import json
from frappe.model.document import Document from frappe.model.document import Document
from frappe.utils import getdate
class EmployeeAttendanceTool(Document): class EmployeeAttendanceTool(Document):
@ -43,17 +44,26 @@ def get_employees(date, department = None, branch = None, company = None):
@frappe.whitelist() @frappe.whitelist()
def mark_employee_attendance(employee_list, status, date, leave_type=None, company=None): def mark_employee_attendance(employee_list, status, date, leave_type=None, company=None):
employee_list = json.loads(employee_list) employee_list = json.loads(employee_list)
for employee in employee_list: for employee in employee_list:
attendance = frappe.new_doc("Attendance")
attendance.employee = employee['employee']
attendance.employee_name = employee['employee_name']
attendance.attendance_date = date
attendance.status = status
if status == "On Leave" and leave_type: if status == "On Leave" and leave_type:
attendance.leave_type = leave_type leave_type = leave_type
if company:
attendance.company = company
else: else:
attendance.company = frappe.db.get_value("Employee", employee['employee'], "Company") leave_type = None
if not company:
company = frappe.db.get_value("Employee", employee['employee'], "Company")
attendance=frappe.get_doc(dict(
doctype='Attendance',
employee=employee.get('employee'),
employee_name=employee.get('employee_name'),
attendance_date=getdate(date),
status=status,
leave_type=leave_type,
company=company
))
attendance.insert()
attendance.submit() attendance.submit()

View File

@ -208,6 +208,24 @@ frappe.ui.form.on("Expense Claim", {
frm.refresh_fields(); frm.refresh_fields();
}, },
grand_total: function(frm) {
frm.trigger("update_employee_advance_claimed_amount");
},
update_employee_advance_claimed_amount: function(frm) {
let amount_to_be_allocated = frm.doc.grand_total;
$.each(frm.doc.advances || [], function(i, advance){
if (amount_to_be_allocated >= advance.unclaimed_amount){
frm.doc.advances[i].allocated_amount = frm.doc.advances[i].unclaimed_amount;
amount_to_be_allocated -= advance.allocated_amount;
} else{
frm.doc.advances[i].allocated_amount = amount_to_be_allocated;
amount_to_be_allocated = 0;
}
frm.refresh_field("advances");
});
},
make_payment_entry: function(frm) { make_payment_entry: function(frm) {
var method = "erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry"; var method = "erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry";
if(frm.doc.__onload && frm.doc.__onload.make_payment_via_journal_entry) { if(frm.doc.__onload && frm.doc.__onload.make_payment_via_journal_entry) {
@ -300,7 +318,7 @@ frappe.ui.form.on("Expense Claim", {
row.advance_account = d.advance_account; row.advance_account = d.advance_account;
row.advance_paid = d.paid_amount; row.advance_paid = d.paid_amount;
row.unclaimed_amount = flt(d.paid_amount) - flt(d.claimed_amount); row.unclaimed_amount = flt(d.paid_amount) - flt(d.claimed_amount);
row.allocated_amount = flt(d.paid_amount) - flt(d.claimed_amount); row.allocated_amount = 0;
}); });
refresh_field("advances"); refresh_field("advances");
} }

View File

@ -55,11 +55,11 @@ class LeaveApplication(Document):
self.reload() self.reload()
def on_cancel(self): def on_cancel(self):
self.create_leave_ledger_entry(submit=False)
self.status = "Cancelled" self.status = "Cancelled"
# notify leave applier about cancellation # notify leave applier about cancellation
self.notify_employee() self.notify_employee()
self.cancel_attendance() self.cancel_attendance()
self.create_leave_ledger_entry(submit=False)
def validate_applicable_after(self): def validate_applicable_after(self):
if self.leave_type: if self.leave_type:
@ -351,6 +351,9 @@ class LeaveApplication(Document):
pass pass
def create_leave_ledger_entry(self, submit=True): def create_leave_ledger_entry(self, submit=True):
if self.status != 'Approved':
return
expiry_date = get_allocation_expiry(self.employee, self.leave_type, expiry_date = get_allocation_expiry(self.employee, self.leave_type,
self.to_date, self.from_date) self.to_date, self.from_date)

View File

@ -89,7 +89,8 @@
"fieldtype": "Link", "fieldtype": "Link",
"label": "Company", "label": "Company",
"options": "Company", "options": "Company",
"reqd": 1 "reqd": 1,
"search_index": 1
}, },
{ {
"fieldname": "section_break_12", "fieldname": "section_break_12",
@ -129,7 +130,7 @@
} }
], ],
"is_submittable": 1, "is_submittable": 1,
"modified": "2019-10-16 13:38:32.302316", "modified": "2019-11-18 19:37:37.151686",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Manufacturing", "module": "Manufacturing",
"name": "Blanket Order", "name": "Blanket Order",

View File

@ -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", "creation": "2018-05-24 07:20:04.255236",
"custom": 0,
"docstatus": 0,
"doctype": "DocType", "doctype": "DocType",
"document_type": "",
"editable_grid": 1, "editable_grid": 1,
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [
"item_code",
"item_name",
"column_break_3",
"qty",
"rate",
"ordered_qty",
"section_break_7",
"terms_and_conditions"
],
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "item_code", "fieldname": "item_code",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0,
"label": "Item Code", "label": "Item Code",
"length": 0,
"no_copy": 0,
"options": "Item", "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, "reqd": 1,
"search_index": 0, "search_index": 1
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_from": "item_code.item_name", "fetch_from": "item_code.item_name",
"fieldname": "item_name", "fieldname": "item_name",
"fieldtype": "Data", "fieldtype": "Data",
"hidden": 0, "label": "Item Name"
"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
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_3", "fieldname": "column_break_3",
"fieldtype": "Column Break", "fieldtype": "Column Break"
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "qty", "fieldname": "qty",
"fieldtype": "Float", "fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0, "label": "Quantity"
"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
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "rate", "fieldname": "rate",
"fieldtype": "Currency", "fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0,
"label": "Rate", "label": "Rate",
"length": 0, "reqd": 1
"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
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "ordered_qty", "fieldname": "ordered_qty",
"fieldtype": "Float", "fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0,
"label": "Ordered Quantity", "label": "Ordered Quantity",
"length": 0,
"no_copy": 1, "no_copy": 1,
"permlevel": 0, "read_only": 1
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_7", "fieldname": "section_break_7",
"fieldtype": "Section Break", "fieldtype": "Section Break"
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "terms_and_conditions", "fieldname": "terms_and_conditions",
"fieldtype": "Text", "fieldtype": "Text",
"hidden": 0, "label": "Terms and Conditions"
"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
} }
], ],
"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, "istable": 1,
"max_attachments": 0, "modified": "2019-11-18 19:37:46.245878",
"modified": "2018-06-14 07:04:14.050836",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Manufacturing", "module": "Manufacturing",
"name": "Blanket Order Item", "name": "Blanket Order Item",
"name_case": "",
"owner": "Administrator", "owner": "Administrator",
"permissions": [], "permissions": [],
"quick_entry": 1, "quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"track_changes": 1, "track_changes": 1
"track_seen": 0
} }

View File

@ -3,6 +3,11 @@
frappe.ui.form.on('Production Plan', { frappe.ui.form.on('Production Plan', {
setup: function(frm) { setup: function(frm) {
frm.custom_make_buttons = {
'Work Order': 'Work Order',
'Material Request': 'Material Request',
};
frm.fields_dict['po_items'].grid.get_field('warehouse').get_query = function(doc) { frm.fields_dict['po_items'].grid.get_field('warehouse').get_query = function(doc) {
return { return {
filters: { filters: {

View File

@ -395,6 +395,11 @@ frappe.ui.form.on("Work Order", {
} }
}); });
} }
},
additional_operating_cost: function(frm) {
erpnext.work_order.calculate_cost(frm.doc);
erpnext.work_order.calculate_total_cost(frm);
} }
}); });
@ -534,8 +539,7 @@ erpnext.work_order = {
}, },
calculate_total_cost: function(frm) { calculate_total_cost: function(frm) {
var variable_cost = frm.doc.actual_operating_cost ? let variable_cost = flt(frm.doc.actual_operating_cost) || flt(frm.doc.planned_operating_cost);
flt(frm.doc.actual_operating_cost) : flt(frm.doc.planned_operating_cost);
frm.set_value("total_operating_cost", (flt(frm.doc.additional_operating_cost) + variable_cost)); frm.set_value("total_operating_cost", (flt(frm.doc.additional_operating_cost) + variable_cost));
}, },

View File

@ -216,14 +216,24 @@ class WorkOrder(Document):
self.db_set(fieldname, qty) self.db_set(fieldname, qty)
from erpnext.selling.doctype.sales_order.sales_order import update_produced_qty_in_so_item from erpnext.selling.doctype.sales_order.sales_order import update_produced_qty_in_so_item
update_produced_qty_in_so_item(self.sales_order_item)
if self.sales_order and self.sales_order_item:
update_produced_qty_in_so_item(self.sales_order, self.sales_order_item)
if self.production_plan: if self.production_plan:
self.update_production_plan_status() self.update_production_plan_status()
def update_production_plan_status(self): def update_production_plan_status(self):
production_plan = frappe.get_doc('Production Plan', self.production_plan) production_plan = frappe.get_doc('Production Plan', self.production_plan)
production_plan.run_method("update_produced_qty", self.produced_qty, self.production_plan_item) produced_qty = 0
if self.production_plan_item:
total_qty = frappe.get_all("Work Order", fields = "sum(produced_qty) as produced_qty",
filters = {'docstatus': 1, 'production_plan': self.production_plan,
'production_plan_item': self.production_plan_item}, as_list=1)
produced_qty = total_qty[0][0] if total_qty else 0
production_plan.run_method("update_produced_qty", produced_qty, self.production_plan_item)
def on_submit(self): def on_submit(self):
if not self.wip_warehouse: if not self.wip_warehouse:

View File

@ -638,7 +638,6 @@ erpnext.patches.v12_0.add_variant_of_in_item_attribute_table
erpnext.patches.v12_0.rename_bank_account_field_in_journal_entry_account erpnext.patches.v12_0.rename_bank_account_field_in_journal_entry_account
erpnext.patches.v12_0.create_default_energy_point_rules erpnext.patches.v12_0.create_default_energy_point_rules
erpnext.patches.v12_0.set_produced_qty_field_in_sales_order_for_work_order erpnext.patches.v12_0.set_produced_qty_field_in_sales_order_for_work_order
erpnext.patches.v12_0.generate_leave_ledger_entries
erpnext.patches.v12_0.set_default_shopify_app_type erpnext.patches.v12_0.set_default_shopify_app_type
erpnext.patches.v12_0.set_cwip_and_delete_asset_settings erpnext.patches.v12_0.set_cwip_and_delete_asset_settings
erpnext.patches.v12_0.set_expense_account_in_landed_cost_voucher_taxes erpnext.patches.v12_0.set_expense_account_in_landed_cost_voucher_taxes
@ -646,3 +645,5 @@ erpnext.patches.v12_0.replace_accounting_with_accounts_in_home_settings
erpnext.patches.v12_0.set_payment_entry_status erpnext.patches.v12_0.set_payment_entry_status
erpnext.patches.v12_0.update_owner_fields_in_acc_dimension_custom_fields erpnext.patches.v12_0.update_owner_fields_in_acc_dimension_custom_fields
erpnext.patches.v12_0.set_default_for_add_taxes_from_item_tax_template erpnext.patches.v12_0.set_default_for_add_taxes_from_item_tax_template
erpnext.patches.v12_0.remove_denied_leaves_from_leave_ledger
erpnext.patches.v12_0.update_price_or_product_discount

View File

@ -17,10 +17,6 @@ def execute():
frappe.db.sql(""" update `tabAsset` ast, `tabWarehouse` wh frappe.db.sql(""" update `tabAsset` ast, `tabWarehouse` wh
set ast.location = wh.warehouse_name where ast.warehouse = wh.name""") 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'): for d in frappe.get_all('Asset'):
doc = frappe.get_doc('Asset', d.name) doc = frappe.get_doc('Asset', d.name)
if doc.calculate_depreciation: if doc.calculate_depreciation:

View File

@ -1,20 +1,30 @@
import frappe import frappe
import json import json
from six import iteritems from six import iteritems
from frappe.model.naming import make_autoname
def execute(): def execute():
if "tax_type" not in frappe.db.get_table_columns("Item Tax"): if "tax_type" not in frappe.db.get_table_columns("Item Tax"):
return return
old_item_taxes = {} old_item_taxes = {}
item_tax_templates = {} item_tax_templates = {}
rename_template_to_untitled = []
frappe.reload_doc("accounts", "doctype", "item_tax_template_detail", force=1)
frappe.reload_doc("accounts", "doctype", "item_tax_template", force=1)
existing_templates = frappe.db.sql("""select template.name, details.tax_type, details.tax_rate
from `tabItem Tax Template` template, `tabItem Tax Template Detail` details
where details.parent=template.name
""", as_dict=1)
if len(existing_templates):
for d in existing_templates:
item_tax_templates.setdefault(d.name, {})
item_tax_templates[d.name][d.tax_type] = d.tax_rate
for d in frappe.db.sql("""select parent as item_code, tax_type, tax_rate from `tabItem Tax`""", as_dict=1): for d in frappe.db.sql("""select parent as item_code, tax_type, tax_rate from `tabItem Tax`""", as_dict=1):
old_item_taxes.setdefault(d.item_code, []) old_item_taxes.setdefault(d.item_code, [])
old_item_taxes[d.item_code].append(d) old_item_taxes[d.item_code].append(d)
frappe.reload_doc("accounts", "doctype", "item_tax_template_detail", force=1)
frappe.reload_doc("accounts", "doctype", "item_tax_template", force=1)
frappe.reload_doc("stock", "doctype", "item", force=1) frappe.reload_doc("stock", "doctype", "item", force=1)
frappe.reload_doc("stock", "doctype", "item_tax", force=1) frappe.reload_doc("stock", "doctype", "item_tax", force=1)
frappe.reload_doc("selling", "doctype", "quotation_item", force=1) frappe.reload_doc("selling", "doctype", "quotation_item", force=1)
@ -27,6 +37,8 @@ def execute():
frappe.reload_doc("accounts", "doctype", "purchase_invoice_item", force=1) frappe.reload_doc("accounts", "doctype", "purchase_invoice_item", force=1)
frappe.reload_doc("accounts", "doctype", "accounts_settings", force=1) frappe.reload_doc("accounts", "doctype", "accounts_settings", force=1)
frappe.db.auto_commit_on_many_writes = True
# for each item that have item tax rates # for each item that have item tax rates
for item_code in old_item_taxes.keys(): for item_code in old_item_taxes.keys():
# make current item's tax map # make current item's tax map
@ -34,8 +46,7 @@ def execute():
for d in old_item_taxes[item_code]: for d in old_item_taxes[item_code]:
item_tax_map[d.tax_type] = d.tax_rate item_tax_map[d.tax_type] = d.tax_rate
item_tax_template_name = get_item_tax_template(item_tax_templates, rename_template_to_untitled, item_tax_template_name = get_item_tax_template(item_tax_templates, item_tax_map, item_code)
item_tax_map, item_code)
# update the item tax table # update the item tax table
item = frappe.get_doc("Item", item_code) item = frappe.get_doc("Item", item_code)
@ -49,35 +60,33 @@ def execute():
'Quotation', 'Sales Order', 'Delivery Note', 'Sales Invoice', 'Quotation', 'Sales Order', 'Delivery Note', 'Sales Invoice',
'Supplier Quotation', 'Purchase Order', 'Purchase Receipt', 'Purchase Invoice' 'Supplier Quotation', 'Purchase Order', 'Purchase Receipt', 'Purchase Invoice'
] ]
for dt in doctypes: for dt in doctypes:
for d in frappe.db.sql("""select name, parent, item_code, item_tax_rate from `tab{0} Item` for d in frappe.db.sql("""select name, parent, item_code, item_tax_rate from `tab{0} Item`
where ifnull(item_tax_rate, '') not in ('', '{{}}')""".format(dt), as_dict=1): where ifnull(item_tax_rate, '') not in ('', '{{}}')
and item_tax_template is NULL""".format(dt), as_dict=1):
item_tax_map = json.loads(d.item_tax_rate) item_tax_map = json.loads(d.item_tax_rate)
item_tax_template = get_item_tax_template(item_tax_templates, rename_template_to_untitled, item_tax_template_name = get_item_tax_template(item_tax_templates,
item_tax_map, d.item_code, d.parent) item_tax_map, d.item_code, d.parent)
frappe.db.set_value(dt + " Item", d.name, "item_tax_template", item_tax_template) frappe.db.set_value(dt + " Item", d.name, "item_tax_template", item_tax_template_name)
idx = 1 frappe.db.auto_commit_on_many_writes = False
for oldname in rename_template_to_untitled:
frappe.rename_doc("Item Tax Template", oldname, "Untitled {}".format(idx))
idx += 1
settings = frappe.get_single("Accounts Settings") settings = frappe.get_single("Accounts Settings")
settings.add_taxes_from_item_tax_template = 0 settings.add_taxes_from_item_tax_template = 0
settings.determine_address_tax_category_from = "Billing Address" settings.determine_address_tax_category_from = "Billing Address"
settings.save() settings.save()
def get_item_tax_template(item_tax_templates, rename_template_to_untitled, item_tax_map, item_code, parent=None): def get_item_tax_template(item_tax_templates, item_tax_map, item_code, parent=None):
# search for previously created item tax template by comparing tax maps # search for previously created item tax template by comparing tax maps
for template, item_tax_template_map in iteritems(item_tax_templates): for template, item_tax_template_map in iteritems(item_tax_templates):
if item_tax_map == item_tax_template_map: if item_tax_map == item_tax_template_map:
if not parent:
rename_template_to_untitled.append(template)
return template return template
# if no item tax template found, create one # if no item tax template found, create one
item_tax_template = frappe.new_doc("Item Tax Template") item_tax_template = frappe.new_doc("Item Tax Template")
item_tax_template.title = "{}--{}".format(parent, item_code) if parent else "Item-{}".format(item_code) item_tax_template.title = make_autoname("Item Tax Template-.####")
for tax_type, tax_rate in iteritems(item_tax_map): for tax_type, tax_rate in iteritems(item_tax_map):
if not frappe.db.exists("Account", tax_type): if not frappe.db.exists("Account", tax_type):
parts = tax_type.strip().split(" - ") parts = tax_type.strip().split(" - ")

View File

@ -0,0 +1,28 @@
# Copyright (c) 2018, Frappe and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
from frappe.utils import getdate, today
def execute():
''' Delete leave ledger entry created
via leave applications with status != Approved '''
if not frappe.db.a_row_exists("Leave Ledger Entry"):
return
leave_application_list = get_denied_leave_application_list()
if leave_application_list:
delete_denied_leaves_from_leave_ledger_entry(leave_application_list)
def get_denied_leave_application_list():
return frappe.db.sql_list(''' Select name from `tabLeave Application` where status <> 'Approved' ''')
def delete_denied_leaves_from_leave_ledger_entry(leave_application_list):
if leave_application_list:
frappe.db.sql(''' Delete
FROM `tabLeave Ledger Entry`
WHERE
transaction_type = 'Leave Application'
AND transaction_name in (%s) ''' % (', '.join(['%s'] * len(leave_application_list))), #nosec
tuple(leave_application_list))

View File

@ -7,16 +7,11 @@ def execute():
'''Get 'Disable CWIP Accounting value' from Asset Settings, set it in 'Enable Capital Work in Progress Accounting' field '''Get 'Disable CWIP Accounting value' from Asset Settings, set it in 'Enable Capital Work in Progress Accounting' field
in Company, delete Asset Settings ''' in Company, delete Asset Settings '''
if frappe.db.exists("DocType","Asset Settings"): if frappe.db.exists("DocType", "Asset Settings"):
frappe.reload_doctype("Company") frappe.reload_doctype("Asset Category")
cwip_value = frappe.db.sql(""" SELECT value FROM `tabSingles` WHERE doctype='Asset Settings' cwip_value = frappe.db.get_single_value("Asset Settings", "disable_cwip_accounting")
and field='disable_cwip_accounting' """, as_dict=1)
companies = [x['name'] for x in frappe.get_all("Company", "name")] frappe.db.sql("""UPDATE `tabAsset Category` SET enable_cwip_accounting = %s""", cint(cwip_value))
for company in companies:
enable_cwip_accounting = cint(not cint(cwip_value[0]['value']))
frappe.set_value("Company", company, "enable_cwip_accounting", enable_cwip_accounting)
frappe.db.sql( frappe.db.sql("""DELETE FROM `tabSingles` where doctype = 'Asset Settings'""")
""" DELETE FROM `tabSingles` where doctype = 'Asset Settings' """) frappe.delete_doc_if_exists("DocType", "Asset Settings")
frappe.delete_doc_if_exists("DocType","Asset Settings")

View File

@ -5,6 +5,10 @@ from erpnext.selling.doctype.sales_order.sales_order import update_produced_qty_
def execute(): def execute():
frappe.reload_doctype('Sales Order Item') frappe.reload_doctype('Sales Order Item')
frappe.reload_doctype('Sales Order') frappe.reload_doctype('Sales Order')
sales_order_items = frappe.db.get_all('Sales Order Item', ['name'])
for so_item in sales_order_items: for d in frappe.get_all('Work Order',
update_produced_qty_in_so_item(so_item.get('name')) fields = ['sales_order', 'sales_order_item'],
filters={'sales_order': ('!=', ''), 'sales_order_item': ('!=', '')}):
# update produced qty in sales order
update_produced_qty_in_so_item(d.sales_order, d.sales_order_item)

View File

@ -0,0 +1,8 @@
from __future__ import unicode_literals
import frappe
def execute():
frappe.reload_doc("accounts", "doctype", "pricing_rule")
frappe.db.sql(""" UPDATE `tabPricing Rule` SET price_or_product_discount = 'Price'
WHERE ifnull(price_or_product_discount,'') = '' """)

View File

@ -10,6 +10,7 @@ from frappe import _, throw
from frappe.utils import add_days, cstr, date_diff, get_link_to_form, getdate from frappe.utils import add_days, cstr, date_diff, get_link_to_form, getdate
from frappe.utils.nestedset import NestedSet from frappe.utils.nestedset import NestedSet
from frappe.desk.form.assign_to import close_all_assignments, clear from frappe.desk.form.assign_to import close_all_assignments, clear
from frappe.utils import date_diff
class CircularReferenceError(frappe.ValidationError): pass class CircularReferenceError(frappe.ValidationError): pass
class EndDateCannotBeGreaterThanProjectEndDateError(frappe.ValidationError): pass class EndDateCannotBeGreaterThanProjectEndDateError(frappe.ValidationError): pass
@ -28,16 +29,29 @@ class Task(NestedSet):
def validate(self): def validate(self):
self.validate_dates() self.validate_dates()
self.validate_parent_project_dates()
self.validate_progress() self.validate_progress()
self.validate_status() self.validate_status()
self.update_depends_on() self.update_depends_on()
def validate_dates(self): def validate_dates(self):
if self.exp_start_date and self.exp_end_date and getdate(self.exp_start_date) > getdate(self.exp_end_date): if self.exp_start_date and self.exp_end_date and getdate(self.exp_start_date) > getdate(self.exp_end_date):
frappe.throw(_("'Expected Start Date' can not be greater than 'Expected End Date'")) frappe.throw(_("{0} can not be greater than {1}").format(frappe.bold("Expected Start Date"), \
frappe.bold("Expected End Date")))
if self.act_start_date and self.act_end_date and getdate(self.act_start_date) > getdate(self.act_end_date): if self.act_start_date and self.act_end_date and getdate(self.act_start_date) > getdate(self.act_end_date):
frappe.throw(_("'Actual Start Date' can not be greater than 'Actual End Date'")) frappe.throw(_("{0} can not be greater than {1}").format(frappe.bold("Actual Start Date"), \
frappe.bold("Actual End Date")))
def validate_parent_project_dates(self):
if not self.project or frappe.flags.in_test:
return
expected_end_date = getdate(frappe.db.get_value("Project", self.project, "expected_end_date"))
if expected_end_date:
validate_project_dates(expected_end_date, self, "exp_start_date", "exp_end_date", "Expected")
validate_project_dates(expected_end_date, self, "act_start_date", "act_end_date", "Actual")
def validate_status(self): def validate_status(self):
if self.status!=self.get_db_value("status") and self.status == "Completed": if self.status!=self.get_db_value("status") and self.status == "Completed":
@ -255,3 +269,10 @@ def add_multiple_tasks(data, parent):
def on_doctype_update(): def on_doctype_update():
frappe.db.add_index("Task", ["lft", "rgt"]) frappe.db.add_index("Task", ["lft", "rgt"])
def validate_project_dates(project_end_date, task, task_start, task_end, actual_or_expected_date):
if task.get(task_start) and date_diff(project_end_date, getdate(task.get(task_start))) < 0:
frappe.throw(_("Task's {0} Start Date cannot be after Project's End Date.").format(actual_or_expected_date))
if task.get(task_end) and date_diff(project_end_date, getdate(task.get(task_end))) < 0:
frappe.throw(_("Task's {0} End Date cannot be after Project's End Date.").format(actual_or_expected_date))

View File

@ -516,7 +516,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
} }
}, },
() => me.conversion_factor(doc, cdt, cdn, true), () => 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) { callback: function(r) {
if (!r.exc && r.message) { if (!r.exc && r.message) {
r.message.forEach(row_item => { 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._set_values_for_item_list(r.message);
me.calculate_taxes_and_totals(); me.calculate_taxes_and_totals();
@ -1283,6 +1283,8 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
_set_values_for_item_list: function(children) { _set_values_for_item_list: function(children) {
var me = this; var me = this;
var price_list_rate_changed = false; var price_list_rate_changed = false;
var items_rule_dict = {};
for(var i=0, l=children.length; i<l; i++) { for(var i=0, l=children.length; i<l; i++) {
var d = children[i]; var d = children[i];
var existing_pricing_rule = frappe.model.get_value(d.doctype, d.name, "pricing_rules"); var existing_pricing_rule = frappe.model.get_value(d.doctype, d.name, "pricing_rules");
@ -1299,14 +1301,39 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
// if pricing rule set as blank from an existing value, apply price_list // if pricing rule set as blank from an existing value, apply price_list
if(!me.frm.doc.ignore_pricing_rule && existing_pricing_rule && !d.pricing_rules) { if(!me.frm.doc.ignore_pricing_rule && existing_pricing_rule && !d.pricing_rules) {
me.apply_price_list(frappe.get_doc(d.doctype, d.name)); me.apply_price_list(frappe.get_doc(d.doctype, d.name));
} else { } else if(!d.pricing_rules) {
me.validate_pricing_rule(frappe.get_doc(d.doctype, d.name)); me.remove_pricing_rule(frappe.get_doc(d.doctype, d.name));
}
if (d.apply_rule_on_other_items) {
items_rule_dict[d.name] = d;
} }
} }
me.apply_rule_on_other_items(items_rule_dict);
if(!price_list_rate_changed) me.calculate_taxes_and_totals(); if(!price_list_rate_changed) me.calculate_taxes_and_totals();
}, },
apply_rule_on_other_items: function(args) {
const me = this;
const fields = ["discount_percentage", "discount_amount", "rate"];
for(var k in args) {
let data = args[k];
me.frm.doc.items.forEach(d => {
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) { apply_price_list: function(item, reset_plc_conversion) {
// We need to reset plc_conversion_rate sometimes because the call to // 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 // `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; let me = this;
const fields = ["discount_percentage", "discount_amount", "pricing_rules"]; const fields = ["discount_percentage", "discount_amount", "pricing_rules"];
if (item.pricing_rules) { if(item.remove_free_item) {
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) {
var items = []; var items = [];
me.frm.doc.items.forEach(d => { me.frm.doc.items.forEach(d => {

View File

@ -74,6 +74,22 @@ $.extend(erpnext, {
); );
}); });
}, },
route_to_adjustment_jv: (args) => {
frappe.model.with_doctype('Journal Entry', () => {
// route to adjustment Journal Entry to handle Account Balance and Stock Value mismatch
let journal_entry = frappe.model.get_new_doc('Journal Entry');
args.accounts.forEach((je_account) => {
let child_row = frappe.model.add_child(journal_entry, "accounts");
child_row.account = je_account.account;
child_row.debit_in_account_currency = je_account.debit_in_account_currency;
child_row.credit_in_account_currency = je_account.credit_in_account_currency;
child_row.party_type = "" ;
});
frappe.set_route('Form','Journal Entry', journal_entry.name);
});
}
}); });

View File

@ -49,9 +49,9 @@ frappe.ui.form.on("Customer", {
}) })
frm.set_query('customer_primary_address', function(doc) { frm.set_query('customer_primary_address', function(doc) {
return { return {
query: "erpnext.selling.doctype.customer.customer.get_customer_primary_address",
filters: { filters: {
'customer': doc.name 'link_doctype': 'Customer',
'link_name': doc.name
} }
} }
}) })

View File

@ -397,15 +397,3 @@ def get_customer_primary_contact(doctype, txt, searchfield, start, page_len, fil
'customer': customer, 'customer': customer,
'txt': '%%%s%%' % txt 'txt': '%%%s%%' % txt
}) })
def get_customer_primary_address(doctype, txt, searchfield, start, page_len, filters):
customer = frappe.db.escape(filters.get('customer'))
return frappe.db.sql("""
select `tabAddress`.name from `tabAddress`, `tabDynamic Link`
where `tabAddress`.name = `tabDynamic Link`.parent and `tabDynamic Link`.link_name = %(customer)s
and `tabDynamic Link`.link_doctype = 'Customer'
and `tabAddress`.name like %(txt)s
""", {
'customer': customer,
'txt': '%%%s%%' % txt
})

View File

@ -1038,14 +1038,18 @@ def create_pick_list(source_name, target_doc=None):
return doc return doc
def update_produced_qty_in_so_item(sales_order_item): def update_produced_qty_in_so_item(sales_order, sales_order_item):
#for multiple work orders against same sales order item #for multiple work orders against same sales order item
linked_wo_with_so_item = frappe.db.get_all('Work Order', ['produced_qty'], { linked_wo_with_so_item = frappe.db.get_all('Work Order', ['produced_qty'], {
'sales_order_item': sales_order_item, 'sales_order_item': sales_order_item,
'sales_order': sales_order,
'docstatus': 1 'docstatus': 1
}) })
if len(linked_wo_with_so_item) > 0:
total_produced_qty = 0 total_produced_qty = 0
for wo in linked_wo_with_so_item: for wo in linked_wo_with_so_item:
total_produced_qty += flt(wo.get('produced_qty')) total_produced_qty += flt(wo.get('produced_qty'))
if not total_produced_qty and frappe.flags.in_patch: return
frappe.db.set_value('Sales Order Item', sales_order_item, 'produced_qty', total_produced_qty) frappe.db.set_value('Sales Order Item', sales_order_item, 'produced_qty', total_produced_qty)

View File

@ -451,7 +451,7 @@ erpnext.pos.PointOfSale = class PointOfSale {
change_pos_profile() { change_pos_profile() {
return new Promise((resolve) => { return new Promise((resolve) => {
const on_submit = ({ pos_profile, set_as_default }) => { const on_submit = ({ company, pos_profile, set_as_default }) => {
if (pos_profile) { if (pos_profile) {
this.pos_profile = 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", method: "erpnext.accounts.doctype.pos_profile.pos_profile.set_default_profile",
args: { args: {
'pos_profile': pos_profile, 'pos_profile': pos_profile,
'company': this.frm.doc.company 'company': company
} }
}).then(() => { }).then(() => {
this.on_change_pos_profile(); 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, on_submit,
__('Select POS Profile') __('Select POS Profile')
); );
@ -494,26 +529,9 @@ erpnext.pos.PointOfSale = class PointOfSale {
]); ]);
} }
get_prompt_fields() { get_default_pos_profile(company) {
return [{ return frappe.xcall("erpnext.stock.get_item_details.get_pos_profile",
fieldtype: 'Link', {'company': company})
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'
}];
} }
setup_company() { setup_company() {

View File

@ -29,7 +29,8 @@ frappe.ui.form.on("Company", {
company_name: function(frm) { company_name: function(frm) {
if(frm.doc.__islocal) { 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) { let abbr = $.map(parts, function (p) {
return p? p.substr(0, 1) : null; return p? p.substr(0, 1) : null;
}).join(""); }).join("");

View File

@ -72,7 +72,6 @@
"stock_received_but_not_billed", "stock_received_but_not_billed",
"expenses_included_in_valuation", "expenses_included_in_valuation",
"fixed_asset_depreciation_settings", "fixed_asset_depreciation_settings",
"enable_cwip_accounting",
"accumulated_depreciation_account", "accumulated_depreciation_account",
"depreciation_expense_account", "depreciation_expense_account",
"series_for_depreciation_entry", "series_for_depreciation_entry",
@ -721,18 +720,12 @@
"fieldtype": "Link", "fieldtype": "Link",
"label": "Default Buying Terms", "label": "Default Buying Terms",
"options": "Terms and Conditions" "options": "Terms and Conditions"
},
{
"default": "0",
"fieldname": "enable_cwip_accounting",
"fieldtype": "Check",
"label": "Enable Capital Work in Progress Accounting"
} }
], ],
"icon": "fa fa-building", "icon": "fa fa-building",
"idx": 1, "idx": 1,
"image_field": "company_logo", "image_field": "company_logo",
"modified": "2019-10-09 14:42:04.440974", "modified": "2019-11-22 13:04:47.470768",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Setup", "module": "Setup",
"name": "Company", "name": "Company",

View File

@ -14,10 +14,14 @@ class CurrencyExchange(Document):
purpose = "" purpose = ""
if not self.date: if not self.date:
self.date = nowdate() self.date = nowdate()
# If both selling and buying enabled
purpose = "Selling-Buying"
if cint(self.for_buying)==0 and cint(self.for_selling)==1: if cint(self.for_buying)==0 and cint(self.for_selling)==1:
purpose = "Selling" purpose = "Selling"
if cint(self.for_buying)==1 and cint(self.for_selling)==0: if cint(self.for_buying)==1 and cint(self.for_selling)==0:
purpose = "Buying" purpose = "Buying"
self.name = '{0}-{1}-{2}{3}'.format(formatdate(get_datetime_str(self.date), "yyyy-MM-dd"), self.name = '{0}-{1}-{2}{3}'.format(formatdate(get_datetime_str(self.date), "yyyy-MM-dd"),
self.from_currency, self.to_currency, ("-" + purpose) if purpose else "") self.from_currency, self.to_currency, ("-" + purpose) if purpose else "")

View File

@ -11,7 +11,9 @@ test_records = frappe.get_test_records('Currency Exchange')
def save_new_records(test_records): def save_new_records(test_records):
for record in test_records: for record in test_records:
purpose = str("") # If both selling and buying enabled
purpose = "Selling-Buying"
if cint(record.get("for_buying"))==0 and cint(record.get("for_selling"))==1: if cint(record.get("for_buying"))==0 and cint(record.get("for_selling"))==1:
purpose = "Selling" purpose = "Selling"
if cint(record.get("for_buying"))==1 and cint(record.get("for_selling"))==0: if cint(record.get("for_buying"))==1 and cint(record.get("for_selling"))==0:

View File

@ -39,6 +39,7 @@ class ItemGroup(NestedSet, WebsiteGenerator):
invalidate_cache_for(self) invalidate_cache_for(self)
self.validate_name_with_item() self.validate_name_with_item()
self.validate_one_root() self.validate_one_root()
self.delete_child_item_groups_key()
def make_route(self): def make_route(self):
'''Make website route''' '''Make website route'''
@ -58,6 +59,7 @@ class ItemGroup(NestedSet, WebsiteGenerator):
def on_trash(self): def on_trash(self):
NestedSet.on_trash(self) NestedSet.on_trash(self)
WebsiteGenerator.on_trash(self) WebsiteGenerator.on_trash(self)
self.delete_child_item_groups_key()
def validate_name_with_item(self): def validate_name_with_item(self):
if frappe.db.exists("Item", self.name): if frappe.db.exists("Item", self.name):
@ -83,6 +85,9 @@ class ItemGroup(NestedSet, WebsiteGenerator):
return context return context
def delete_child_item_groups_key(self):
frappe.cache().hdel("child_item_groups", self.name)
@frappe.whitelist(allow_guest=True) @frappe.whitelist(allow_guest=True)
def get_product_list_for_group(product_group=None, start=0, limit=10, search=None): def get_product_list_for_group(product_group=None, start=0, limit=10, search=None):
if product_group: 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'], fields = ['name', 'route', 'description', 'image'],
filters = dict( filters = dict(
show_in_website = 1, show_in_website = 1,
parent_item_group = item_group.name,
lft = ('>', item_group.lft), lft = ('>', item_group.lft),
rgt = ('<', item_group.rgt), 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 from `tabItem Group` where lft>=%(lft)s and rgt<=%(rgt)s
and show_in_website = 1""", {"lft": item_group.lft, "rgt": item_group.rgt}) 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): def get_item_for_list_in_html(context):
# add missing absolute link in files # add missing absolute link in files
# user may forget it during upload # user may forget it during upload

View File

@ -1,599 +1,200 @@
{ {
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "MAT-BIN-.YYYY.-.#####", "autoname": "MAT-BIN-.YYYY.-.#####",
"beta": 0,
"creation": "2013-01-10 16:34:25", "creation": "2013-01-10 16:34:25",
"custom": 0,
"docstatus": 0,
"doctype": "DocType", "doctype": "DocType",
"editable_grid": 0,
"engine": "InnoDB", "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": [ "fields": [
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "warehouse", "fieldname": "warehouse",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 1, "in_filter": 1,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Warehouse", "label": "Warehouse",
"length": 0,
"no_copy": 0,
"oldfieldname": "warehouse", "oldfieldname": "warehouse",
"oldfieldtype": "Link", "oldfieldtype": "Link",
"options": "Warehouse", "options": "Warehouse",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1, "read_only": 1,
"remember_last_selected_value": 0, "search_index": 1
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "item_code", "fieldname": "item_code",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 1, "in_filter": 1,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Item Code", "label": "Item Code",
"length": 0,
"no_copy": 0,
"oldfieldname": "item_code", "oldfieldname": "item_code",
"oldfieldtype": "Link", "oldfieldtype": "Link",
"options": "Item", "options": "Item",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1, "read_only": 1,
"remember_last_selected_value": 0, "search_index": 1
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0.00", "default": "0.00",
"fieldname": "reserved_qty", "fieldname": "reserved_qty",
"fieldtype": "Float", "fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0,
"label": "Reserved Quantity", "label": "Reserved Quantity",
"length": 0,
"no_copy": 0,
"oldfieldname": "reserved_qty", "oldfieldname": "reserved_qty",
"oldfieldtype": "Currency", "oldfieldtype": "Currency",
"permlevel": 0, "read_only": 1
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0.00", "default": "0.00",
"fieldname": "actual_qty", "fieldname": "actual_qty",
"fieldtype": "Float", "fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 1, "in_filter": 1,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0,
"label": "Actual Quantity", "label": "Actual Quantity",
"length": 0,
"no_copy": 0,
"oldfieldname": "actual_qty", "oldfieldname": "actual_qty",
"oldfieldtype": "Currency", "oldfieldtype": "Currency",
"permlevel": 0, "read_only": 1
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0.00", "default": "0.00",
"fieldname": "ordered_qty", "fieldname": "ordered_qty",
"fieldtype": "Float", "fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0,
"label": "Ordered Quantity", "label": "Ordered Quantity",
"length": 0,
"no_copy": 0,
"oldfieldname": "ordered_qty", "oldfieldname": "ordered_qty",
"oldfieldtype": "Currency", "oldfieldtype": "Currency",
"permlevel": 0, "read_only": 1
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0.00", "default": "0.00",
"fieldname": "indented_qty", "fieldname": "indented_qty",
"fieldtype": "Float", "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", "label": "Requested Quantity",
"length": 0,
"no_copy": 0,
"oldfieldname": "indented_qty", "oldfieldname": "indented_qty",
"oldfieldtype": "Currency", "oldfieldtype": "Currency",
"permlevel": 0, "read_only": 1
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "planned_qty", "fieldname": "planned_qty",
"fieldtype": "Float", "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", "label": "Planned Qty",
"length": 0,
"no_copy": 0,
"oldfieldname": "planned_qty", "oldfieldname": "planned_qty",
"oldfieldtype": "Currency", "oldfieldtype": "Currency",
"permlevel": 0, "read_only": 1
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "projected_qty", "fieldname": "projected_qty",
"fieldtype": "Float", "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", "label": "Projected Qty",
"length": 0,
"no_copy": 0,
"oldfieldname": "projected_qty", "oldfieldname": "projected_qty",
"oldfieldtype": "Currency", "oldfieldtype": "Currency",
"permlevel": 0, "read_only": 1
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "reserved_qty_for_production", "fieldname": "reserved_qty_for_production",
"fieldtype": "Float", "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", "label": "Reserved Qty for Production",
"length": 0, "read_only": 1
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "reserved_qty_for_sub_contract", "fieldname": "reserved_qty_for_sub_contract",
"fieldtype": "Float", "fieldtype": "Float",
"hidden": 0, "label": "Reserved Qty for sub contract"
"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
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "ma_rate", "fieldname": "ma_rate",
"fieldtype": "Float", "fieldtype": "Float",
"hidden": 1, "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", "label": "Moving Average Rate",
"length": 0,
"no_copy": 0,
"oldfieldname": "ma_rate", "oldfieldname": "ma_rate",
"oldfieldtype": "Currency", "oldfieldtype": "Currency",
"permlevel": 0,
"print_hide": 1, "print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1, "read_only": 1,
"remember_last_selected_value": 0, "report_hide": 1
"report_hide": 1,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "stock_uom", "fieldname": "stock_uom",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 1, "in_filter": 1,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "UOM", "label": "UOM",
"length": 0,
"no_copy": 0,
"oldfieldname": "stock_uom", "oldfieldname": "stock_uom",
"oldfieldtype": "Data", "oldfieldtype": "Data",
"options": "UOM", "options": "UOM",
"permlevel": 0, "read_only": 1
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "fcfs_rate", "fieldname": "fcfs_rate",
"fieldtype": "Float", "fieldtype": "Float",
"hidden": 1, "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", "label": "FCFS Rate",
"length": 0,
"no_copy": 0,
"oldfieldname": "fcfs_rate", "oldfieldname": "fcfs_rate",
"oldfieldtype": "Currency", "oldfieldtype": "Currency",
"permlevel": 0,
"print_hide": 1, "print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1, "read_only": 1,
"remember_last_selected_value": 0, "report_hide": 1
"report_hide": 1,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "valuation_rate", "fieldname": "valuation_rate",
"fieldtype": "Float", "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", "label": "Valuation Rate",
"length": 0,
"no_copy": 0,
"oldfieldname": "valuation_rate", "oldfieldname": "valuation_rate",
"oldfieldtype": "Currency", "oldfieldtype": "Currency",
"permlevel": 0, "read_only": 1
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "stock_value", "fieldname": "stock_value",
"fieldtype": "Float", "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", "label": "Stock Value",
"length": 0,
"no_copy": 0,
"oldfieldname": "stock_value", "oldfieldname": "stock_value",
"oldfieldtype": "Currency", "oldfieldtype": "Currency",
"permlevel": 0, "read_only": 1
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
} }
], ],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 1, "hide_toolbar": 1,
"idx": 1, "idx": 1,
"image_view": 0,
"in_create": 1, "in_create": 1,
"is_submittable": 0, "modified": "2019-11-18 18:34:59.456882",
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-08-21 16:15:39.356230",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Bin", "name": "Bin",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
"amend": 0,
"cancel": 0,
"create": 0,
"delete": 0,
"email": 1, "email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "Sales User", "role": "Sales User"
"set_user_permissions": 0,
"share": 0,
"submit": 0,
"write": 0
}, },
{ {
"amend": 0,
"cancel": 0,
"create": 0,
"delete": 0,
"email": 1, "email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "Purchase User", "role": "Purchase User"
"set_user_permissions": 0,
"share": 0,
"submit": 0,
"write": 0
}, },
{ {
"amend": 0,
"cancel": 0,
"create": 0,
"delete": 0,
"email": 1, "email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "Stock User", "role": "Stock User"
"set_user_permissions": 0,
"share": 0,
"submit": 0,
"write": 0
} }
], ],
"quick_entry": 1, "quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"search_fields": "item_code,warehouse", "search_fields": "item_code,warehouse",
"show_name_in_global_search": 0, "sort_order": "ASC"
"sort_order": "ASC",
"track_changes": 0,
"track_seen": 0,
"track_views": 0
} }

View File

@ -25,7 +25,7 @@ frappe.ui.form.on("Item", {
}, },
refresh: function(frm) { refresh: function(frm) {
if(frm.doc.is_stock_item) { if (frm.doc.is_stock_item) {
frm.add_custom_button(__("Balance"), function() { frm.add_custom_button(__("Balance"), function() {
frappe.route_options = { frappe.route_options = {
"item_code": frm.doc.name "item_code": frm.doc.name
@ -46,10 +46,15 @@ frappe.ui.form.on("Item", {
}, __("View")); }, __("View"));
} }
if(!frm.doc.is_fixed_asset) { if (!frm.doc.is_fixed_asset) {
erpnext.item.make_dashboard(frm); erpnext.item.make_dashboard(frm);
} }
if (frm.doc.is_fixed_asset) {
frm.trigger('is_fixed_asset');
frm.trigger('auto_create_assets');
}
// clear intro // clear intro
frm.set_intro(); frm.set_intro();
@ -132,6 +137,12 @@ frappe.ui.form.on("Item", {
}, },
is_fixed_asset: function(frm) { 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_reqd(['asset_category'], frm.doc.is_fixed_asset);
frm.toggle_display(['has_serial_no', 'serial_no_series'], !frm.doc.is_fixed_asset);
frm.call({ frm.call({
method: "set_asset_naming_series", method: "set_asset_naming_series",
doc: frm.doc, doc: frm.doc,
@ -139,7 +150,9 @@ frappe.ui.form.on("Item", {
frm.set_value("is_stock_item", frm.doc.is_fixed_asset ? 0 : 1); frm.set_value("is_stock_item", frm.doc.is_fixed_asset ? 0 : 1);
frm.trigger("set_asset_naming_series"); frm.trigger("set_asset_naming_series");
} }
}) });
frm.trigger('auto_create_assets');
}, },
set_asset_naming_series: function(frm) { set_asset_naming_series: function(frm) {
@ -148,6 +161,11 @@ frappe.ui.form.on("Item", {
} }
}, },
auto_create_assets: function(frm) {
frm.toggle_reqd(['asset_naming_series'], frm.doc.auto_create_assets);
frm.toggle_display(['asset_naming_series'], frm.doc.auto_create_assets);
},
page_name: frappe.utils.warn_page_name_change, page_name: frappe.utils.warn_page_name_change,
item_code: function(frm) { item_code: function(frm) {

View File

@ -27,6 +27,7 @@
"valuation_rate", "valuation_rate",
"standard_rate", "standard_rate",
"is_fixed_asset", "is_fixed_asset",
"auto_create_assets",
"asset_category", "asset_category",
"asset_naming_series", "asset_naming_series",
"over_delivery_receipt_allowance", "over_delivery_receipt_allowance",
@ -134,7 +135,8 @@
"publish_in_hub", "publish_in_hub",
"hub_category_to_publish", "hub_category_to_publish",
"hub_warehouse", "hub_warehouse",
"synced_with_hub" "synced_with_hub",
"manufacturers"
], ],
"fields": [ "fields": [
{ {
@ -158,8 +160,8 @@
"label": "Item Code", "label": "Item Code",
"oldfieldname": "item_code", "oldfieldname": "item_code",
"oldfieldtype": "Data", "oldfieldtype": "Data",
"unique": 1, "reqd": 1,
"reqd": 1 "unique": 1
}, },
{ {
"depends_on": "variant_of", "depends_on": "variant_of",
@ -1029,10 +1031,17 @@
"oldfieldtype": "Currency" "oldfieldtype": "Currency"
}, },
{ {
"depends_on": "eval:!doc.__islocal",
"fieldname": "over_billing_allowance", "fieldname": "over_billing_allowance",
"fieldtype": "Float", "fieldtype": "Float",
"label": "Over Billing Allowance (%)", "label": "Over Billing Allowance (%)"
"depends_on": "eval:!doc.__islocal" },
{
"default": "0",
"depends_on": "is_fixed_asset",
"fieldname": "auto_create_assets",
"fieldtype": "Check",
"label": "Auto Create Assets on Purchase"
} }
], ],
"has_web_view": 1, "has_web_view": 1,
@ -1040,7 +1049,7 @@
"idx": 2, "idx": 2,
"image_field": "image", "image_field": "image",
"max_attachments": 1, "max_attachments": 1,
"modified": "2019-09-03 18:34:13.977931", "modified": "2019-10-09 17:05:59.576119",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Item", "name": "Item",
@ -1102,4 +1111,4 @@
"sort_order": "DESC", "sort_order": "DESC",
"title_field": "item_name", "title_field": "item_name",
"track_changes": 1 "track_changes": 1
} }

View File

@ -645,7 +645,7 @@ class Item(WebsiteGenerator):
json.dumps(item_wise_tax_detail), update_modified=False) json.dumps(item_wise_tax_detail), update_modified=False)
def set_last_purchase_rate(self, new_name): def set_last_purchase_rate(self, new_name):
last_purchase_rate = get_last_purchase_details(new_name).get("base_rate", 0) last_purchase_rate = get_last_purchase_details(new_name).get("base_net_rate", 0)
frappe.db.set_value("Item", new_name, "last_purchase_rate", last_purchase_rate) frappe.db.set_value("Item", new_name, "last_purchase_rate", last_purchase_rate)
def recalculate_bin_qty(self, new_name): def recalculate_bin_qty(self, new_name):
@ -942,7 +942,7 @@ def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0):
last_purchase_order = frappe.db.sql("""\ last_purchase_order = frappe.db.sql("""\
select po.name, po.transaction_date, po.conversion_rate, select po.name, po.transaction_date, po.conversion_rate,
po_item.conversion_factor, po_item.base_price_list_rate, po_item.conversion_factor, po_item.base_price_list_rate,
po_item.discount_percentage, po_item.base_rate po_item.discount_percentage, po_item.base_rate, po_item.base_net_rate
from `tabPurchase Order` po, `tabPurchase Order Item` po_item from `tabPurchase Order` po, `tabPurchase Order Item` po_item
where po.docstatus = 1 and po_item.item_code = %s and po.name != %s and where po.docstatus = 1 and po_item.item_code = %s and po.name != %s and
po.name = po_item.parent po.name = po_item.parent
@ -953,7 +953,7 @@ def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0):
last_purchase_receipt = frappe.db.sql("""\ last_purchase_receipt = frappe.db.sql("""\
select pr.name, pr.posting_date, pr.posting_time, pr.conversion_rate, select pr.name, pr.posting_date, pr.posting_time, pr.conversion_rate,
pr_item.conversion_factor, pr_item.base_price_list_rate, pr_item.discount_percentage, pr_item.conversion_factor, pr_item.base_price_list_rate, pr_item.discount_percentage,
pr_item.base_rate pr_item.base_rate, pr_item.base_net_rate
from `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pr_item from `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pr_item
where pr.docstatus = 1 and pr_item.item_code = %s and pr.name != %s and where pr.docstatus = 1 and pr_item.item_code = %s and pr.name != %s and
pr.name = pr_item.parent pr.name = pr_item.parent
@ -984,6 +984,7 @@ def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0):
out = frappe._dict({ out = frappe._dict({
"base_price_list_rate": flt(last_purchase.base_price_list_rate) / conversion_factor, "base_price_list_rate": flt(last_purchase.base_price_list_rate) / conversion_factor,
"base_rate": flt(last_purchase.base_rate) / conversion_factor, "base_rate": flt(last_purchase.base_rate) / conversion_factor,
"base_net_rate": flt(last_purchase.net_rate) / conversion_factor,
"discount_percentage": flt(last_purchase.discount_percentage), "discount_percentage": flt(last_purchase.discount_percentage),
"purchase_date": purchase_date "purchase_date": purchase_date
}) })
@ -992,7 +993,8 @@ def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0):
out.update({ out.update({
"price_list_rate": out.base_price_list_rate / conversion_rate, "price_list_rate": out.base_price_list_rate / conversion_rate,
"rate": out.base_rate / conversion_rate, "rate": out.base_rate / conversion_rate,
"base_rate": out.base_rate "base_rate": out.base_rate,
"base_net_rate": out.base_net_rate
}) })
return out return out

View File

@ -13,6 +13,7 @@
"qty", "qty",
"rate", "rate",
"amount", "amount",
"is_fixed_asset",
"applicable_charges", "applicable_charges",
"purchase_receipt_item", "purchase_receipt_item",
"accounting_dimensions_section", "accounting_dimensions_section",
@ -119,14 +120,25 @@
{ {
"fieldname": "dimension_col_break", "fieldname": "dimension_col_break",
"fieldtype": "Column 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, "idx": 1,
"istable": 1, "istable": 1,
"modified": "2019-05-26 09:48:15.569956", "modified": "2019-11-12 15:41:21.053462",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Landed Cost Item", "name": "Landed Cost Item",
"owner": "wasim@webnotestech.com", "owner": "wasim@webnotestech.com",
"permissions": [] "permissions": [],
"sort_field": "modified",
"sort_order": "DESC"
} }

View File

@ -129,6 +129,10 @@ erpnext.stock.LandedCostVoucher = erpnext.stock.StockController.extend({
}, },
distribute_charges_based_on: function (frm) { distribute_charges_based_on: function (frm) {
this.set_applicable_charges_for_item(); this.set_applicable_charges_for_item();
},
items_remove: () => {
this.trigger('set_applicable_charges_for_item');
} }
}); });

View File

@ -1,516 +1,132 @@
{ {
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "naming_series:", "autoname": "naming_series:",
"beta": 0,
"creation": "2014-07-11 11:33:42.547339", "creation": "2014-07-11 11:33:42.547339",
"custom": 0,
"docstatus": 0,
"doctype": "DocType", "doctype": "DocType",
"document_type": "Document", "document_type": "Document",
"editable_grid": 0,
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [
"naming_series",
"company",
"purchase_receipts",
"purchase_receipt_items",
"get_items_from_purchase_receipts",
"items",
"sec_break1",
"taxes",
"section_break_9",
"total_taxes_and_charges",
"col_break1",
"distribute_charges_based_on",
"amended_from",
"sec_break2",
"landed_cost_help"
],
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "",
"fieldname": "naming_series", "fieldname": "naming_series",
"fieldtype": "Select", "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", "label": "Series",
"length": 0,
"no_copy": 1, "no_copy": 1,
"options": "MAT-LCV-.YYYY.-", "options": "MAT-LCV-.YYYY.-",
"permlevel": 0,
"precision": "",
"print_hide": 1, "print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1, "reqd": 1,
"search_index": 0, "set_only_once": 1
"set_only_once": 1,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "company", "fieldname": "company",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Company", "label": "Company",
"length": 0,
"no_copy": 0,
"options": "Company", "options": "Company",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 1, "remember_last_selected_value": 1,
"report_hide": 0, "reqd": 1
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "purchase_receipts", "fieldname": "purchase_receipts",
"fieldtype": "Table", "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", "label": "Purchase Receipts",
"length": 0,
"no_copy": 0,
"options": "Landed Cost Purchase Receipt", "options": "Landed Cost Purchase Receipt",
"permlevel": 0, "reqd": 1
"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
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "purchase_receipt_items", "fieldname": "purchase_receipt_items",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 0, "label": "Purchase Receipt Items"
"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
}, },
{ {
"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", "fieldname": "get_items_from_purchase_receipts",
"fieldtype": "Button", "fieldtype": "Button",
"hidden": 0, "label": "Get Items From Purchase Receipts"
"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
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "items", "fieldname": "items",
"fieldtype": "Table", "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", "label": "Purchase Receipt Items",
"length": 0,
"no_copy": 1, "no_copy": 1,
"options": "Landed Cost Item", "options": "Landed Cost Item",
"permlevel": 0, "reqd": 1
"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
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "sec_break1", "fieldname": "sec_break1",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 0, "label": "Applicable Charges"
"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
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "taxes", "fieldname": "taxes",
"fieldtype": "Table", "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", "label": "Taxes and Charges",
"length": 0,
"no_copy": 0,
"options": "Landed Cost Taxes and Charges", "options": "Landed Cost Taxes and Charges",
"permlevel": 0, "reqd": 1
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_9", "fieldname": "section_break_9",
"fieldtype": "Section Break", "fieldtype": "Section Break"
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "total_taxes_and_charges", "fieldname": "total_taxes_and_charges",
"fieldtype": "Currency", "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", "label": "Total Taxes and Charges",
"length": 0,
"no_copy": 0,
"options": "Company:company:default_currency", "options": "Company:company:default_currency",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1, "read_only": 1,
"remember_last_selected_value": 0, "reqd": 1
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "col_break1", "fieldname": "col_break1",
"fieldtype": "Column Break", "fieldtype": "Column Break"
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "",
"fieldname": "distribute_charges_based_on", "fieldname": "distribute_charges_based_on",
"fieldtype": "Select", "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", "label": "Distribute Charges Based On",
"length": 0, "options": "Qty\nAmount",
"no_copy": 0, "reqd": 1
"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
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "amended_from", "fieldname": "amended_from",
"fieldtype": "Link", "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", "label": "Amended From",
"length": 0,
"no_copy": 1, "no_copy": 1,
"options": "Landed Cost Voucher", "options": "Landed Cost Voucher",
"permlevel": 0,
"print_hide": 1, "print_hide": 1,
"print_hide_if_no_value": 0, "read_only": 1
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "sec_break2", "fieldname": "sec_break2",
"fieldtype": "Section Break", "fieldtype": "Section Break"
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "landed_cost_help", "fieldname": "landed_cost_help",
"fieldtype": "HTML", "fieldtype": "HTML",
"hidden": 0, "label": "Landed Cost Help"
"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
} }
], ],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "icon-usd", "icon": "icon-usd",
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 1, "is_submittable": 1,
"issingle": 0, "modified": "2019-11-21 15:34:10.846093",
"istable": 0,
"max_attachments": 0,
"modified": "2018-08-21 14:44:30.850736",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Landed Cost Voucher", "name": "Landed Cost Voucher",
"name_case": "",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
@ -518,28 +134,16 @@
"cancel": 1, "cancel": 1,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 0,
"export": 1, "export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 0,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "Stock Manager", "role": "Stock Manager",
"set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 1, "submit": 1,
"write": 1 "write": 1
} }
], ],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 1, "show_name_in_global_search": 1,
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC"
"track_changes": 0,
"track_seen": 0,
"track_views": 0
} }

View File

@ -16,16 +16,13 @@ class LandedCostVoucher(Document):
if pr.receipt_document_type and pr.receipt_document: if pr.receipt_document_type and pr.receipt_document:
pr_items = frappe.db.sql("""select pr_item.item_code, pr_item.description, 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.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 from `tab{doctype} Item` pr_item where parent = %s
and exists(select name from tabItem and exists(select name from tabItem
where name = pr_item.item_code and (is_stock_item = 1 or is_fixed_asset=1)) 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) """.format(doctype=pr.receipt_document_type), pr.receipt_document, as_dict=True)
for d in pr_items: for d in pr_items:
if d.asset and frappe.db.get_value("Asset", d.asset, 'docstatus') == 1:
continue
item = self.append("items") item = self.append("items")
item.item_code = d.item_code item.item_code = d.item_code
item.description = d.description item.description = d.description
@ -37,15 +34,16 @@ class LandedCostVoucher(Document):
item.receipt_document_type = pr.receipt_document_type item.receipt_document_type = pr.receipt_document_type
item.receipt_document = pr.receipt_document item.receipt_document = pr.receipt_document
item.purchase_receipt_item = d.name item.purchase_receipt_item = d.name
item.is_fixed_asset = d.is_fixed_asset
def validate(self): def validate(self):
self.check_mandatory() self.check_mandatory()
self.validate_purchase_receipts()
self.set_total_taxes_and_charges()
if not self.get("items"): if not self.get("items"):
self.get_items_from_purchase_receipts() self.get_items_from_purchase_receipts()
else: else:
self.validate_applicable_charges_for_item() self.validate_applicable_charges_for_item()
self.validate_purchase_receipts()
self.set_total_taxes_and_charges()
def check_mandatory(self): def check_mandatory(self):
if not self.get("purchase_receipts"): if not self.get("purchase_receipts"):
@ -64,6 +62,7 @@ class LandedCostVoucher(Document):
for item in self.get("items"): for item in self.get("items"):
if not item.receipt_document: if not item.receipt_document:
frappe.throw(_("Item must be added using 'Get Items from Purchase Receipts' button")) frappe.throw(_("Item must be added using 'Get Items from Purchase Receipts' button"))
elif item.receipt_document not in receipt_documents: elif item.receipt_document not in receipt_documents:
frappe.throw(_("Item Row {0}: {1} {2} does not exist in above '{1}' table") frappe.throw(_("Item Row {0}: {1} {2} does not exist in above '{1}' table")
.format(item.idx, item.receipt_document_type, item.receipt_document)) .format(item.idx, item.receipt_document_type, item.receipt_document))
@ -96,8 +95,6 @@ class LandedCostVoucher(Document):
else: else:
frappe.throw(_("Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges")) frappe.throw(_("Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges"))
def on_submit(self): def on_submit(self):
self.update_landed_cost() self.update_landed_cost()
@ -108,6 +105,9 @@ class LandedCostVoucher(Document):
for d in self.get("purchase_receipts"): for d in self.get("purchase_receipts"):
doc = frappe.get_doc(d.receipt_document_type, d.receipt_document) 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 # set landed cost voucher amount in pr item
doc.set_landed_cost_voucher_amount() doc.set_landed_cost_voucher_amount()
@ -118,23 +118,41 @@ class LandedCostVoucher(Document):
for item in doc.get("items"): for item in doc.get("items"):
item.db_update() item.db_update()
# asset rate will be updated while creating asset gl entries from PI or PY
# update latest valuation rate in serial no # 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 # update stock & gl entries for cancelled state of PR
doc.docstatus = 2 doc.docstatus = 2
doc.update_stock_ledger(allow_negative_stock=True, via_landed_cost_voucher=True) doc.update_stock_ledger(allow_negative_stock=True, via_landed_cost_voucher=True)
doc.make_gl_entries_on_cancel(repost_future_gle=False) doc.make_gl_entries_on_cancel(repost_future_gle=False)
# update stock & gl entries for submit state of PR # update stock & gl entries for submit state of PR
doc.docstatus = 1 doc.docstatus = 1
doc.update_stock_ledger(allow_negative_stock=True, via_landed_cost_voucher=True) doc.update_stock_ledger(allow_negative_stock=True, via_landed_cost_voucher=True)
doc.make_gl_entries() 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,
'item_code': item.item_code }, 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} <b>{0}</b> has submitted Assets.\
Remove Item <b>{1}</b> 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"): 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) serial_nos = get_serial_nos(item.serial_no)
if serial_nos: if serial_nos:
frappe.db.sql("update `tabSerial No` set purchase_rate=%s where name in ({0})" frappe.db.sql("update `tabSerial No` set purchase_rate=%s where name in ({0})"

View File

@ -54,9 +54,10 @@ class TestLandedCostVoucher(unittest.TestCase):
expected_values = { expected_values = {
stock_in_hand_account: [800.0, 0.0], stock_in_hand_account: [800.0, 0.0],
"Stock Received But Not Billed - TCP1": [0.0, 500.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: else:
expected_values = { expected_values = {
stock_in_hand_account: [400.0, 0.0], stock_in_hand_account: [400.0, 0.0],

View File

@ -16,6 +16,7 @@ class PriceList(Document):
def on_update(self): def on_update(self):
self.set_default_if_missing() self.set_default_if_missing()
self.update_item_price() self.update_item_price()
self.delete_price_list_details_key()
def set_default_if_missing(self): def set_default_if_missing(self):
if cint(self.selling): if cint(self.selling):
@ -32,6 +33,8 @@ class PriceList(Document):
(self.currency, cint(self.buying), cint(self.selling), self.name)) (self.currency, cint(self.buying), cint(self.selling), self.name))
def on_trash(self): def on_trash(self):
self.delete_price_list_details_key()
def _update_default_price_list(module): def _update_default_price_list(module):
b = frappe.get_doc(module + " Settings") b = frappe.get_doc(module + " Settings")
price_list_fieldname = module.lower() + "_price_list" price_list_fieldname = module.lower() + "_price_list"
@ -43,3 +46,20 @@ class PriceList(Document):
for module in ["Selling", "Buying"]: for module in ["Selling", "Buying"]:
_update_default_price_list(module) _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 {}

View File

@ -6,24 +6,32 @@
frappe.provide("erpnext.stock"); frappe.provide("erpnext.stock");
frappe.ui.form.on("Purchase Receipt", { 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 = { frm.custom_make_buttons = {
'Stock Entry': 'Return', 'Stock Entry': 'Return',
'Purchase Invoice': 'Invoice' 'Purchase Invoice': 'Invoice'
}; };
frm.set_query("asset", "items", function() {
return {
filters: {
"purchase_receipt": frm.doc.name
}
}
});
frm.set_query("expense_account", "items", function() { frm.set_query("expense_account", "items", function() {
return { return {
query: "erpnext.controllers.queries.get_expense_account", query: "erpnext.controllers.queries.get_expense_account",
filters: {'company': frm.doc.company} filters: {'company': frm.doc.company }
} }
}); });
@ -57,7 +65,7 @@ frappe.ui.form.on("Purchase Receipt", {
toggle_display_account_head: function(frm) { toggle_display_account_head: function(frm) {
var enabled = erpnext.is_perpetual_inventory_enabled(frm.doc.company) var enabled = erpnext.is_perpetual_inventory_enabled(frm.doc.company)
frm.fields_dict["items"].grid.set_column_disp(["cost_center"], enabled); frm.fields_dict["items"].grid.set_column_disp(["cost_center"], enabled);
}, }
}); });
erpnext.stock.PurchaseReceiptController = erpnext.buying.BuyingController.extend({ erpnext.stock.PurchaseReceiptController = erpnext.buying.BuyingController.extend({

View File

@ -82,12 +82,22 @@ class PurchaseReceipt(BuyingController):
self.validate_with_previous_doc() self.validate_with_previous_doc()
self.validate_uom_is_integer("uom", ["qty", "received_qty"]) self.validate_uom_is_integer("uom", ["qty", "received_qty"])
self.validate_uom_is_integer("stock_uom", "stock_qty") self.validate_uom_is_integer("stock_uom", "stock_qty")
self.validate_cwip_accounts()
self.check_on_hold_or_closed_status() self.check_on_hold_or_closed_status()
if getdate(self.posting_date) > getdate(nowdate()): if getdate(self.posting_date) > getdate(nowdate()):
throw(_("Posting Date cannot be future date")) throw(_("Posting Date cannot be future date"))
def validate_cwip_accounts(self):
for item in self.get('items'):
if item.is_fixed_asset and is_cwip_accounting_enabled(item.asset_category):
# check cwip accounts before making auto assets
# Improves UX by not giving messages of "Assets Created" before throwing error of not finding arbnb account
arbnb_account = self.get_company_default("asset_received_but_not_billed")
cwip_account = get_asset_account("capital_work_in_progress_account", company = self.company)
break
def validate_with_previous_doc(self): def validate_with_previous_doc(self):
super(PurchaseReceipt, self).validate_with_previous_doc({ super(PurchaseReceipt, self).validate_with_previous_doc({
"Purchase Order": { "Purchase Order": {
@ -281,15 +291,15 @@ class PurchaseReceipt(BuyingController):
d.rejected_warehouse not in warehouse_with_no_account: d.rejected_warehouse not in warehouse_with_no_account:
warehouse_with_no_account.append(d.warehouse) 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 # Cost center-wise amount breakup for other charges included for valuation
valuation_tax = {} valuation_tax = {}
for tax in self.get("taxes"): for tax in self.get("taxes"):
if tax.category in ("Valuation", "Valuation and Total") and flt(tax.base_tax_amount_after_discount_amount): if tax.category in ("Valuation", "Valuation and Total") and flt(tax.base_tax_amount_after_discount_amount):
if not tax.cost_center: 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))) 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.setdefault(tax.name, 0)
valuation_tax[tax.cost_center] += \ valuation_tax[tax.name] += \
(tax.add_deduct_tax == "Add" and 1 or -1) * flt(tax.base_tax_amount_after_discount_amount) (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: if negative_expense_to_be_booked and valuation_tax:
@ -297,34 +307,39 @@ class PurchaseReceipt(BuyingController):
# If expenses_included_in_valuation account has been credited in against PI # If expenses_included_in_valuation account has been credited in against PI
# and charges added via Landed Cost Voucher, # and charges added via Landed Cost Voucher,
# post valuation related charges on "Stock Received But Not Billed" # 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 negative_expense_booked_in_pi = frappe.db.sql("""select name from `tabPurchase Invoice Item` pi
where docstatus = 1 and purchase_receipt=%s where docstatus = 1 and purchase_receipt=%s
and exists(select name from `tabGL Entry` where voucher_type='Purchase Invoice' 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)) 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]) against_account = ", ".join([d.account for d in gl_entries if flt(d.debit) > 0])
total_valuation_amount = sum(valuation_tax.values()) total_valuation_amount = sum(valuation_tax.values())
amount_including_divisional_loss = negative_expense_to_be_booked amount_including_divisional_loss = negative_expense_to_be_booked
i = 1 i = 1
for cost_center, amount in iteritems(valuation_tax): for tax in self.get("taxes"):
if valuation_tax.get(tax.name):
if negative_expense_booked_in_pi:
account = stock_rbnb
else:
account = tax.account_head
if i == len(valuation_tax): if i == len(valuation_tax):
applicable_amount = amount_including_divisional_loss applicable_amount = amount_including_divisional_loss
else: else:
applicable_amount = negative_expense_to_be_booked * (amount / total_valuation_amount) applicable_amount = negative_expense_to_be_booked * (valuation_tax[tax.name] / total_valuation_amount)
amount_including_divisional_loss -= applicable_amount amount_including_divisional_loss -= applicable_amount
gl_entries.append( gl_entries.append(
self.get_gl_dict({ self.get_gl_dict({
"account": expenses_included_in_valuation, "account": account,
"cost_center": cost_center, "cost_center": tax.cost_center,
"credit": applicable_amount, "credit": applicable_amount,
"remarks": self.remarks or _("Accounting Entry for Stock"), "remarks": self.remarks or _("Accounting Entry for Stock"),
"against": against_account "against": against_account
}) }, item=tax)
) )
i += 1 i += 1
@ -335,81 +350,85 @@ class PurchaseReceipt(BuyingController):
return process_gl_map(gl_entries) return process_gl_map(gl_entries)
def get_asset_gl_entry(self, gl_entries, expenses_included_in_valuation=None): def get_asset_gl_entry(self, gl_entries):
arbnb_account, cwip_account = None, None for item in self.get("items"):
if item.is_fixed_asset:
if is_cwip_accounting_enabled(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
if not expenses_included_in_valuation: def add_asset_gl_entries(self, item, gl_entries):
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") 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)
# CWIP entry asset_amount = flt(item.net_amount) + flt(item.item_tax_amount/self.conversion_rate)
cwip_account = get_asset_account("capital_work_in_progress_account", d.asset, base_asset_amount = flt(item.base_net_amount + item.item_tax_amount)
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) cwip_account_currency = get_account_currency(cwip_account)
# debit cwip account
gl_entries.append(self.get_gl_dict({ gl_entries.append(self.get_gl_dict({
"account": cwip_account, "account": cwip_account,
"against": arbnb_account, "against": arbnb_account,
"cost_center": d.cost_center, "cost_center": item.cost_center,
"remarks": self.get("remarks") or _("Accounting Entry for Asset"), "remarks": self.get("remarks") or _("Accounting Entry for Asset"),
"debit": base_asset_amount, "debit": base_asset_amount,
"debit_in_account_currency": (base_asset_amount "debit_in_account_currency": (base_asset_amount
if cwip_account_currency == self.company_currency else asset_amount) if cwip_account_currency == self.company_currency else asset_amount)
}, item=d)) }, item=item))
# Asset received but not billed
asset_rbnb_currency = get_account_currency(arbnb_account) asset_rbnb_currency = get_account_currency(arbnb_account)
# credit arbnb account
gl_entries.append(self.get_gl_dict({ gl_entries.append(self.get_gl_dict({
"account": arbnb_account, "account": arbnb_account,
"against": cwip_account, "against": cwip_account,
"cost_center": d.cost_center, "cost_center": item.cost_center,
"remarks": self.get("remarks") or _("Accounting Entry for Asset"), "remarks": self.get("remarks") or _("Accounting Entry for Asset"),
"credit": base_asset_amount, "credit": base_asset_amount,
"credit_in_account_currency": (base_asset_amount "credit_in_account_currency": (base_asset_amount
if asset_rbnb_currency == self.company_currency else asset_amount) if asset_rbnb_currency == self.company_currency else asset_amount)
}, item=d)) }, item=item))
if d.is_fixed_asset and flt(d.landed_cost_voucher_amount): def add_lcv_gl_entries(self, item, gl_entries):
asset_account = (get_asset_category_account(d.asset, 'fixed_asset_account', expenses_included_in_asset_valuation = self.get_company_default("expenses_included_in_asset_valuation")
company = self.company) if not cwip_enabled else cwip_account) if not is_cwip_accounting_enabled(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({ gl_entries.append(self.get_gl_dict({
"account": expenses_included_in_valuation, "account": expenses_included_in_asset_valuation,
"against": asset_account, "against": asset_account,
"cost_center": d.cost_center, "cost_center": item.cost_center,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"), "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"credit": flt(d.landed_cost_voucher_amount), "credit": flt(item.landed_cost_voucher_amount),
"project": d.project "project": item.project
}, item=d)) }, item=item))
gl_entries.append(self.get_gl_dict({ gl_entries.append(self.get_gl_dict({
"account": asset_account, "account": asset_account,
"against": expenses_included_in_valuation, "against": expenses_included_in_asset_valuation,
"cost_center": d.cost_center, "cost_center": item.cost_center,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"), "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"debit": flt(d.landed_cost_voucher_amount), "debit": flt(item.landed_cost_voucher_amount),
"project": d.project "project": item.project
}, item=d)) }, item=item))
if d.asset: def update_assets(self, item, valuation_rate):
doc = frappe.get_doc("Asset", d.asset) assets = frappe.db.get_all('Asset',
frappe.db.set_value("Asset", d.asset, "gross_purchase_amount", filters={ 'purchase_receipt': self.name, 'item_code': item.item_code }
doc.gross_purchase_amount + flt(d.landed_cost_voucher_amount)) )
frappe.db.set_value("Asset", d.asset, "purchase_receipt_amount", for asset in assets:
doc.purchase_receipt_amount + flt(d.landed_cost_voucher_amount)) 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))
return gl_entries
def update_status(self, status): def update_status(self, status):
self.set_status(update=True, status = status) self.set_status(update=True, status = status)
@ -517,7 +536,8 @@ def make_purchase_invoice(source_name, target_doc=None):
"purchase_order_item": "po_detail", "purchase_order_item": "po_detail",
"purchase_order": "purchase_order", "purchase_order": "purchase_order",
"is_fixed_asset": "is_fixed_asset", "is_fixed_asset": "is_fixed_asset",
"asset": "asset", "asset_location": "asset_location",
"asset_category": 'asset_category'
}, },
"postprocess": update_item, "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 "filter": lambda d: get_pending_qty(d)[0] <= 0 if not doc.get("is_return") else get_pending_qty(d)[0] > 0

View File

@ -66,14 +66,15 @@ class TestPurchaseReceipt(unittest.TestCase):
expected_values = { expected_values = {
stock_in_hand_account: [750.0, 0.0], stock_in_hand_account: [750.0, 0.0],
"Stock Received But Not Billed - TCP1": [0.0, 500.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: else:
expected_values = { expected_values = {
stock_in_hand_account: [375.0, 0.0], stock_in_hand_account: [375.0, 0.0],
fixed_asset_account: [375.0, 0.0], fixed_asset_account: [375.0, 0.0],
"Stock Received But Not Billed - TCP1": [0.0, 500.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: for gle in gl_entries:
self.assertEqual(expected_values[gle.account][0], gle.debit) 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) serial_no=serial_no, basic_rate=100, do_not_submit=True)
self.assertRaises(SerialNoDuplicateError, se.submit) self.assertRaises(SerialNoDuplicateError, se.submit)
def test_serialized_asset_item(self): def test_auto_asset_creation(self):
asset_item = "Test Serialized Asset Item" asset_item = "Test Asset Item"
if not frappe.db.exists('Item', asset_item): if not frappe.db.exists('Item', asset_item):
asset_category = frappe.get_all('Asset Category') asset_category = frappe.get_all('Asset Category')
@ -308,30 +309,18 @@ class TestPurchaseReceipt(unittest.TestCase):
asset_category = doc.name asset_category = doc.name
item_data = make_item(asset_item, {'is_stock_item':0, item_data = make_item(asset_item, {'is_stock_item':0,
'stock_uom': 'Box', 'is_fixed_asset': 1, 'has_serial_no': 1, 'stock_uom': 'Box', 'is_fixed_asset': 1, 'auto_create_assets': 1,
'asset_category': asset_category, 'serial_no_series': 'ABC.###'}) 'asset_category': asset_category, 'asset_naming_series': 'ABC.###'})
asset_item = item_data.item_code asset_item = item_data.item_code
pr = make_purchase_receipt(item_code=asset_item, qty=3) pr = make_purchase_receipt(item_code=asset_item, qty=3)
asset = frappe.db.get_value('Asset', {'purchase_receipt': pr.name}, 'name') assets = frappe.db.get_all('Asset', filters={'purchase_receipt': pr.name})
asset_movement = frappe.db.get_value('Asset Movement', {'reference_name': pr.name}, 'name')
serial_nos = frappe.get_all('Serial No', {'asset': asset}, '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") 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): 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 from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings') 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 received_qty = args.received_qty or qty
rejected_qty = args.rejected_qty or flt(received_qty) - flt(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", { 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", "warehouse": args.warehouse or "_Test Warehouse - _TC",
"qty": qty, "qty": qty,
"received_qty": received_qty, "received_qty": received_qty,
@ -545,7 +536,7 @@ def make_purchase_receipt(**args):
"conversion_factor": args.conversion_factor or 1.0, "conversion_factor": args.conversion_factor or 1.0,
"serial_no": args.serial_no, "serial_no": args.serial_no,
"stock_uom": args.stock_uom or "_Test UOM", "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'), "cost_center": args.cost_center or frappe.get_cached_value('Company', pr.company, 'cost_center'),
"asset_location": args.location or "Test Location" "asset_location": args.location or "Test Location"
}) })

View File

@ -67,26 +67,26 @@
"warehouse_and_reference", "warehouse_and_reference",
"warehouse", "warehouse",
"rejected_warehouse", "rejected_warehouse",
"quality_inspection",
"purchase_order", "purchase_order",
"material_request", "material_request",
"purchase_order_item",
"material_request_item",
"column_break_40", "column_break_40",
"is_fixed_asset", "is_fixed_asset",
"asset",
"asset_location", "asset_location",
"asset_category",
"schedule_date", "schedule_date",
"quality_inspection",
"stock_qty", "stock_qty",
"purchase_order_item",
"material_request_item",
"section_break_45", "section_break_45",
"allow_zero_valuation_rate",
"bom",
"col_break5",
"serial_no", "serial_no",
"batch_no", "batch_no",
"column_break_48", "column_break_48",
"rejected_serial_no", "rejected_serial_no",
"expense_account", "expense_account",
"col_break5",
"allow_zero_valuation_rate",
"bom",
"include_exploded_items", "include_exploded_items",
"item_tax_rate", "item_tax_rate",
"accounting_dimensions_section", "accounting_dimensions_section",
@ -500,21 +500,6 @@
"print_hide": 1, "print_hide": 1,
"read_only": 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", "fieldname": "purchase_order",
"fieldtype": "Link", "fieldtype": "Link",
@ -553,6 +538,7 @@
"fieldtype": "Section Break" "fieldtype": "Section Break"
}, },
{ {
"depends_on": "eval:!doc.is_fixed_asset",
"fieldname": "serial_no", "fieldname": "serial_no",
"fieldtype": "Small Text", "fieldtype": "Small Text",
"in_list_view": 1, "in_list_view": 1,
@ -562,10 +548,11 @@
"oldfieldtype": "Text" "oldfieldtype": "Text"
}, },
{ {
"depends_on": "eval:!doc.is_fixed_asset",
"fieldname": "batch_no", "fieldname": "batch_no",
"fieldtype": "Link", "fieldtype": "Link",
"in_list_view": 1, "in_list_view": 1,
"label": "Batch No", "label": "Batch No!",
"no_copy": 1, "no_copy": 1,
"oldfieldname": "batch_no", "oldfieldname": "batch_no",
"oldfieldtype": "Link", "oldfieldtype": "Link",
@ -577,6 +564,7 @@
"fieldtype": "Column Break" "fieldtype": "Column Break"
}, },
{ {
"depends_on": "eval:!doc.is_fixed_asset",
"fieldname": "rejected_serial_no", "fieldname": "rejected_serial_no",
"fieldtype": "Small Text", "fieldtype": "Small Text",
"label": "Rejected Serial No", "label": "Rejected Serial No",
@ -814,11 +802,28 @@
"fieldtype": "Data", "fieldtype": "Data",
"label": "Manufacturer Part Number", "label": "Manufacturer Part Number",
"read_only": 1 "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, "idx": 1,
"istable": 1, "istable": 1,
"modified": "2019-09-17 22:33:01.109004", "modified": "2019-10-14 16:03:25.499557",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Purchase Receipt Item", "name": "Purchase Receipt Item",

View File

@ -52,9 +52,10 @@ class StockReconciliation(StockController):
def _changed(item): def _changed(item):
item_dict = get_stock_balance_for(item.item_code, item.warehouse, item_dict = get_stock_balance_for(item.item_code, item.warehouse,
self.posting_date, self.posting_time, batch_no=item.batch_no) self.posting_date, self.posting_time, batch_no=item.batch_no)
if (((item.qty is None or item.qty==item_dict.get("qty")) and
(item.valuation_rate is None or item.valuation_rate==item_dict.get("rate")) and not item.serial_no) if ((item.qty is None or item.qty==item_dict.get("qty")) and
or (item.serial_no and item.serial_no == item_dict.get("serial_nos"))): (item.valuation_rate is None or item.valuation_rate==item_dict.get("rate")) and
(not item.serial_no or (item.serial_no == item_dict.get("serial_nos")) )):
return False return False
else: else:
# set default as current rates # set default as current rates
@ -182,9 +183,11 @@ class StockReconciliation(StockController):
from erpnext.stock.stock_ledger import get_previous_sle from erpnext.stock.stock_ledger import get_previous_sle
sl_entries = [] sl_entries = []
has_serial_no = False
for row in self.items: for row in self.items:
item = frappe.get_doc("Item", row.item_code) item = frappe.get_doc("Item", row.item_code)
if item.has_serial_no or item.has_batch_no: if item.has_serial_no or item.has_batch_no:
has_serial_no = True
self.get_sle_for_serialized_items(row, sl_entries) self.get_sle_for_serialized_items(row, sl_entries)
else: else:
previous_sle = get_previous_sle({ previous_sle = get_previous_sle({
@ -212,8 +215,14 @@ class StockReconciliation(StockController):
sl_entries.append(self.get_sle_for_items(row)) sl_entries.append(self.get_sle_for_items(row))
if sl_entries: if sl_entries:
if has_serial_no:
sl_entries = self.merge_similar_item_serial_nos(sl_entries)
self.make_sl_entries(sl_entries) self.make_sl_entries(sl_entries)
if has_serial_no and sl_entries:
self.update_valuation_rate_for_serial_no()
def get_sle_for_serialized_items(self, row, sl_entries): def get_sle_for_serialized_items(self, row, sl_entries):
from erpnext.stock.stock_ledger import get_previous_sle from erpnext.stock.stock_ledger import get_previous_sle
@ -275,8 +284,18 @@ class StockReconciliation(StockController):
# update valuation rate # update valuation rate
self.update_valuation_rate_for_serial_nos(row, serial_nos) self.update_valuation_rate_for_serial_nos(row, serial_nos)
def update_valuation_rate_for_serial_no(self):
for d in self.items:
if not d.serial_no: continue
serial_nos = get_serial_nos(d.serial_no)
self.update_valuation_rate_for_serial_nos(d, serial_nos)
def update_valuation_rate_for_serial_nos(self, row, serial_nos): def update_valuation_rate_for_serial_nos(self, row, serial_nos):
valuation_rate = row.valuation_rate if self.docstatus == 1 else row.current_valuation_rate valuation_rate = row.valuation_rate if self.docstatus == 1 else row.current_valuation_rate
if valuation_rate is None:
return
for d in serial_nos: for d in serial_nos:
frappe.db.set_value("Serial No", d, 'purchase_rate', valuation_rate) frappe.db.set_value("Serial No", d, 'purchase_rate', valuation_rate)
@ -321,11 +340,17 @@ class StockReconciliation(StockController):
where voucher_type=%s and voucher_no=%s""", (self.doctype, self.name)) where voucher_type=%s and voucher_no=%s""", (self.doctype, self.name))
sl_entries = [] sl_entries = []
has_serial_no = False
for row in self.items: for row in self.items:
if row.serial_no or row.batch_no or row.current_serial_no: if row.serial_no or row.batch_no or row.current_serial_no:
has_serial_no = True
self.get_sle_for_serialized_items(row, sl_entries) self.get_sle_for_serialized_items(row, sl_entries)
if sl_entries: if sl_entries:
if has_serial_no:
sl_entries = self.merge_similar_item_serial_nos(sl_entries)
sl_entries.reverse() sl_entries.reverse()
allow_negative_stock = frappe.db.get_value("Stock Settings", None, "allow_negative_stock") allow_negative_stock = frappe.db.get_value("Stock Settings", None, "allow_negative_stock")
self.make_sl_entries(sl_entries, allow_negative_stock=allow_negative_stock) self.make_sl_entries(sl_entries, allow_negative_stock=allow_negative_stock)
@ -339,6 +364,35 @@ class StockReconciliation(StockController):
"posting_time": self.posting_time "posting_time": self.posting_time
}) })
def merge_similar_item_serial_nos(self, sl_entries):
# If user has put the same item in multiple row with different serial no
new_sl_entries = []
merge_similar_entries = {}
for d in sl_entries:
if not d.serial_no or d.actual_qty < 0:
new_sl_entries.append(d)
continue
key = (d.item_code, d.warehouse)
if key not in merge_similar_entries:
merge_similar_entries[key] = d
elif d.serial_no:
data = merge_similar_entries[key]
data.actual_qty += d.actual_qty
data.qty_after_transaction += d.qty_after_transaction
data.valuation_rate = (data.valuation_rate + d.valuation_rate) / data.actual_qty
data.serial_no += '\n' + d.serial_no
if data.incoming_rate:
data.incoming_rate = (data.incoming_rate + d.incoming_rate) / data.actual_qty
for key, value in merge_similar_entries.items():
new_sl_entries.append(value)
return new_sl_entries
def get_gl_entries(self, warehouse_account=None): def get_gl_entries(self, warehouse_account=None):
if not self.cost_center: if not self.cost_center:
msgprint(_("Please enter Cost Center"), raise_exception=1) msgprint(_("Please enter Cost Center"), raise_exception=1)
@ -456,7 +510,7 @@ def get_qty_rate_for_serial_nos(item_code, warehouse, posting_date, posting_time
} }
serial_nos_list = [serial_no.get("name") serial_nos_list = [serial_no.get("name")
for serial_no in get_available_serial_nos(item_code, warehouse)] for serial_no in get_available_serial_nos(args)]
qty = len(serial_nos_list) qty = len(serial_nos_list)
serial_nos = '\n'.join(serial_nos_list) serial_nos = '\n'.join(serial_nos_list)

View File

@ -12,6 +12,7 @@ from frappe.model.meta import get_field_precision
from erpnext.stock.doctype.batch.batch import get_batch_no from erpnext.stock.doctype.batch.batch import get_batch_no
from erpnext import get_company_currency from erpnext import get_company_currency
from erpnext.stock.doctype.item.item import get_item_defaults, get_uom_conv_factor 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.item_group.item_group import get_item_group_defaults
from erpnext.setup.doctype.brand.brand import get_brand_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 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'] purchase_doctypes = ['Material Request', 'Supplier Quotation', 'Purchase Order', 'Purchase Receipt', 'Purchase Invoice']
@frappe.whitelist() @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 = { args = {
"item_code": "", "item_code": "",
@ -74,7 +75,9 @@ def get_item_details(args, doc=None, overwrite_warehouse=True):
if args.get(key) is None: if args.get(key) is None:
args[key] = value 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) out.update(data)
update_stock(args, out) 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'): if meta.get_field("currency") or args.get('currency'):
pl_details = get_price_list_currency_and_exchange_rate(args) pl_details = get_price_list_currency_and_exchange_rate(args)
args.update(pl_details) args.update(pl_details)
validate_price_list(args)
if meta.get_field("currency"): if meta.get_field("currency"):
validate_conversion_rate(args, meta) validate_conversion_rate(args, meta)
@ -634,14 +636,6 @@ def check_packing_list(price_list_rate_name, desired_qty, item_code):
return flag 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): def validate_conversion_rate(args, meta):
from erpnext.controllers.accounts_controller import validate_conversion_rate from erpnext.controllers.accounts_controller import validate_conversion_rate
@ -905,27 +899,6 @@ def apply_price_list_on_item(args):
return item_details 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): def get_price_list_currency_and_exchange_rate(args):
if not args.price_list: if not args.price_list:
return {} return {}
@ -935,8 +908,11 @@ def get_price_list_currency_and_exchange_rate(args):
elif args.doctype in ['Purchase Order', 'Purchase Receipt', 'Purchase Invoice']: elif args.doctype in ['Purchase Order', 'Purchase Receipt', 'Purchase Invoice']:
args.update({"exchange_rate": "for_buying"}) args.update({"exchange_rate": "for_buying"})
price_list_currency = get_price_list_currency(args.price_list) price_list_details = get_price_list_details(args.price_list)
price_list_uom_dependant = get_price_list_uom_dependant(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 plc_conversion_rate = args.plc_conversion_rate
company_currency = get_company_currency(args.company) company_currency = get_company_currency(args.company)

View File

@ -292,7 +292,7 @@ def validate_filters(filters):
if not (filters.get("item_code") or filters.get("warehouse")): if not (filters.get("item_code") or filters.get("warehouse")):
sle_count = flt(frappe.db.sql("""select count(name) from `tabStock Ledger Entry`""")[0][0]) sle_count = flt(frappe.db.sql("""select count(name) from `tabStock Ledger Entry`""")[0][0])
if sle_count > 500000: if sle_count > 500000:
frappe.throw(_("Please set filter based on Item or Warehouse")) frappe.throw(_("Please set filter based on Item or Warehouse due to a large amount of entries."))
def get_variants_attributes(): def get_variants_attributes():
'''Return all item variant attributes.''' '''Return all item variant attributes.'''

View File

@ -293,9 +293,11 @@ def update_included_uom_in_report(columns, result, include_uom, conversion_facto
row, key, value = data row, key, value = data
row[key] = value row[key] = value
def get_available_serial_nos(item_code, warehouse): def get_available_serial_nos(args):
return frappe.get_all("Serial No", filters = {'item_code': item_code, return frappe.db.sql(""" SELECT name from `tabSerial No`
'warehouse': warehouse, 'delivery_document_no': ''}) or [] WHERE item_code = %(item_code)s and warehouse = %(warehouse)s
and timestamp(purchase_date, purchase_time) <= timestamp(%(posting_date)s, %(posting_time)s)
""", args, as_dict=1)
def add_additional_uom_columns(columns, result, include_uom, conversion_factors): def add_additional_uom_columns(columns, result, include_uom, conversion_factors):
if not include_uom or not conversion_factors: if not include_uom or not conversion_factors: