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);
@ -434,18 +434,6 @@ cur_frm.set_query("expense_account", "items", function(doc) {
} }
}); });
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'
}
}
});
cur_frm.cscript.expense_account = function(doc, cdt, cdn){ cur_frm.cscript.expense_account = function(doc, cdt, cdn){
var d = locals[cdt][cdn]; var d = locals[cdt][cdn];
if(d.idx == 1 && d.expense_account){ if(d.idx == 1 && d.expense_account){

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,
args:{
"assets": [{ name: cur_frm.doc.name }],
"purpose": "Transfer"
},
callback: function (r) { callback: function (r) {
cur_frm.reload_doc(); 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,7 +19,9 @@ 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:
@ -28,6 +30,9 @@ def get_approvers(doctype, txt, searchfield, start, page_len, filters):
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"
else: else:
@ -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

@ -8,15 +8,10 @@ def execute():
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

@ -50,6 +50,11 @@ frappe.ui.form.on("Item", {
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",

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,20 +6,28 @@
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",
@ -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: