mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-29 09:54:47 +00:00
Merge branch 'develop' into setup_wizard
This commit is contained in:
@@ -5,7 +5,7 @@ import frappe
|
|||||||
from erpnext.hooks import regional_overrides
|
from erpnext.hooks import regional_overrides
|
||||||
from frappe.utils import getdate
|
from frappe.utils import getdate
|
||||||
|
|
||||||
__version__ = '12.1.8'
|
__version__ = '12.2.0'
|
||||||
|
|
||||||
def get_default_company(user=None):
|
def get_default_company(user=None):
|
||||||
'''Get default company for user'''
|
'''Get default company for user'''
|
||||||
|
|||||||
@@ -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')
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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 = ''
|
||||||
|
|||||||
@@ -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,17 +341,19 @@ 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:
|
|
||||||
if d == row.get(frappe.scrub(pr_doc.apply_on)):
|
|
||||||
pricing_rules = filter_pricing_rules_for_qty_amount(row.get("stock_qty"),
|
|
||||||
row.get("amount"), pricing_rules, row)
|
|
||||||
|
|
||||||
if pricing_rules and pricing_rules[0]:
|
for row in doc.items:
|
||||||
return pricing_rules
|
if row.get(frappe.scrub(pr_doc.apply_rule_on_other)) in items:
|
||||||
|
pricing_rules = filter_pricing_rules_for_qty_amount(row.get("stock_qty"),
|
||||||
|
row.get("amount"), pricing_rules, row)
|
||||||
|
|
||||||
|
if pricing_rules and pricing_rules[0]:
|
||||||
|
pricing_rules[0].apply_rule_on_other_items = items
|
||||||
|
return pricing_rules
|
||||||
|
|
||||||
def get_qty_amount_data_for_cumulative(pr_doc, doc, items=[]):
|
def get_qty_amount_data_for_cumulative(pr_doc, doc, items=[]):
|
||||||
sum_qty, sum_amt = [0, 0]
|
sum_qty, sum_amt = [0, 0]
|
||||||
@@ -397,31 +401,7 @@ def get_qty_amount_data_for_cumulative(pr_doc, doc, items=[]):
|
|||||||
|
|
||||||
return [sum_qty, sum_amt]
|
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
|
||||||
|
|||||||
@@ -331,15 +331,15 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
asset: function(frm, cdt, cdn) {
|
item_code: function(frm, cdt, cdn) {
|
||||||
var row = locals[cdt][cdn];
|
var row = locals[cdt][cdn];
|
||||||
if(row.asset) {
|
if(row.item_code) {
|
||||||
frappe.call({
|
frappe.call({
|
||||||
method: "erpnext.assets.doctype.asset_category.asset_category.get_asset_category_account",
|
method: "erpnext.assets.doctype.asset_category.asset_category.get_asset_category_account",
|
||||||
args: {
|
args: {
|
||||||
"asset": row.asset,
|
"item": row.item_code,
|
||||||
"fieldname": "fixed_asset_account",
|
"fieldname": "fixed_asset_account",
|
||||||
"account": row.expense_account
|
"company": frm.doc.company
|
||||||
},
|
},
|
||||||
callback: function(r, rt) {
|
callback: function(r, rt) {
|
||||||
frappe.model.set_value(cdt, cdn, "expense_account", r.message);
|
frappe.model.set_value(cdt, cdn, "expense_account", r.message);
|
||||||
@@ -430,19 +430,7 @@ cur_frm.fields_dict['select_print_heading'].get_query = function(doc, cdt, cdn)
|
|||||||
cur_frm.set_query("expense_account", "items", function(doc) {
|
cur_frm.set_query("expense_account", "items", function(doc) {
|
||||||
return {
|
return {
|
||||||
query: "erpnext.controllers.queries.get_expense_account",
|
query: "erpnext.controllers.queries.get_expense_account",
|
||||||
filters: {'company': doc.company}
|
filters: {'company': doc.company }
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
cur_frm.set_query("asset", "items", function(doc, cdt, cdn) {
|
|
||||||
var d = locals[cdt][cdn];
|
|
||||||
return {
|
|
||||||
filters: {
|
|
||||||
'item_code': d.item_code,
|
|
||||||
'docstatus': 1,
|
|
||||||
'company': doc.company,
|
|
||||||
'status': 'Submitted'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ from erpnext.accounts.general_ledger import make_gl_entries, merge_similar_entri
|
|||||||
from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt
|
from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt
|
||||||
from erpnext.buying.utils import check_on_hold_or_closed_status
|
from erpnext.buying.utils import check_on_hold_or_closed_status
|
||||||
from erpnext.accounts.general_ledger import get_round_off_account_and_cost_center
|
from erpnext.accounts.general_ledger import get_round_off_account_and_cost_center
|
||||||
from erpnext.assets.doctype.asset.asset import get_asset_account, is_cwip_accounting_disabled
|
from erpnext.assets.doctype.asset.asset import get_asset_account, is_cwip_accounting_enabled
|
||||||
from frappe.model.mapper import get_mapped_doc
|
from frappe.model.mapper import get_mapped_doc
|
||||||
from six import iteritems
|
from six import iteritems
|
||||||
from erpnext.accounts.doctype.sales_invoice.sales_invoice import validate_inter_company_party, update_linked_doc,\
|
from erpnext.accounts.doctype.sales_invoice.sales_invoice import validate_inter_company_party, update_linked_doc,\
|
||||||
@@ -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()
|
||||||
@@ -226,6 +225,8 @@ class PurchaseInvoice(BuyingController):
|
|||||||
# in case of auto inventory accounting,
|
# in case of auto inventory accounting,
|
||||||
# expense account is always "Stock Received But Not Billed" for a stock item
|
# expense account is always "Stock Received But Not Billed" for a stock item
|
||||||
# except epening entry, drop-ship entry and fixed asset items
|
# except epening entry, drop-ship entry and fixed asset items
|
||||||
|
if item.item_code:
|
||||||
|
asset_category = frappe.get_cached_value("Item", item.item_code, "asset_category")
|
||||||
|
|
||||||
if auto_accounting_for_stock and item.item_code in stock_items \
|
if auto_accounting_for_stock and item.item_code in stock_items \
|
||||||
and self.is_opening == 'No' and not item.is_fixed_asset \
|
and self.is_opening == 'No' and not item.is_fixed_asset \
|
||||||
@@ -236,12 +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 is_cwip_accounting_disabled():
|
elif item.is_fixed_asset and not is_cwip_accounting_enabled(asset_category):
|
||||||
if not item.asset:
|
item.expense_account = get_asset_category_account('fixed_asset_account', item=item.item_code,
|
||||||
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
|
||||||
@@ -392,7 +389,8 @@ class PurchaseInvoice(BuyingController):
|
|||||||
|
|
||||||
self.make_supplier_gl_entry(gl_entries)
|
self.make_supplier_gl_entry(gl_entries)
|
||||||
self.make_item_gl_entries(gl_entries)
|
self.make_item_gl_entries(gl_entries)
|
||||||
if not is_cwip_accounting_disabled():
|
|
||||||
|
if self.check_asset_cwip_enabled():
|
||||||
self.get_asset_gl_entry(gl_entries)
|
self.get_asset_gl_entry(gl_entries)
|
||||||
|
|
||||||
self.make_tax_gl_entries(gl_entries)
|
self.make_tax_gl_entries(gl_entries)
|
||||||
@@ -405,6 +403,15 @@ class PurchaseInvoice(BuyingController):
|
|||||||
|
|
||||||
return gl_entries
|
return gl_entries
|
||||||
|
|
||||||
|
def check_asset_cwip_enabled(self):
|
||||||
|
# Check if there exists any item with cwip accounting enabled in it's asset category
|
||||||
|
for item in self.get("items"):
|
||||||
|
if item.item_code and item.is_fixed_asset:
|
||||||
|
asset_category = frappe.get_cached_value("Item", item.item_code, "asset_category")
|
||||||
|
if is_cwip_accounting_enabled(asset_category):
|
||||||
|
return 1
|
||||||
|
return 0
|
||||||
|
|
||||||
def make_supplier_gl_entry(self, gl_entries):
|
def make_supplier_gl_entry(self, gl_entries):
|
||||||
# Checked both rounding_adjustment and rounded_total
|
# Checked both rounding_adjustment and rounded_total
|
||||||
# because rounded_total had value even before introcution of posting GLE based on rounded total
|
# because rounded_total had value even before introcution of posting GLE based on rounded total
|
||||||
@@ -445,9 +452,15 @@ 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)
|
||||||
|
if item.item_code:
|
||||||
|
asset_category = frappe.get_cached_value("Item", item.item_code, "asset_category")
|
||||||
|
|
||||||
if self.update_stock and self.auto_accounting_for_stock and item.item_code in stock_items:
|
if self.update_stock and self.auto_accounting_for_stock and item.item_code in stock_items:
|
||||||
# warehouse account
|
# warehouse account
|
||||||
@@ -490,31 +503,61 @@ class PurchaseInvoice(BuyingController):
|
|||||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||||
"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 is_cwip_accounting_disabled()):
|
|
||||||
|
|
||||||
|
elif not item.is_fixed_asset or (item.is_fixed_asset and not is_cwip_accounting_enabled(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(
|
||||||
@@ -531,27 +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):
|
||||||
|
arbnb_account = self.get_company_default("asset_received_but_not_billed")
|
||||||
|
eiiav_account = self.get_company_default("expenses_included_in_asset_valuation")
|
||||||
|
|
||||||
for item in self.get("items"):
|
for item in self.get("items"):
|
||||||
if item.is_fixed_asset:
|
if item.is_fixed_asset:
|
||||||
eiiav_account = self.get_company_default("expenses_included_in_asset_valuation")
|
|
||||||
|
|
||||||
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))
|
||||||
|
|
||||||
@@ -568,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({
|
||||||
@@ -595,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):
|
||||||
@@ -645,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:
|
||||||
@@ -662,36 +734,38 @@ 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 i == len(valuation_tax):
|
if valuation_tax.get(tax.name):
|
||||||
applicable_amount = amount_including_divisional_loss
|
if i == len(valuation_tax):
|
||||||
else:
|
applicable_amount = amount_including_divisional_loss
|
||||||
applicable_amount = self.negative_expense_to_be_booked * (amount / total_valuation_amount)
|
else:
|
||||||
amount_including_divisional_loss -= applicable_amount
|
applicable_amount = self.negative_expense_to_be_booked * (valuation_tax[tax.name] / total_valuation_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"):
|
||||||
gl_entries.append(
|
if valuation_tax.get(tax.name):
|
||||||
self.get_gl_dict({
|
gl_entries.append(
|
||||||
"account": self.expenses_included_in_valuation,
|
self.get_gl_dict({
|
||||||
"cost_center": cost_center,
|
"account": tax.account_head,
|
||||||
"against": self.supplier,
|
"cost_center": tax.cost_center,
|
||||||
"credit": amount,
|
"against": self.supplier,
|
||||||
"remarks": self.remarks or "Accounting Entry for Stock"
|
"credit": valuation_tax[tax.name],
|
||||||
})
|
"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):
|
||||||
# Make Cash GL Entries
|
# Make Cash GL Entries
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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'),
|
||||||
|
|||||||
@@ -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}"
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ from frappe.utils import nowdate
|
|||||||
class ShareDontExists(ValidationError): pass
|
class ShareDontExists(ValidationError): pass
|
||||||
|
|
||||||
class ShareTransfer(Document):
|
class ShareTransfer(Document):
|
||||||
def before_submit(self):
|
def on_submit(self):
|
||||||
if self.transfer_type == 'Issue':
|
if self.transfer_type == 'Issue':
|
||||||
shareholder = self.get_shareholder_doc(self.company)
|
shareholder = self.get_company_shareholder()
|
||||||
shareholder.append('share_balance', {
|
shareholder.append('share_balance', {
|
||||||
'share_type': self.share_type,
|
'share_type': self.share_type,
|
||||||
'from_no': self.from_no,
|
'from_no': self.from_no,
|
||||||
@@ -28,7 +28,7 @@ class ShareTransfer(Document):
|
|||||||
})
|
})
|
||||||
shareholder.save()
|
shareholder.save()
|
||||||
|
|
||||||
doc = frappe.get_doc('Shareholder', self.to_shareholder)
|
doc = self.get_shareholder_doc(self.to_shareholder)
|
||||||
doc.append('share_balance', {
|
doc.append('share_balance', {
|
||||||
'share_type': self.share_type,
|
'share_type': self.share_type,
|
||||||
'from_no': self.from_no,
|
'from_no': self.from_no,
|
||||||
@@ -41,11 +41,11 @@ class ShareTransfer(Document):
|
|||||||
|
|
||||||
elif self.transfer_type == 'Purchase':
|
elif self.transfer_type == 'Purchase':
|
||||||
self.remove_shares(self.from_shareholder)
|
self.remove_shares(self.from_shareholder)
|
||||||
self.remove_shares(self.get_shareholder_doc(self.company).name)
|
self.remove_shares(self.get_company_shareholder().name)
|
||||||
|
|
||||||
elif self.transfer_type == 'Transfer':
|
elif self.transfer_type == 'Transfer':
|
||||||
self.remove_shares(self.from_shareholder)
|
self.remove_shares(self.from_shareholder)
|
||||||
doc = frappe.get_doc('Shareholder', self.to_shareholder)
|
doc = self.get_shareholder_doc(self.to_shareholder)
|
||||||
doc.append('share_balance', {
|
doc.append('share_balance', {
|
||||||
'share_type': self.share_type,
|
'share_type': self.share_type,
|
||||||
'from_no': self.from_no,
|
'from_no': self.from_no,
|
||||||
@@ -56,143 +56,127 @@ class ShareTransfer(Document):
|
|||||||
})
|
})
|
||||||
doc.save()
|
doc.save()
|
||||||
|
|
||||||
|
def on_cancel(self):
|
||||||
|
if self.transfer_type == 'Issue':
|
||||||
|
compnay_shareholder = self.get_company_shareholder()
|
||||||
|
self.remove_shares(compnay_shareholder.name)
|
||||||
|
self.remove_shares(self.to_shareholder)
|
||||||
|
|
||||||
|
elif self.transfer_type == 'Purchase':
|
||||||
|
compnay_shareholder = self.get_company_shareholder()
|
||||||
|
from_shareholder = self.get_shareholder_doc(self.from_shareholder)
|
||||||
|
|
||||||
|
from_shareholder.append('share_balance', {
|
||||||
|
'share_type': self.share_type,
|
||||||
|
'from_no': self.from_no,
|
||||||
|
'to_no': self.to_no,
|
||||||
|
'rate': self.rate,
|
||||||
|
'amount': self.amount,
|
||||||
|
'no_of_shares': self.no_of_shares
|
||||||
|
})
|
||||||
|
|
||||||
|
from_shareholder.save()
|
||||||
|
|
||||||
|
compnay_shareholder.append('share_balance', {
|
||||||
|
'share_type': self.share_type,
|
||||||
|
'from_no': self.from_no,
|
||||||
|
'to_no': self.to_no,
|
||||||
|
'rate': self.rate,
|
||||||
|
'amount': self.amount,
|
||||||
|
'no_of_shares': self.no_of_shares
|
||||||
|
})
|
||||||
|
|
||||||
|
compnay_shareholder.save()
|
||||||
|
|
||||||
|
elif self.transfer_type == 'Transfer':
|
||||||
|
self.remove_shares(self.to_shareholder)
|
||||||
|
from_shareholder = self.get_shareholder_doc(self.from_shareholder)
|
||||||
|
from_shareholder.append('share_balance', {
|
||||||
|
'share_type': self.share_type,
|
||||||
|
'from_no': self.from_no,
|
||||||
|
'to_no': self.to_no,
|
||||||
|
'rate': self.rate,
|
||||||
|
'amount': self.amount,
|
||||||
|
'no_of_shares': self.no_of_shares
|
||||||
|
})
|
||||||
|
from_shareholder.save()
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
|
self.get_company_shareholder()
|
||||||
self.basic_validations()
|
self.basic_validations()
|
||||||
self.folio_no_validation()
|
self.folio_no_validation()
|
||||||
|
|
||||||
if self.transfer_type == 'Issue':
|
if self.transfer_type == 'Issue':
|
||||||
if not self.get_shareholder_doc(self.company):
|
# validate share doesn't exist in company
|
||||||
shareholder = frappe.get_doc({
|
ret_val = self.share_exists(self.get_company_shareholder().name)
|
||||||
'doctype': 'Shareholder',
|
if ret_val in ('Complete', 'Partial'):
|
||||||
'title': self.company,
|
|
||||||
'company': self.company,
|
|
||||||
'is_company': 1
|
|
||||||
})
|
|
||||||
shareholder.insert()
|
|
||||||
# validate share doesnt exist in company
|
|
||||||
ret_val = self.share_exists(self.get_shareholder_doc(self.company).name)
|
|
||||||
if ret_val != False:
|
|
||||||
frappe.throw(_('The shares already exist'), frappe.DuplicateEntryError)
|
frappe.throw(_('The shares already exist'), frappe.DuplicateEntryError)
|
||||||
else:
|
else:
|
||||||
# validate share exists with from_shareholder
|
# validate share exists with from_shareholder
|
||||||
ret_val = self.share_exists(self.from_shareholder)
|
ret_val = self.share_exists(self.from_shareholder)
|
||||||
if ret_val != True:
|
if ret_val in ('Outside', 'Partial'):
|
||||||
frappe.throw(_("The shares don't exist with the {0}")
|
frappe.throw(_("The shares don't exist with the {0}")
|
||||||
.format(self.from_shareholder), ShareDontExists)
|
.format(self.from_shareholder), ShareDontExists)
|
||||||
|
|
||||||
def basic_validations(self):
|
def basic_validations(self):
|
||||||
if self.transfer_type == 'Purchase':
|
if self.transfer_type == 'Purchase':
|
||||||
self.to_shareholder = ''
|
self.to_shareholder = ''
|
||||||
if self.from_shareholder is None or self.from_shareholder is '':
|
if not self.from_shareholder:
|
||||||
frappe.throw(_('The field From Shareholder cannot be blank'))
|
frappe.throw(_('The field From Shareholder cannot be blank'))
|
||||||
if self.from_folio_no is None or self.from_folio_no is '':
|
if not self.from_folio_no:
|
||||||
self.to_folio_no = self.autoname_folio(self.to_shareholder)
|
self.to_folio_no = self.autoname_folio(self.to_shareholder)
|
||||||
if self.asset_account is None:
|
if not self.asset_account:
|
||||||
frappe.throw(_('The field Asset Account cannot be blank'))
|
frappe.throw(_('The field Asset Account cannot be blank'))
|
||||||
elif (self.transfer_type == 'Issue'):
|
elif (self.transfer_type == 'Issue'):
|
||||||
self.from_shareholder = ''
|
self.from_shareholder = ''
|
||||||
if self.to_shareholder is None or self.to_shareholder == '':
|
if not self.to_shareholder:
|
||||||
frappe.throw(_('The field To Shareholder cannot be blank'))
|
frappe.throw(_('The field To Shareholder cannot be blank'))
|
||||||
if self.to_folio_no is None or self.to_folio_no is '':
|
if not self.to_folio_no:
|
||||||
self.to_folio_no = self.autoname_folio(self.to_shareholder)
|
self.to_folio_no = self.autoname_folio(self.to_shareholder)
|
||||||
if self.asset_account is None:
|
if not self.asset_account:
|
||||||
frappe.throw(_('The field Asset Account cannot be blank'))
|
frappe.throw(_('The field Asset Account cannot be blank'))
|
||||||
else:
|
else:
|
||||||
if self.from_shareholder is None or self.to_shareholder is None:
|
if not self.from_shareholder or not self.to_shareholder:
|
||||||
frappe.throw(_('The fields From Shareholder and To Shareholder cannot be blank'))
|
frappe.throw(_('The fields From Shareholder and To Shareholder cannot be blank'))
|
||||||
if self.to_folio_no is None or self.to_folio_no is '':
|
if not self.to_folio_no:
|
||||||
self.to_folio_no = self.autoname_folio(self.to_shareholder)
|
self.to_folio_no = self.autoname_folio(self.to_shareholder)
|
||||||
if self.equity_or_liability_account is None:
|
if not self.equity_or_liability_account:
|
||||||
frappe.throw(_('The field Equity/Liability Account cannot be blank'))
|
frappe.throw(_('The field Equity/Liability Account cannot be blank'))
|
||||||
if self.from_shareholder == self.to_shareholder:
|
if self.from_shareholder == self.to_shareholder:
|
||||||
frappe.throw(_('The seller and the buyer cannot be the same'))
|
frappe.throw(_('The seller and the buyer cannot be the same'))
|
||||||
if self.no_of_shares != self.to_no - self.from_no + 1:
|
if self.no_of_shares != self.to_no - self.from_no + 1:
|
||||||
frappe.throw(_('The number of shares and the share numbers are inconsistent'))
|
frappe.throw(_('The number of shares and the share numbers are inconsistent'))
|
||||||
if self.amount is None:
|
if not self.amount:
|
||||||
self.amount = self.rate * self.no_of_shares
|
self.amount = self.rate * self.no_of_shares
|
||||||
if self.amount != self.rate * self.no_of_shares:
|
if self.amount != self.rate * self.no_of_shares:
|
||||||
frappe.throw(_('There are inconsistencies between the rate, no of shares and the amount calculated'))
|
frappe.throw(_('There are inconsistencies between the rate, no of shares and the amount calculated'))
|
||||||
|
|
||||||
def share_exists(self, shareholder):
|
def share_exists(self, shareholder):
|
||||||
# return True if exits,
|
doc = self.get_shareholder_doc(shareholder)
|
||||||
# False if completely doesn't exist,
|
|
||||||
# 'partially exists' if partailly doesn't exist
|
|
||||||
ret_val = self.recursive_share_check(shareholder, self.share_type,
|
|
||||||
query = {
|
|
||||||
'from_no': self.from_no,
|
|
||||||
'to_no': self.to_no
|
|
||||||
}
|
|
||||||
)
|
|
||||||
if all(boolean == True for boolean in ret_val):
|
|
||||||
return True
|
|
||||||
elif True in ret_val:
|
|
||||||
return 'partially exists'
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def recursive_share_check(self, shareholder, share_type, query):
|
|
||||||
# query = {'from_no': share_starting_no, 'to_no': share_ending_no}
|
|
||||||
# Recursive check if a given part of shares is held by the shareholder
|
|
||||||
# return a list containing True and False
|
|
||||||
# Eg. [True, False, True]
|
|
||||||
# All True implies its completely inside
|
|
||||||
# All False implies its completely outside
|
|
||||||
# A mix implies its partially inside/outside
|
|
||||||
does_share_exist = []
|
|
||||||
doc = frappe.get_doc('Shareholder', shareholder)
|
|
||||||
for entry in doc.share_balance:
|
for entry in doc.share_balance:
|
||||||
if entry.share_type != share_type or \
|
if entry.share_type != self.share_type or \
|
||||||
entry.from_no > query['to_no'] or \
|
entry.from_no > self.to_no or \
|
||||||
entry.to_no < query['from_no']:
|
entry.to_no < self.from_no:
|
||||||
continue # since query lies outside bounds
|
continue # since query lies outside bounds
|
||||||
elif entry.from_no <= query['from_no'] and entry.to_no >= query['to_no']:
|
elif entry.from_no <= self.from_no and entry.to_no >= self.to_no: #both inside
|
||||||
return [True] # absolute truth!
|
return 'Complete' # absolute truth!
|
||||||
elif entry.from_no >= query['from_no'] and entry.to_no <= query['to_no']:
|
elif entry.from_no <= self.from_no <= self.to_no:
|
||||||
# split and check
|
return 'Partial'
|
||||||
does_share_exist.extend(self.recursive_share_check(shareholder,
|
elif entry.from_no <= self.to_no <= entry.to_no:
|
||||||
share_type,
|
return 'Partial'
|
||||||
{
|
|
||||||
'from_no': query['from_no'],
|
|
||||||
'to_no': entry.from_no - 1
|
|
||||||
}
|
|
||||||
))
|
|
||||||
does_share_exist.append(True)
|
|
||||||
does_share_exist.extend(self.recursive_share_check(shareholder,
|
|
||||||
share_type,
|
|
||||||
{
|
|
||||||
'from_no': entry.to_no + 1,
|
|
||||||
'to_no': query['to_no']
|
|
||||||
}
|
|
||||||
))
|
|
||||||
elif query['from_no'] <= entry.from_no <= query['to_no'] and entry.to_no >= query['to_no']:
|
|
||||||
does_share_exist.extend(self.recursive_share_check(shareholder,
|
|
||||||
share_type,
|
|
||||||
{
|
|
||||||
'from_no': query['from_no'],
|
|
||||||
'to_no': entry.from_no - 1
|
|
||||||
}
|
|
||||||
))
|
|
||||||
elif query['from_no'] <= entry.to_no <= query['to_no'] and entry.from_no <= query['from_no']:
|
|
||||||
does_share_exist.extend(self.recursive_share_check(shareholder,
|
|
||||||
share_type,
|
|
||||||
{
|
|
||||||
'from_no': entry.to_no + 1,
|
|
||||||
'to_no': query['to_no']
|
|
||||||
}
|
|
||||||
))
|
|
||||||
|
|
||||||
does_share_exist.append(False)
|
return 'Outside'
|
||||||
return does_share_exist
|
|
||||||
|
|
||||||
def folio_no_validation(self):
|
def folio_no_validation(self):
|
||||||
shareholders = ['from_shareholder', 'to_shareholder']
|
shareholders = ['from_shareholder', 'to_shareholder']
|
||||||
shareholders = [shareholder for shareholder in shareholders if self.get(shareholder) is not '']
|
shareholders = [shareholder for shareholder in shareholders if self.get(shareholder) is not '']
|
||||||
for shareholder in shareholders:
|
for shareholder in shareholders:
|
||||||
doc = frappe.get_doc('Shareholder', self.get(shareholder))
|
doc = self.get_shareholder_doc(self.get(shareholder))
|
||||||
if doc.company != self.company:
|
if doc.company != self.company:
|
||||||
frappe.throw(_('The shareholder does not belong to this company'))
|
frappe.throw(_('The shareholder does not belong to this company'))
|
||||||
if doc.folio_no is '' or doc.folio_no is None:
|
if not doc.folio_no:
|
||||||
doc.folio_no = self.from_folio_no \
|
doc.folio_no = self.from_folio_no \
|
||||||
if (shareholder == 'from_shareholder') else self.to_folio_no;
|
if (shareholder == 'from_shareholder') else self.to_folio_no
|
||||||
doc.save()
|
doc.save()
|
||||||
else:
|
else:
|
||||||
if doc.folio_no and doc.folio_no != (self.from_folio_no if (shareholder == 'from_shareholder') else self.to_folio_no):
|
if doc.folio_no and doc.folio_no != (self.from_folio_no if (shareholder == 'from_shareholder') else self.to_folio_no):
|
||||||
@@ -200,24 +184,14 @@ class ShareTransfer(Document):
|
|||||||
|
|
||||||
def autoname_folio(self, shareholder, is_company=False):
|
def autoname_folio(self, shareholder, is_company=False):
|
||||||
if is_company:
|
if is_company:
|
||||||
doc = self.get_shareholder_doc(shareholder)
|
doc = self.get_company_shareholder()
|
||||||
else:
|
else:
|
||||||
doc = frappe.get_doc('Shareholder' , shareholder)
|
doc = self.get_shareholder_doc(shareholder)
|
||||||
doc.folio_no = make_autoname('FN.#####')
|
doc.folio_no = make_autoname('FN.#####')
|
||||||
doc.save()
|
doc.save()
|
||||||
return doc.folio_no
|
return doc.folio_no
|
||||||
|
|
||||||
def remove_shares(self, shareholder):
|
def remove_shares(self, shareholder):
|
||||||
self.iterative_share_removal(shareholder, self.share_type,
|
|
||||||
{
|
|
||||||
'from_no': self.from_no,
|
|
||||||
'to_no' : self.to_no
|
|
||||||
},
|
|
||||||
rate = self.rate,
|
|
||||||
amount = self.amount
|
|
||||||
)
|
|
||||||
|
|
||||||
def iterative_share_removal(self, shareholder, share_type, query, rate, amount):
|
|
||||||
# query = {'from_no': share_starting_no, 'to_no': share_ending_no}
|
# query = {'from_no': share_starting_no, 'to_no': share_ending_no}
|
||||||
# Shares exist for sure
|
# Shares exist for sure
|
||||||
# Iterate over all entries and modify entry if in entry
|
# Iterate over all entries and modify entry if in entry
|
||||||
@@ -227,31 +201,31 @@ class ShareTransfer(Document):
|
|||||||
|
|
||||||
for entry in current_entries:
|
for entry in current_entries:
|
||||||
# use spaceage logic here
|
# use spaceage logic here
|
||||||
if entry.share_type != share_type or \
|
if entry.share_type != self.share_type or \
|
||||||
entry.from_no > query['to_no'] or \
|
entry.from_no > self.to_no or \
|
||||||
entry.to_no < query['from_no']:
|
entry.to_no < self.from_no:
|
||||||
new_entries.append(entry)
|
new_entries.append(entry)
|
||||||
continue # since query lies outside bounds
|
continue # since query lies outside bounds
|
||||||
elif entry.from_no <= query['from_no'] and entry.to_no >= query['to_no']:
|
elif entry.from_no <= self.from_no and entry.to_no >= self.to_no:
|
||||||
#split
|
#split
|
||||||
if entry.from_no == query['from_no']:
|
if entry.from_no == self.from_no:
|
||||||
if entry.to_no == query['to_no']:
|
if entry.to_no == self.to_no:
|
||||||
pass #nothing to append
|
pass #nothing to append
|
||||||
else:
|
else:
|
||||||
new_entries.append(self.return_share_balance_entry(query['to_no']+1, entry.to_no, entry.rate))
|
new_entries.append(self.return_share_balance_entry(self.to_no+1, entry.to_no, entry.rate))
|
||||||
else:
|
else:
|
||||||
if entry.to_no == query['to_no']:
|
if entry.to_no == self.to_no:
|
||||||
new_entries.append(self.return_share_balance_entry(entry.from_no, query['from_no']-1, entry.rate))
|
new_entries.append(self.return_share_balance_entry(entry.from_no, self.from_no-1, entry.rate))
|
||||||
else:
|
else:
|
||||||
new_entries.append(self.return_share_balance_entry(entry.from_no, query['from_no']-1, entry.rate))
|
new_entries.append(self.return_share_balance_entry(entry.from_no, self.from_no-1, entry.rate))
|
||||||
new_entries.append(self.return_share_balance_entry(query['to_no']+1, entry.to_no, entry.rate))
|
new_entries.append(self.return_share_balance_entry(self.to_no+1, entry.to_no, entry.rate))
|
||||||
elif entry.from_no >= query['from_no'] and entry.to_no <= query['to_no']:
|
elif entry.from_no >= self.from_no and entry.to_no <= self.to_no:
|
||||||
# split and check
|
# split and check
|
||||||
pass #nothing to append
|
pass #nothing to append
|
||||||
elif query['from_no'] <= entry.from_no <= query['to_no'] and entry.to_no >= query['to_no']:
|
elif self.from_no <= entry.from_no <= self.to_no and entry.to_no >= self.to_no:
|
||||||
new_entries.append(self.return_share_balance_entry(query['to_no']+1, entry.to_no, entry.rate))
|
new_entries.append(self.return_share_balance_entry(self.to_no+1, entry.to_no, entry.rate))
|
||||||
elif query['from_no'] <= entry.to_no <= query['to_no'] and entry.from_no <= query['from_no']:
|
elif self.from_no <= entry.to_no <= self.to_no and entry.from_no <= self.from_no:
|
||||||
new_entries.append(self.return_share_balance_entry(entry.from_no, query['from_no']-1, entry.rate))
|
new_entries.append(self.return_share_balance_entry(entry.from_no, self.from_no-1, entry.rate))
|
||||||
else:
|
else:
|
||||||
new_entries.append(entry)
|
new_entries.append(entry)
|
||||||
|
|
||||||
@@ -272,16 +246,34 @@ class ShareTransfer(Document):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def get_shareholder_doc(self, shareholder):
|
def get_shareholder_doc(self, shareholder):
|
||||||
# Get Shareholder doc based on the Shareholder title
|
# Get Shareholder doc based on the Shareholder name
|
||||||
doc = frappe.get_list('Shareholder',
|
if shareholder:
|
||||||
filters = [
|
query_filters = {'name': shareholder}
|
||||||
('Shareholder', 'title', '=', shareholder)
|
|
||||||
]
|
name = frappe.db.get_value('Shareholder', {'name': shareholder}, 'name')
|
||||||
)
|
|
||||||
if len(doc) == 1:
|
return frappe.get_doc('Shareholder', name)
|
||||||
return frappe.get_doc('Shareholder', doc[0]['name'])
|
|
||||||
else: #It will necessarily by 0 indicating it doesn't exist
|
def get_company_shareholder(self):
|
||||||
return False
|
# Get company doc or create one if not present
|
||||||
|
company_shareholder = frappe.db.get_value('Shareholder',
|
||||||
|
{
|
||||||
|
'company': self.company,
|
||||||
|
'is_company': 1
|
||||||
|
}, 'name')
|
||||||
|
|
||||||
|
if company_shareholder:
|
||||||
|
return frappe.get_doc('Shareholder', company_shareholder)
|
||||||
|
else:
|
||||||
|
shareholder = frappe.get_doc({
|
||||||
|
'doctype': 'Shareholder',
|
||||||
|
'title': self.company,
|
||||||
|
'company': self.company,
|
||||||
|
'is_company': 1
|
||||||
|
})
|
||||||
|
shareholder.insert()
|
||||||
|
|
||||||
|
return shareholder
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def make_jv_entry( company, account, amount, payment_account,\
|
def make_jv_entry( company, account, amount, payment_account,\
|
||||||
|
|||||||
@@ -15,73 +15,73 @@ class TestShareTransfer(unittest.TestCase):
|
|||||||
frappe.db.sql("delete from `tabShare Balance`")
|
frappe.db.sql("delete from `tabShare Balance`")
|
||||||
share_transfers = [
|
share_transfers = [
|
||||||
{
|
{
|
||||||
"doctype" : "Share Transfer",
|
"doctype": "Share Transfer",
|
||||||
"transfer_type" : "Issue",
|
"transfer_type": "Issue",
|
||||||
"date" : "2018-01-01",
|
"date": "2018-01-01",
|
||||||
"to_shareholder" : "SH-00001",
|
"to_shareholder": "SH-00001",
|
||||||
"share_type" : "Equity",
|
"share_type": "Equity",
|
||||||
"from_no" : 1,
|
"from_no": 1,
|
||||||
"to_no" : 500,
|
"to_no": 500,
|
||||||
"no_of_shares" : 500,
|
"no_of_shares": 500,
|
||||||
"rate" : 10,
|
"rate": 10,
|
||||||
"company" : "_Test Company",
|
"company": "_Test Company",
|
||||||
"asset_account" : "Cash - _TC",
|
"asset_account": "Cash - _TC",
|
||||||
"equity_or_liability_account": "Creditors - _TC"
|
"equity_or_liability_account": "Creditors - _TC"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"doctype" : "Share Transfer",
|
"doctype": "Share Transfer",
|
||||||
"transfer_type" : "Transfer",
|
"transfer_type": "Transfer",
|
||||||
"date" : "2018-01-02",
|
"date": "2018-01-02",
|
||||||
"from_shareholder" : "SH-00001",
|
"from_shareholder": "SH-00001",
|
||||||
"to_shareholder" : "SH-00002",
|
"to_shareholder": "SH-00002",
|
||||||
"share_type" : "Equity",
|
"share_type": "Equity",
|
||||||
"from_no" : 101,
|
"from_no": 101,
|
||||||
"to_no" : 200,
|
"to_no": 200,
|
||||||
"no_of_shares" : 100,
|
"no_of_shares": 100,
|
||||||
"rate" : 15,
|
"rate": 15,
|
||||||
"company" : "_Test Company",
|
"company": "_Test Company",
|
||||||
"equity_or_liability_account": "Creditors - _TC"
|
"equity_or_liability_account": "Creditors - _TC"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"doctype" : "Share Transfer",
|
"doctype": "Share Transfer",
|
||||||
"transfer_type" : "Transfer",
|
"transfer_type": "Transfer",
|
||||||
"date" : "2018-01-03",
|
"date": "2018-01-03",
|
||||||
"from_shareholder" : "SH-00001",
|
"from_shareholder": "SH-00001",
|
||||||
"to_shareholder" : "SH-00003",
|
"to_shareholder": "SH-00003",
|
||||||
"share_type" : "Equity",
|
"share_type": "Equity",
|
||||||
"from_no" : 201,
|
"from_no": 201,
|
||||||
"to_no" : 500,
|
"to_no": 500,
|
||||||
"no_of_shares" : 300,
|
"no_of_shares": 300,
|
||||||
"rate" : 20,
|
"rate": 20,
|
||||||
"company" : "_Test Company",
|
"company": "_Test Company",
|
||||||
"equity_or_liability_account": "Creditors - _TC"
|
"equity_or_liability_account": "Creditors - _TC"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"doctype" : "Share Transfer",
|
"doctype": "Share Transfer",
|
||||||
"transfer_type" : "Transfer",
|
"transfer_type": "Transfer",
|
||||||
"date" : "2018-01-04",
|
"date": "2018-01-04",
|
||||||
"from_shareholder" : "SH-00003",
|
"from_shareholder": "SH-00003",
|
||||||
"to_shareholder" : "SH-00002",
|
"to_shareholder": "SH-00002",
|
||||||
"share_type" : "Equity",
|
"share_type": "Equity",
|
||||||
"from_no" : 201,
|
"from_no": 201,
|
||||||
"to_no" : 400,
|
"to_no": 400,
|
||||||
"no_of_shares" : 200,
|
"no_of_shares": 200,
|
||||||
"rate" : 15,
|
"rate": 15,
|
||||||
"company" : "_Test Company",
|
"company": "_Test Company",
|
||||||
"equity_or_liability_account": "Creditors - _TC"
|
"equity_or_liability_account": "Creditors - _TC"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"doctype" : "Share Transfer",
|
"doctype": "Share Transfer",
|
||||||
"transfer_type" : "Purchase",
|
"transfer_type": "Purchase",
|
||||||
"date" : "2018-01-05",
|
"date": "2018-01-05",
|
||||||
"from_shareholder" : "SH-00003",
|
"from_shareholder": "SH-00003",
|
||||||
"share_type" : "Equity",
|
"share_type": "Equity",
|
||||||
"from_no" : 401,
|
"from_no": 401,
|
||||||
"to_no" : 500,
|
"to_no": 500,
|
||||||
"no_of_shares" : 100,
|
"no_of_shares": 100,
|
||||||
"rate" : 25,
|
"rate": 25,
|
||||||
"company" : "_Test Company",
|
"company": "_Test Company",
|
||||||
"asset_account" : "Cash - _TC",
|
"asset_account": "Cash - _TC",
|
||||||
"equity_or_liability_account": "Creditors - _TC"
|
"equity_or_liability_account": "Creditors - _TC"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -91,33 +91,33 @@ class TestShareTransfer(unittest.TestCase):
|
|||||||
|
|
||||||
def test_invalid_share_transfer(self):
|
def test_invalid_share_transfer(self):
|
||||||
doc = frappe.get_doc({
|
doc = frappe.get_doc({
|
||||||
"doctype" : "Share Transfer",
|
"doctype": "Share Transfer",
|
||||||
"transfer_type" : "Transfer",
|
"transfer_type": "Transfer",
|
||||||
"date" : "2018-01-05",
|
"date": "2018-01-05",
|
||||||
"from_shareholder" : "SH-00003",
|
"from_shareholder": "SH-00003",
|
||||||
"to_shareholder" : "SH-00002",
|
"to_shareholder": "SH-00002",
|
||||||
"share_type" : "Equity",
|
"share_type": "Equity",
|
||||||
"from_no" : 1,
|
"from_no": 1,
|
||||||
"to_no" : 100,
|
"to_no": 100,
|
||||||
"no_of_shares" : 100,
|
"no_of_shares": 100,
|
||||||
"rate" : 15,
|
"rate": 15,
|
||||||
"company" : "_Test Company",
|
"company": "_Test Company",
|
||||||
"equity_or_liability_account": "Creditors - _TC"
|
"equity_or_liability_account": "Creditors - _TC"
|
||||||
})
|
})
|
||||||
self.assertRaises(ShareDontExists, doc.insert)
|
self.assertRaises(ShareDontExists, doc.insert)
|
||||||
|
|
||||||
doc = frappe.get_doc({
|
doc = frappe.get_doc({
|
||||||
"doctype" : "Share Transfer",
|
"doctype": "Share Transfer",
|
||||||
"transfer_type" : "Purchase",
|
"transfer_type": "Purchase",
|
||||||
"date" : "2018-01-02",
|
"date": "2018-01-02",
|
||||||
"from_shareholder" : "SH-00001",
|
"from_shareholder": "SH-00001",
|
||||||
"share_type" : "Equity",
|
"share_type": "Equity",
|
||||||
"from_no" : 1,
|
"from_no": 1,
|
||||||
"to_no" : 200,
|
"to_no": 200,
|
||||||
"no_of_shares" : 200,
|
"no_of_shares": 200,
|
||||||
"rate" : 15,
|
"rate": 15,
|
||||||
"company" : "_Test Company",
|
"company": "_Test Company",
|
||||||
"asset_account" : "Cash - _TC",
|
"asset_account": "Cash - _TC",
|
||||||
"equity_or_liability_account": "Creditors - _TC"
|
"equity_or_liability_account": "Creditors - _TC"
|
||||||
})
|
})
|
||||||
self.assertRaises(ShareDontExists, doc.insert)
|
self.assertRaises(ShareDontExists, doc.insert)
|
||||||
|
|||||||
@@ -1,587 +1,163 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"autoname": "naming_series:",
|
||||||
"allow_guest_to_view": 0,
|
"creation": "2017-12-25 16:50:53.878430",
|
||||||
"allow_import": 0,
|
"doctype": "DocType",
|
||||||
"allow_rename": 0,
|
"editable_grid": 1,
|
||||||
"autoname": "naming_series:",
|
"engine": "InnoDB",
|
||||||
"beta": 0,
|
"field_order": [
|
||||||
"creation": "2017-12-25 16:50:53.878430",
|
"title",
|
||||||
"custom": 0,
|
"column_break_2",
|
||||||
"description": "",
|
"naming_series",
|
||||||
"docstatus": 0,
|
"section_break_2",
|
||||||
"doctype": "DocType",
|
"folio_no",
|
||||||
"document_type": "",
|
"column_break_4",
|
||||||
"editable_grid": 1,
|
"company",
|
||||||
"engine": "InnoDB",
|
"is_company",
|
||||||
|
"address_contacts",
|
||||||
|
"address_html",
|
||||||
|
"column_break_9",
|
||||||
|
"contact_html",
|
||||||
|
"section_break_3",
|
||||||
|
"share_balance",
|
||||||
|
"contact_list"
|
||||||
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "title",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Data",
|
||||||
"allow_on_submit": 0,
|
"label": "Title",
|
||||||
"bold": 0,
|
"reqd": 1
|
||||||
"collapsible": 0,
|
},
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "title",
|
|
||||||
"fieldtype": "Data",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Title",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "column_break_2",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Column Break"
|
||||||
"allow_on_submit": 0,
|
},
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "column_break_2",
|
|
||||||
"fieldtype": "Column Break",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "naming_series",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Select",
|
||||||
"allow_on_submit": 0,
|
"options": "ACC-SH-.YYYY.-"
|
||||||
"bold": 0,
|
},
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"default": "",
|
|
||||||
"fieldname": "naming_series",
|
|
||||||
"fieldtype": "Select",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "ACC-SH-.YYYY.-",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "section_break_2",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Section Break"
|
||||||
"allow_on_submit": 0,
|
},
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "section_break_2",
|
|
||||||
"fieldtype": "Section Break",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "folio_no",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Data",
|
||||||
"allow_on_submit": 0,
|
"label": "Folio no.",
|
||||||
"bold": 0,
|
"read_only": 1,
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "folio_no",
|
|
||||||
"fieldtype": "Data",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Folio no.",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 1
|
"unique": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "column_break_4",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Column Break"
|
||||||
"allow_on_submit": 0,
|
},
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "column_break_4",
|
|
||||||
"fieldtype": "Column Break",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "company",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Link",
|
||||||
"allow_on_submit": 0,
|
"in_list_view": 1,
|
||||||
"bold": 0,
|
"label": "Company",
|
||||||
"collapsible": 0,
|
"options": "Company",
|
||||||
"columns": 0,
|
"reqd": 1
|
||||||
"fieldname": "company",
|
},
|
||||||
"fieldtype": "Link",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Company",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Company",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"default": "0",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldname": "is_company",
|
||||||
"allow_on_submit": 0,
|
"fieldtype": "Check",
|
||||||
"bold": 0,
|
"hidden": 1,
|
||||||
"collapsible": 0,
|
"label": "Is Company",
|
||||||
"columns": 0,
|
"read_only": 1
|
||||||
"fieldname": "is_company",
|
},
|
||||||
"fieldtype": "Check",
|
|
||||||
"hidden": 1,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Is Company",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "address_contacts",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Section Break",
|
||||||
"allow_on_submit": 0,
|
"label": "Address and Contacts",
|
||||||
"bold": 0,
|
"options": "fa fa-map-marker"
|
||||||
"collapsible": 0,
|
},
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "address_contacts",
|
|
||||||
"fieldtype": "Section Break",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Address and Contacts",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "fa fa-map-marker",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "address_html",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "HTML",
|
||||||
"allow_on_submit": 0,
|
"label": "Address HTML",
|
||||||
"bold": 0,
|
"read_only": 1
|
||||||
"collapsible": 0,
|
},
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "address_html",
|
|
||||||
"fieldtype": "HTML",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Address HTML",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "column_break_9",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Column Break"
|
||||||
"allow_on_submit": 0,
|
},
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "column_break_9",
|
|
||||||
"fieldtype": "Column Break",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "contact_html",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "HTML",
|
||||||
"allow_on_submit": 0,
|
"label": "Contact HTML",
|
||||||
"bold": 0,
|
"read_only": 1
|
||||||
"collapsible": 0,
|
},
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "contact_html",
|
|
||||||
"fieldtype": "HTML",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Contact HTML",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "section_break_3",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Section Break",
|
||||||
"allow_on_submit": 0,
|
"label": "Share Balance"
|
||||||
"bold": 0,
|
},
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "section_break_3",
|
|
||||||
"fieldtype": "Section Break",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Share Balance",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "share_balance",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Table",
|
||||||
"allow_on_submit": 0,
|
"label": "Share Balance",
|
||||||
"bold": 0,
|
"options": "Share Balance",
|
||||||
"collapsible": 0,
|
"read_only": 1
|
||||||
"columns": 0,
|
},
|
||||||
"fieldname": "share_balance",
|
|
||||||
"fieldtype": "Table",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Share Balance",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Share Balance",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"description": "Hidden list maintaining the list of contacts linked to Shareholder",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldname": "contact_list",
|
||||||
"allow_on_submit": 0,
|
"fieldtype": "Code",
|
||||||
"bold": 0,
|
"hidden": 1,
|
||||||
"collapsible": 0,
|
"label": "Contact List",
|
||||||
"columns": 0,
|
"read_only": 1
|
||||||
"description": "Hidden list maintaining the list of contacts linked to Shareholder",
|
|
||||||
"fieldname": "contact_list",
|
|
||||||
"fieldtype": "Code",
|
|
||||||
"hidden": 1,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Contact List",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"has_web_view": 0,
|
"modified": "2019-11-17 23:24:11.395882",
|
||||||
"hide_heading": 0,
|
"modified_by": "Administrator",
|
||||||
"hide_toolbar": 0,
|
"module": "Accounts",
|
||||||
"idx": 0,
|
"name": "Shareholder",
|
||||||
"image_view": 0,
|
"name_case": "Title Case",
|
||||||
"in_create": 0,
|
"owner": "Administrator",
|
||||||
"is_submittable": 0,
|
|
||||||
"issingle": 0,
|
|
||||||
"istable": 0,
|
|
||||||
"max_attachments": 0,
|
|
||||||
"modified": "2018-09-18 14:14:24.953014",
|
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": "Accounts",
|
|
||||||
"name": "Shareholder",
|
|
||||||
"name_case": "Title Case",
|
|
||||||
"owner": "Administrator",
|
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
"amend": 0,
|
"create": 1,
|
||||||
"cancel": 0,
|
"delete": 1,
|
||||||
"create": 1,
|
"email": 1,
|
||||||
"delete": 1,
|
"export": 1,
|
||||||
"email": 1,
|
"print": 1,
|
||||||
"export": 1,
|
"read": 1,
|
||||||
"if_owner": 0,
|
"report": 1,
|
||||||
"import": 0,
|
"role": "System Manager",
|
||||||
"permlevel": 0,
|
"share": 1,
|
||||||
"print": 1,
|
|
||||||
"read": 1,
|
|
||||||
"report": 1,
|
|
||||||
"role": "System Manager",
|
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 1,
|
|
||||||
"submit": 0,
|
|
||||||
"write": 1
|
"write": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"amend": 0,
|
"create": 1,
|
||||||
"cancel": 0,
|
"delete": 1,
|
||||||
"create": 1,
|
"email": 1,
|
||||||
"delete": 1,
|
"export": 1,
|
||||||
"email": 1,
|
"print": 1,
|
||||||
"export": 1,
|
"read": 1,
|
||||||
"if_owner": 0,
|
"report": 1,
|
||||||
"import": 0,
|
"role": "Accounts Manager",
|
||||||
"permlevel": 0,
|
"share": 1,
|
||||||
"print": 1,
|
|
||||||
"read": 1,
|
|
||||||
"report": 1,
|
|
||||||
"role": "Accounts Manager",
|
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 1,
|
|
||||||
"submit": 0,
|
|
||||||
"write": 1
|
"write": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"amend": 0,
|
"create": 1,
|
||||||
"cancel": 0,
|
"delete": 1,
|
||||||
"create": 1,
|
"email": 1,
|
||||||
"delete": 1,
|
"export": 1,
|
||||||
"email": 1,
|
"print": 1,
|
||||||
"export": 1,
|
"read": 1,
|
||||||
"if_owner": 0,
|
"report": 1,
|
||||||
"import": 0,
|
"role": "Accounts User",
|
||||||
"permlevel": 0,
|
"share": 1,
|
||||||
"print": 1,
|
|
||||||
"read": 1,
|
|
||||||
"report": 1,
|
|
||||||
"role": "Accounts User",
|
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 1,
|
|
||||||
"submit": 0,
|
|
||||||
"write": 1
|
"write": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"quick_entry": 0,
|
"search_fields": "folio_no",
|
||||||
"read_only": 0,
|
"sort_field": "modified",
|
||||||
"read_only_onload": 0,
|
"sort_order": "DESC",
|
||||||
"search_fields": "folio_no",
|
"title_field": "title",
|
||||||
"show_name_in_global_search": 0,
|
"track_changes": 1
|
||||||
"sort_field": "modified",
|
|
||||||
"sort_order": "DESC",
|
|
||||||
"title_field": "title",
|
|
||||||
"track_changes": 1,
|
|
||||||
"track_seen": 0,
|
|
||||||
"track_views": 0
|
|
||||||
}
|
}
|
||||||
@@ -163,19 +163,44 @@ 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):
|
||||||
if not cint(frappe.db.get_value("Asset Settings", None, "disable_cwip_accounting")) \
|
cwip_enabled = any([cint(ac.enable_cwip_accounting) for ac in frappe.db.get_all("Asset Category","enable_cwip_accounting")])
|
||||||
and gl_map[0].voucher_type == "Journal Entry":
|
|
||||||
|
if cwip_enabled and gl_map[0].voucher_type == "Journal Entry":
|
||||||
cwip_accounts = [d[0] for d in frappe.db.sql("""select name from tabAccount
|
cwip_accounts = [d[0] for d in frappe.db.sql("""select name from tabAccount
|
||||||
where account_type = 'Capital Work in Progress' and is_group=0""")]
|
where account_type = 'Capital Work in Progress' and is_group=0""")]
|
||||||
|
|
||||||
for entry in gl_map:
|
for entry in gl_map:
|
||||||
if entry.account in cwip_accounts:
|
if entry.account in cwip_accounts:
|
||||||
frappe.throw(_("Account: <b>{0}</b> is capital Work in progress and can not be updated by Journal Entry").format(entry.account))
|
frappe.throw(
|
||||||
|
_("Account: <b>{0}</b> is capital Work in progress and can not be updated by Journal Entry").format(entry.account))
|
||||||
|
|
||||||
def round_off_debit_credit(gl_map):
|
def round_off_debit_credit(gl_map):
|
||||||
precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"),
|
precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"),
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -630,7 +630,7 @@ def get_held_invoices(party_type, party):
|
|||||||
'select name from `tabPurchase Invoice` where release_date IS NOT NULL and release_date > CURDATE()',
|
'select name from `tabPurchase Invoice` where release_date IS NOT NULL and release_date > CURDATE()',
|
||||||
as_dict=1
|
as_dict=1
|
||||||
)
|
)
|
||||||
held_invoices = [d['name'] for d in held_invoices]
|
held_invoices = set([d['name'] for d in held_invoices])
|
||||||
|
|
||||||
return held_invoices
|
return held_invoices
|
||||||
|
|
||||||
@@ -639,14 +639,19 @@ def get_outstanding_invoices(party_type, party, account, condition=None, filters
|
|||||||
outstanding_invoices = []
|
outstanding_invoices = []
|
||||||
precision = frappe.get_precision("Sales Invoice", "outstanding_amount") or 2
|
precision = frappe.get_precision("Sales Invoice", "outstanding_amount") or 2
|
||||||
|
|
||||||
if erpnext.get_party_account_type(party_type) == 'Receivable':
|
if account:
|
||||||
|
root_type = frappe.get_cached_value("Account", account, "root_type")
|
||||||
|
party_account_type = "Receivable" if root_type == "Asset" else "Payable"
|
||||||
|
else:
|
||||||
|
party_account_type = erpnext.get_party_account_type(party_type)
|
||||||
|
|
||||||
|
if party_account_type == 'Receivable':
|
||||||
dr_or_cr = "debit_in_account_currency - credit_in_account_currency"
|
dr_or_cr = "debit_in_account_currency - credit_in_account_currency"
|
||||||
payment_dr_or_cr = "credit_in_account_currency - debit_in_account_currency"
|
payment_dr_or_cr = "credit_in_account_currency - debit_in_account_currency"
|
||||||
else:
|
else:
|
||||||
dr_or_cr = "credit_in_account_currency - debit_in_account_currency"
|
dr_or_cr = "credit_in_account_currency - debit_in_account_currency"
|
||||||
payment_dr_or_cr = "debit_in_account_currency - credit_in_account_currency"
|
payment_dr_or_cr = "debit_in_account_currency - credit_in_account_currency"
|
||||||
|
|
||||||
invoice = 'Sales Invoice' if erpnext.get_party_account_type(party_type) == 'Receivable' else 'Purchase Invoice'
|
|
||||||
held_invoices = get_held_invoices(party_type, party)
|
held_invoices = get_held_invoices(party_type, party)
|
||||||
|
|
||||||
invoice_list = frappe.db.sql("""
|
invoice_list = frappe.db.sql("""
|
||||||
@@ -665,7 +670,6 @@ def get_outstanding_invoices(party_type, party, account, condition=None, filters
|
|||||||
group by voucher_type, voucher_no
|
group by voucher_type, voucher_no
|
||||||
order by posting_date, name""".format(
|
order by posting_date, name""".format(
|
||||||
dr_or_cr=dr_or_cr,
|
dr_or_cr=dr_or_cr,
|
||||||
invoice = invoice,
|
|
||||||
condition=condition or ""
|
condition=condition or ""
|
||||||
), {
|
), {
|
||||||
"party_type": party_type,
|
"party_type": party_type,
|
||||||
|
|||||||
@@ -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']
|
||||||
|
|||||||
@@ -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,21 +229,25 @@ frappe.ui.form.on('Asset', {
|
|||||||
|
|
||||||
item_code: function(frm) {
|
item_code: function(frm) {
|
||||||
if(frm.doc.item_code) {
|
if(frm.doc.item_code) {
|
||||||
frappe.call({
|
frm.trigger('set_finance_book');
|
||||||
method: "erpnext.assets.doctype.asset.asset.get_item_details",
|
|
||||||
args: {
|
|
||||||
item_code: frm.doc.item_code,
|
|
||||||
asset_category: frm.doc.asset_category
|
|
||||||
},
|
|
||||||
callback: function(r, rt) {
|
|
||||||
if(r.message) {
|
|
||||||
frm.set_value('finance_books', r.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
set_finance_book: function(frm) {
|
||||||
|
frappe.call({
|
||||||
|
method: "erpnext.assets.doctype.asset.asset.get_item_details",
|
||||||
|
args: {
|
||||||
|
item_code: frm.doc.item_code,
|
||||||
|
asset_category: frm.doc.asset_category
|
||||||
|
},
|
||||||
|
callback: function(r, rt) {
|
||||||
|
if(r.message) {
|
||||||
|
frm.set_value('finance_books', r.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
available_for_use_date: function(frm) {
|
available_for_use_date: function(frm) {
|
||||||
$.each(frm.doc.finance_books || [], function(i, d) {
|
$.each(frm.doc.finance_books || [], function(i, d) {
|
||||||
if(!d.depreciation_start_date) d.depreciation_start_date = frm.doc.available_for_use_date;
|
if(!d.depreciation_start_date) d.depreciation_start_date = frm.doc.available_for_use_date;
|
||||||
@@ -203,33 +260,18 @@ frappe.ui.form.on('Asset', {
|
|||||||
},
|
},
|
||||||
|
|
||||||
opening_accumulated_depreciation: function(frm) {
|
opening_accumulated_depreciation: function(frm) {
|
||||||
erpnext.asset.set_accululated_depreciation(frm);
|
erpnext.asset.set_accumulated_depreciation(frm);
|
||||||
},
|
},
|
||||||
|
|
||||||
make_schedules_editable: function(frm) {
|
make_schedules_editable: function(frm) {
|
||||||
var is_editable = frm.doc.finance_books.filter(d => d.depreciation_method == "Manual").length > 0
|
if (frm.doc.finance_books) {
|
||||||
? true : false;
|
var is_editable = frm.doc.finance_books.filter(d => d.depreciation_method == "Manual").length > 0
|
||||||
|
? 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) {
|
||||||
@@ -282,17 +324,6 @@ frappe.ui.form.on('Asset', {
|
|||||||
},
|
},
|
||||||
|
|
||||||
calculate_depreciation: function(frm) {
|
calculate_depreciation: function(frm) {
|
||||||
frappe.db.get_value("Asset Settings", {'name':"Asset Settings"}, 'schedule_based_on_fiscal_year', (data) => {
|
|
||||||
if (data.schedule_based_on_fiscal_year == 1) {
|
|
||||||
frm.set_df_property("depreciation_method", "options", "\nStraight Line\nManual");
|
|
||||||
frm.toggle_reqd("available_for_use_date", true);
|
|
||||||
frm.toggle_display("frequency_of_depreciation", false);
|
|
||||||
frappe.db.get_value("Fiscal Year", {'name': frappe.sys_defaults.fiscal_year}, "year_end_date", (data) => {
|
|
||||||
frm.set_value("next_depreciation_date", data.year_end_date);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
frm.toggle_reqd("finance_books", frm.doc.calculate_depreciation);
|
frm.toggle_reqd("finance_books", frm.doc.calculate_depreciation);
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -302,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) {
|
||||||
@@ -371,12 +461,12 @@ frappe.ui.form.on('Depreciation Schedule', {
|
|||||||
},
|
},
|
||||||
|
|
||||||
depreciation_amount: function(frm, cdt, cdn) {
|
depreciation_amount: function(frm, cdt, cdn) {
|
||||||
erpnext.asset.set_accululated_depreciation(frm);
|
erpnext.asset.set_accumulated_depreciation(frm);
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
erpnext.asset.set_accululated_depreciation = function(frm) {
|
erpnext.asset.set_accumulated_depreciation = function(frm) {
|
||||||
if(frm.doc.depreciation_method != "Manual") return;
|
if(frm.doc.depreciation_method != "Manual") return;
|
||||||
|
|
||||||
var accumulated_depreciation = flt(frm.doc.opening_accumulated_depreciation);
|
var accumulated_depreciation = flt(frm.doc.opening_accumulated_depreciation);
|
||||||
@@ -415,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: [
|
freeze: true,
|
||||||
{
|
args:{
|
||||||
"label": __("Target Location"),
|
"assets": [{ name: cur_frm.doc.name }],
|
||||||
"fieldname": "target_location",
|
"purpose": "Transfer"
|
||||||
"fieldtype": "Link",
|
},
|
||||||
"options": "Location",
|
callback: function (r) {
|
||||||
"get_query": function () {
|
if (r.message) {
|
||||||
return {
|
var doc = frappe.model.sync(r.message)[0];
|
||||||
filters: [
|
frappe.set_route("Form", doc.doctype, doc.name);
|
||||||
["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,
|
|
||||||
callback: function(r) {
|
|
||||||
cur_frm.reload_doc();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
dialog.show();
|
|
||||||
};
|
};
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -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,10 +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 not is_cwip_accounting_disabled():
|
if not self.booked_fixed_asset and is_cwip_accounting_enabled(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()
|
||||||
@@ -40,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
|
||||||
@@ -76,10 +92,13 @@ class Asset(AccountsController):
|
|||||||
self.set('finance_books', finance_books)
|
self.set('finance_books', finance_books)
|
||||||
|
|
||||||
def validate_asset_values(self):
|
def validate_asset_values(self):
|
||||||
|
if not self.asset_category:
|
||||||
|
self.asset_category = frappe.get_cached_value("Item", self.item_code, "asset_category")
|
||||||
|
|
||||||
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 not is_cwip_accounting_disabled():
|
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))
|
||||||
@@ -105,6 +124,38 @@ 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):
|
||||||
|
movements = frappe.db.sql(
|
||||||
|
"""SELECT asm.name, asm.docstatus
|
||||||
|
FROM `tabAsset Movement` asm, `tabAsset Movement Item` asm_item
|
||||||
|
WHERE asm_item.parent=asm.name and asm_item.asset=%s and asm.docstatus=1""", self.name, as_dict=1)
|
||||||
|
if len(movements) > 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', movements[0].get('name'))
|
||||||
|
movement.flags.ignore_validate = True
|
||||||
|
movement.cancel()
|
||||||
|
|
||||||
|
def make_asset_movement(self):
|
||||||
|
reference_doctype = 'Purchase Receipt' if self.purchase_receipt else 'Purchase Invoice'
|
||||||
|
reference_docname = self.purchase_receipt or self.purchase_invoice
|
||||||
|
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),
|
||||||
@@ -145,19 +196,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,
|
||||||
@@ -171,13 +234,50 @@ class Asset(AccountsController):
|
|||||||
skip_row = True
|
skip_row = True
|
||||||
|
|
||||||
if depreciation_amount > 0:
|
if depreciation_amount > 0:
|
||||||
self.append("schedules", {
|
# With monthly depreciation, each depreciation is divided by months remaining until next date
|
||||||
"schedule_date": schedule_date,
|
if self.allow_monthly_depreciation:
|
||||||
"depreciation_amount": depreciation_amount,
|
# month range is 1 to 12
|
||||||
"depreciation_method": d.depreciation_method,
|
# In pro rata case, for first and last depreciation, month range would be different
|
||||||
"finance_book": d.finance_book,
|
month_range = months \
|
||||||
"finance_book_id": d.idx
|
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", {
|
||||||
|
"schedule_date": schedule_date,
|
||||||
|
"depreciation_amount": depreciation_amount,
|
||||||
|
"depreciation_method": d.depreciation_method,
|
||||||
|
"finance_book": d.finance_book,
|
||||||
|
"finance_book_id": d.idx
|
||||||
|
})
|
||||||
|
|
||||||
def check_is_pro_rata(self, row):
|
def check_is_pro_rata(self, row):
|
||||||
has_pro_rata = False
|
has_pro_rata = False
|
||||||
@@ -196,7 +296,9 @@ class Asset(AccountsController):
|
|||||||
.format(row.idx))
|
.format(row.idx))
|
||||||
|
|
||||||
if not row.depreciation_start_date:
|
if not row.depreciation_start_date:
|
||||||
frappe.throw(_("Row {0}: Depreciation Start Date is required").format(row.idx))
|
if not self.available_for_use_date:
|
||||||
|
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
|
||||||
@@ -345,22 +447,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",
|
||||||
@@ -368,7 +461,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,
|
||||||
@@ -377,7 +470,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,
|
||||||
@@ -424,7 +517,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 is_cwip_accounting_disabled():
|
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`
|
||||||
@@ -438,25 +531,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")
|
||||||
@@ -531,7 +605,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:
|
||||||
@@ -574,17 +648,43 @@ def make_journal_entry(asset_name):
|
|||||||
|
|
||||||
return je
|
return je
|
||||||
|
|
||||||
def is_cwip_accounting_disabled():
|
@frappe.whitelist()
|
||||||
return cint(frappe.db.get_single_value("Asset Settings", "disable_cwip_accounting"))
|
def make_asset_movement(assets, purpose=None):
|
||||||
|
import json
|
||||||
|
from six import string_types
|
||||||
|
|
||||||
|
if isinstance(assets, string_types):
|
||||||
|
assets = json.loads(assets)
|
||||||
|
|
||||||
|
if len(assets) == 0:
|
||||||
|
frappe.throw(_('Atleast one asset has to be selected.'))
|
||||||
|
|
||||||
|
asset_movement = frappe.new_doc("Asset Movement")
|
||||||
|
asset_movement.quantity = len(assets)
|
||||||
|
for asset in assets:
|
||||||
|
asset = frappe.get_doc('Asset', asset.get('name'))
|
||||||
|
asset_movement.company = asset.get('company')
|
||||||
|
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"))
|
||||||
|
|
||||||
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,
|
||||||
cint(frequency) * -1)
|
cint(frequency) * -1)
|
||||||
|
|
||||||
return date_diff(date, period_start_date)
|
return date_diff(date, period_start_date)
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -7,14 +7,13 @@ 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
|
||||||
|
|
||||||
class TestAsset(unittest.TestCase):
|
class TestAsset(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
set_depreciation_settings_in_company()
|
set_depreciation_settings_in_company()
|
||||||
remove_prorated_depreciation_schedule()
|
|
||||||
create_asset_data()
|
create_asset_data()
|
||||||
frappe.db.sql("delete from `tabTax Rule`")
|
frappe.db.sql("delete from `tabTax Rule`")
|
||||||
|
|
||||||
@@ -40,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),
|
||||||
@@ -61,20 +60,23 @@ 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}))
|
||||||
|
|
||||||
def test_is_fixed_asset_set(self):
|
def test_is_fixed_asset_set(self):
|
||||||
|
asset = create_asset(is_existing_asset = 1)
|
||||||
doc = frappe.new_doc('Purchase Invoice')
|
doc = frappe.new_doc('Purchase Invoice')
|
||||||
doc.supplier = '_Test Supplier'
|
doc.supplier = '_Test Supplier'
|
||||||
doc.append('items', {
|
doc.append('items', {
|
||||||
'item_code': 'Macbook Pro',
|
'item_code': 'Macbook Pro',
|
||||||
'qty': 1
|
'qty': 1,
|
||||||
|
'asset': asset.name
|
||||||
})
|
})
|
||||||
|
|
||||||
doc.set_missing_values()
|
doc.set_missing_values()
|
||||||
@@ -200,7 +202,6 @@ class TestAsset(unittest.TestCase):
|
|||||||
self.assertEqual(schedules, expected_schedules)
|
self.assertEqual(schedules, expected_schedules)
|
||||||
|
|
||||||
def test_schedule_for_prorated_straight_line_method(self):
|
def test_schedule_for_prorated_straight_line_method(self):
|
||||||
set_prorated_depreciation_schedule()
|
|
||||||
pr = make_purchase_receipt(item_code="Macbook Pro",
|
pr = make_purchase_receipt(item_code="Macbook Pro",
|
||||||
qty=1, rate=100000.0, location="Test Location")
|
qty=1, rate=100000.0, location="Test Location")
|
||||||
|
|
||||||
@@ -233,8 +234,6 @@ class TestAsset(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(schedules, expected_schedules)
|
self.assertEqual(schedules, expected_schedules)
|
||||||
|
|
||||||
remove_prorated_depreciation_schedule()
|
|
||||||
|
|
||||||
def test_depreciation(self):
|
def test_depreciation(self):
|
||||||
pr = make_purchase_receipt(item_code="Macbook Pro",
|
pr = make_purchase_receipt(item_code="Macbook Pro",
|
||||||
qty=1, rate=100000.0, location="Test Location")
|
qty=1, rate=100000.0, location="Test Location")
|
||||||
@@ -484,9 +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)
|
|
||||||
|
|
||||||
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')
|
||||||
@@ -565,6 +561,7 @@ class TestAsset(unittest.TestCase):
|
|||||||
where voucher_type='Asset' and voucher_no = %s
|
where voucher_type='Asset' and voucher_no = %s
|
||||||
order by account""", asset_doc.name)
|
order by account""", asset_doc.name)
|
||||||
|
|
||||||
|
|
||||||
self.assertEqual(gle, expected_gle)
|
self.assertEqual(gle, expected_gle)
|
||||||
|
|
||||||
def test_expense_head(self):
|
def test_expense_head(self):
|
||||||
@@ -575,7 +572,6 @@ class TestAsset(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEquals('Asset Received But Not Billed - _TC', doc.items[0].expense_account)
|
self.assertEquals('Asset Received But Not Billed - _TC', doc.items[0].expense_account)
|
||||||
|
|
||||||
|
|
||||||
def create_asset_data():
|
def create_asset_data():
|
||||||
if not frappe.db.exists("Asset Category", "Computers"):
|
if not frappe.db.exists("Asset Category", "Computers"):
|
||||||
create_asset_category()
|
create_asset_category()
|
||||||
@@ -596,15 +592,15 @@ def create_asset(**args):
|
|||||||
|
|
||||||
asset = frappe.get_doc({
|
asset = frappe.get_doc({
|
||||||
"doctype": "Asset",
|
"doctype": "Asset",
|
||||||
"asset_name": "Macbook Pro 1",
|
"asset_name": args.asset_name or "Macbook Pro 1",
|
||||||
"asset_category": "Computers",
|
"asset_category": "Computers",
|
||||||
"item_code": "Macbook Pro",
|
"item_code": args.item_code or "Macbook Pro",
|
||||||
"company": "_Test Company",
|
"company": args.company or"_Test Company",
|
||||||
"purchase_date": "2015-01-01",
|
"purchase_date": "2015-01-01",
|
||||||
"calculate_depreciation": 0,
|
"calculate_depreciation": 0,
|
||||||
"gross_purchase_amount": 100000,
|
"gross_purchase_amount": 100000,
|
||||||
"expected_value_after_useful_life": 10000,
|
"expected_value_after_useful_life": 10000,
|
||||||
"warehouse": "_Test Warehouse - _TC",
|
"warehouse": args.warehouse or "_Test Warehouse - _TC",
|
||||||
"available_for_use_date": "2020-06-06",
|
"available_for_use_date": "2020-06-06",
|
||||||
"location": "Test Location",
|
"location": "Test Location",
|
||||||
"asset_owner": "Company",
|
"asset_owner": "Company",
|
||||||
@@ -616,6 +612,9 @@ def create_asset(**args):
|
|||||||
except frappe.DuplicateEntryError:
|
except frappe.DuplicateEntryError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
if args.submit:
|
||||||
|
asset.submit()
|
||||||
|
|
||||||
return asset
|
return asset
|
||||||
|
|
||||||
def create_asset_category():
|
def create_asset_category():
|
||||||
@@ -623,6 +622,7 @@ def create_asset_category():
|
|||||||
asset_category.asset_category_name = "Computers"
|
asset_category.asset_category_name = "Computers"
|
||||||
asset_category.total_number_of_depreciations = 3
|
asset_category.total_number_of_depreciations = 3
|
||||||
asset_category.frequency_of_depreciation = 3
|
asset_category.frequency_of_depreciation = 3
|
||||||
|
asset_category.enable_cwip_accounting = 1
|
||||||
asset_category.append("accounts", {
|
asset_category.append("accounts", {
|
||||||
"company_name": "_Test Company",
|
"company_name": "_Test Company",
|
||||||
"fixed_asset_account": "_Test Fixed Asset - _TC",
|
"fixed_asset_account": "_Test Fixed Asset - _TC",
|
||||||
@@ -632,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",
|
||||||
@@ -642,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
|
||||||
@@ -656,19 +660,4 @@ def set_depreciation_settings_in_company():
|
|||||||
company.save()
|
company.save()
|
||||||
|
|
||||||
# Enable booking asset depreciation entry automatically
|
# Enable booking asset depreciation entry automatically
|
||||||
frappe.db.set_value("Accounts Settings", None, "book_asset_depreciation_entry_automatically", 1)
|
frappe.db.set_value("Accounts Settings", None, "book_asset_depreciation_entry_automatically", 1)
|
||||||
|
|
||||||
def remove_prorated_depreciation_schedule():
|
|
||||||
asset_settings = frappe.get_doc("Asset Settings", "Asset Settings")
|
|
||||||
asset_settings.schedule_based_on_fiscal_year = 0
|
|
||||||
asset_settings.save()
|
|
||||||
|
|
||||||
frappe.db.commit()
|
|
||||||
|
|
||||||
def set_prorated_depreciation_schedule():
|
|
||||||
asset_settings = frappe.get_doc("Asset Settings", "Asset Settings")
|
|
||||||
asset_settings.schedule_based_on_fiscal_year = 1
|
|
||||||
asset_settings.number_of_days_in_fiscal_year = 360
|
|
||||||
asset_settings.save()
|
|
||||||
|
|
||||||
frappe.db.commit()
|
|
||||||
@@ -1,284 +1,115 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"allow_import": 1,
|
||||||
"allow_guest_to_view": 0,
|
"allow_rename": 1,
|
||||||
"allow_import": 1,
|
"autoname": "field:asset_category_name",
|
||||||
"allow_rename": 1,
|
"creation": "2016-03-01 17:41:39.778765",
|
||||||
"autoname": "field:asset_category_name",
|
"doctype": "DocType",
|
||||||
"beta": 0,
|
"document_type": "Document",
|
||||||
"creation": "2016-03-01 17:41:39.778765",
|
"engine": "InnoDB",
|
||||||
"custom": 0,
|
"field_order": [
|
||||||
"docstatus": 0,
|
"asset_category_name",
|
||||||
"doctype": "DocType",
|
"column_break_3",
|
||||||
"document_type": "Document",
|
"depreciation_options",
|
||||||
"editable_grid": 0,
|
"enable_cwip_accounting",
|
||||||
"engine": "InnoDB",
|
"finance_book_detail",
|
||||||
|
"finance_books",
|
||||||
|
"section_break_2",
|
||||||
|
"accounts"
|
||||||
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "asset_category_name",
|
||||||
"allow_on_submit": 0,
|
"fieldtype": "Data",
|
||||||
"bold": 0,
|
"in_list_view": 1,
|
||||||
"collapsible": 0,
|
"label": "Asset Category Name",
|
||||||
"columns": 0,
|
"reqd": 1,
|
||||||
"fieldname": "asset_category_name",
|
"unique": 1
|
||||||
"fieldtype": "Data",
|
},
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Asset Category Name",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "column_break_3",
|
||||||
"allow_on_submit": 0,
|
"fieldtype": "Column Break"
|
||||||
"bold": 0,
|
},
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "column_break_3",
|
|
||||||
"fieldtype": "Column Break",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "finance_book_detail",
|
||||||
"allow_on_submit": 0,
|
"fieldtype": "Section Break",
|
||||||
"bold": 0,
|
"label": "Finance Book Detail"
|
||||||
"collapsible": 0,
|
},
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "finance_book_detail",
|
|
||||||
"fieldtype": "Section Break",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Finance Book Detail",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "finance_books",
|
||||||
"allow_on_submit": 0,
|
"fieldtype": "Table",
|
||||||
"bold": 0,
|
"label": "Finance Books",
|
||||||
"collapsible": 0,
|
"options": "Asset Finance Book"
|
||||||
"columns": 0,
|
},
|
||||||
"fieldname": "finance_books",
|
|
||||||
"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": "Finance Books",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Asset Finance Book",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "section_break_2",
|
||||||
"allow_on_submit": 0,
|
"fieldtype": "Section Break",
|
||||||
"bold": 0,
|
"label": "Accounts"
|
||||||
"collapsible": 0,
|
},
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "section_break_2",
|
|
||||||
"fieldtype": "Section Break",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Accounts",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "accounts",
|
||||||
"allow_on_submit": 0,
|
"fieldtype": "Table",
|
||||||
"bold": 0,
|
"label": "Accounts",
|
||||||
"collapsible": 0,
|
"options": "Asset Category Account",
|
||||||
"columns": 0,
|
"reqd": 1
|
||||||
"fieldname": "accounts",
|
},
|
||||||
"fieldtype": "Table",
|
{
|
||||||
"hidden": 0,
|
"fieldname": "depreciation_options",
|
||||||
"ignore_user_permissions": 0,
|
"fieldtype": "Section Break",
|
||||||
"ignore_xss_filter": 0,
|
"label": "Depreciation Options"
|
||||||
"in_filter": 0,
|
},
|
||||||
"in_global_search": 0,
|
{
|
||||||
"in_list_view": 0,
|
"default": "0",
|
||||||
"in_standard_filter": 0,
|
"fieldname": "enable_cwip_accounting",
|
||||||
"label": "Accounts",
|
"fieldtype": "Check",
|
||||||
"length": 0,
|
"label": "Enable Capital Work in Progress Accounting"
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Asset Category Account",
|
|
||||||
"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,
|
|
||||||
"unique": 0
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"has_web_view": 0,
|
"modified": "2019-10-11 12:19:59.759136",
|
||||||
"hide_heading": 0,
|
"modified_by": "Administrator",
|
||||||
"hide_toolbar": 0,
|
"module": "Assets",
|
||||||
"idx": 0,
|
"name": "Asset Category",
|
||||||
"image_view": 0,
|
"owner": "Administrator",
|
||||||
"in_create": 0,
|
|
||||||
"is_submittable": 0,
|
|
||||||
"issingle": 0,
|
|
||||||
"istable": 0,
|
|
||||||
"max_attachments": 0,
|
|
||||||
"modified": "2018-05-12 14:56:04.116425",
|
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": "Assets",
|
|
||||||
"name": "Asset Category",
|
|
||||||
"name_case": "",
|
|
||||||
"owner": "Administrator",
|
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
"amend": 0,
|
"create": 1,
|
||||||
"apply_user_permissions": 0,
|
"delete": 1,
|
||||||
"cancel": 0,
|
"email": 1,
|
||||||
"create": 1,
|
"export": 1,
|
||||||
"delete": 1,
|
"import": 1,
|
||||||
"email": 1,
|
"print": 1,
|
||||||
"export": 1,
|
"read": 1,
|
||||||
"if_owner": 0,
|
"report": 1,
|
||||||
"import": 1,
|
"role": "Accounts User",
|
||||||
"permlevel": 0,
|
"share": 1,
|
||||||
"print": 1,
|
|
||||||
"read": 1,
|
|
||||||
"report": 1,
|
|
||||||
"role": "Accounts User",
|
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 1,
|
|
||||||
"submit": 0,
|
|
||||||
"write": 1
|
"write": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"amend": 0,
|
"create": 1,
|
||||||
"apply_user_permissions": 0,
|
"delete": 1,
|
||||||
"cancel": 0,
|
"email": 1,
|
||||||
"create": 1,
|
"export": 1,
|
||||||
"delete": 1,
|
"import": 1,
|
||||||
"email": 1,
|
"print": 1,
|
||||||
"export": 1,
|
"read": 1,
|
||||||
"if_owner": 0,
|
"report": 1,
|
||||||
"import": 1,
|
"role": "Accounts Manager",
|
||||||
"permlevel": 0,
|
"share": 1,
|
||||||
"print": 1,
|
|
||||||
"read": 1,
|
|
||||||
"report": 1,
|
|
||||||
"role": "Accounts Manager",
|
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 1,
|
|
||||||
"submit": 0,
|
|
||||||
"write": 1
|
"write": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"amend": 0,
|
"create": 1,
|
||||||
"apply_user_permissions": 0,
|
"delete": 1,
|
||||||
"cancel": 0,
|
"email": 1,
|
||||||
"create": 1,
|
"export": 1,
|
||||||
"delete": 1,
|
"print": 1,
|
||||||
"email": 1,
|
"read": 1,
|
||||||
"export": 1,
|
"report": 1,
|
||||||
"if_owner": 0,
|
"role": "Quality Manager",
|
||||||
"import": 0,
|
"share": 1,
|
||||||
"permlevel": 0,
|
|
||||||
"print": 1,
|
|
||||||
"read": 1,
|
|
||||||
"report": 1,
|
|
||||||
"role": "Quality Manager",
|
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 1,
|
|
||||||
"submit": 0,
|
|
||||||
"write": 1
|
"write": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"quick_entry": 0,
|
"show_name_in_global_search": 1,
|
||||||
"read_only": 0,
|
"sort_field": "modified",
|
||||||
"read_only_onload": 0,
|
"sort_order": "DESC"
|
||||||
"show_name_in_global_search": 1,
|
|
||||||
"sort_field": "modified",
|
|
||||||
"sort_order": "DESC",
|
|
||||||
"track_changes": 0,
|
|
||||||
"track_seen": 0
|
|
||||||
}
|
}
|
||||||
@@ -10,14 +10,20 @@ from frappe.model.document import Document
|
|||||||
|
|
||||||
class AssetCategory(Document):
|
class AssetCategory(Document):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
|
self.validate_finance_books()
|
||||||
|
|
||||||
|
def validate_finance_books(self):
|
||||||
for d in self.finance_books:
|
for d in self.finance_books:
|
||||||
for field in ("Total Number of Depreciations", "Frequency of Depreciation"):
|
for field in ("Total Number of Depreciations", "Frequency of Depreciation"):
|
||||||
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)
|
||||||
|
|
||||||
@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
|
||||||
|
|||||||
@@ -73,8 +73,10 @@ def create_asset_data():
|
|||||||
'doctype': 'Location',
|
'doctype': 'Location',
|
||||||
'location_name': 'Test Location'
|
'location_name': 'Test Location'
|
||||||
}).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():
|
||||||
|
|||||||
@@ -2,27 +2,101 @@
|
|||||||
// 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"]]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
frm.set_query("asset", "assets", () => {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
status: ["not in", ["Draft"]]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
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');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -1,26 +1,19 @@
|
|||||||
{
|
{
|
||||||
"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",
|
"section_break_10",
|
||||||
"serial_no",
|
"assets",
|
||||||
"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",
|
||||||
"amended_from"
|
"amended_from"
|
||||||
],
|
],
|
||||||
@@ -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",
|
||||||
@@ -65,56 +47,7 @@
|
|||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "quantity",
|
"collapsible": 1,
|
||||||
"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",
|
||||||
"label": "Reference"
|
"label": "Reference"
|
||||||
@@ -122,18 +55,16 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "reference_doctype",
|
"fieldname": "reference_doctype",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Reference DocType",
|
"label": "Reference Document Type",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "DocType",
|
"options": "DocType"
|
||||||
"read_only": 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
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "amended_from",
|
"fieldname": "amended_from",
|
||||||
@@ -145,16 +76,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-23 13:28:47.256935",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Assets",
|
"module": "Assets",
|
||||||
"name": "Asset Movement",
|
"name": "Asset Movement",
|
||||||
|
|||||||
@@ -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:
|
||||||
if self.purpose == 'Transfer' and status in ("Draft", "Scrapped", "Sold"):
|
status, company = frappe.db.get_value("Asset", d.asset, ["status", "company"])
|
||||||
frappe.throw(_("{0} asset cannot be transferred").format(status))
|
if self.purpose == 'Transfer' and status in ("Draft", "Scrapped", "Sold"):
|
||||||
|
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"))
|
frappe.throw(_("Either location or employee must be required"))
|
||||||
|
|
||||||
if not(self.source_location or self.target_location or self.from_employee or self.to_employee):
|
|
||||||
frappe.throw(_("Either location or employee must be required"))
|
|
||||||
|
|
||||||
if (not self.serial_no and
|
|
||||||
frappe.db.get_value('Serial No', {'asset': self.asset}, 'name')):
|
|
||||||
frappe.throw(_("Serial no is required for the asset {0}").format(self.asset))
|
|
||||||
|
|
||||||
def validate_location(self):
|
def validate_location(self):
|
||||||
if self.purpose in ['Transfer', 'Issue']:
|
for d in self.assets:
|
||||||
if not self.serial_no and not (self.from_employee or self.to_employee):
|
if self.purpose in ['Transfer', 'Issue']:
|
||||||
self.source_location = frappe.db.get_value("Asset", self.asset, "location")
|
if not d.source_location:
|
||||||
|
d.source_location = frappe.db.get_value("Asset", d.asset, "location")
|
||||||
|
|
||||||
if self.purpose == 'Issue' and not (self.source_location or self.from_employee):
|
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.purpose == 'Issue':
|
||||||
|
if d.target_location:
|
||||||
|
frappe.throw(_("Issuing cannot be done to a location. \
|
||||||
|
Please enter employee who has issued Asset {0}").format(d.asset), title="Incorrect Movement Purpose")
|
||||||
|
if not d.to_employee:
|
||||||
|
frappe.throw(_("Employee is required while issuing Asset {0}").format(d.asset))
|
||||||
|
|
||||||
|
if self.purpose == 'Transfer':
|
||||||
|
if d.to_employee:
|
||||||
|
frappe.throw(_("Transferring cannot be done to an Employee. \
|
||||||
|
Please enter location where Asset {0} has to be transferred").format(
|
||||||
|
d.asset), title="Incorrect Movement Purpose")
|
||||||
|
if not d.target_location:
|
||||||
|
frappe.throw(_("Target Location is required while transferring Asset {0}").format(d.asset))
|
||||||
|
if d.source_location == d.target_location:
|
||||||
|
frappe.throw(_("Source and Target Location cannot be same"))
|
||||||
|
|
||||||
|
if self.purpose == 'Receipt':
|
||||||
|
# only when asset is bought and first entry is made
|
||||||
|
if not d.source_location and not (d.target_location or d.to_employee):
|
||||||
|
frappe.throw(_("Target Location or To Employee is required while receiving Asset {0}").format(d.asset))
|
||||||
|
elif d.source_location:
|
||||||
|
# when asset is received from an employee
|
||||||
|
if d.target_location and not d.from_employee:
|
||||||
|
frappe.throw(_("From employee is required while receiving Asset {0} to a target location").format(d.asset))
|
||||||
|
if d.from_employee and not d.target_location:
|
||||||
|
frappe.throw(_("Target Location is required while receiving Asset {0} from an employee").format(d.asset))
|
||||||
|
if d.to_employee and d.target_location:
|
||||||
|
frappe.throw(_("Asset {0} cannot be received at a location and \
|
||||||
|
given to employee in a single movement").format(d.asset))
|
||||||
|
|
||||||
if self.source_location and self.source_location == self.target_location and self.purpose == 'Transfer':
|
def validate_employee(self):
|
||||||
frappe.throw(_("Source and Target Location cannot be same"))
|
for d in self.assets:
|
||||||
|
if d.from_employee:
|
||||||
|
current_custodian = frappe.db.get_value("Asset", d.asset, "custodian")
|
||||||
|
|
||||||
if self.purpose == 'Receipt' and not (self.target_location or self.to_employee):
|
if current_custodian != d.from_employee:
|
||||||
frappe.throw(_("Target Location is required for the asset {0}").format(self.asset))
|
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"
|
||||||
|
|
||||||
args = {
|
for d in self.assets:
|
||||||
'asset': self.asset,
|
args = {
|
||||||
'company': self.company
|
'asset': d.asset,
|
||||||
}
|
'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
|
||||||
|
WHERE
|
||||||
|
asm_item.parent=asm.name and
|
||||||
|
asm_item.asset=%(asset)s and
|
||||||
|
asm.company=%(company)s and
|
||||||
|
asm.docstatus=1 and {0}
|
||||||
|
ORDER BY
|
||||||
|
asm.transaction_date desc limit 1
|
||||||
|
""".format(cond), args)
|
||||||
|
if latest_movement_entry:
|
||||||
|
current_location = latest_movement_entry[0][0]
|
||||||
|
current_employee = latest_movement_entry[0][1]
|
||||||
|
|
||||||
latest_movement_entry = frappe.db.sql("""select target_location, to_employee from `tabAsset Movement`
|
frappe.db.set_value('Asset', d.asset, 'location', current_location)
|
||||||
where asset=%(asset)s and docstatus=1 and company=%(company)s and {0}
|
frappe.db.set_value('Asset', d.asset, 'custodian', current_employee)
|
||||||
order by transaction_date desc limit 1""".format(cond), args)
|
|
||||||
|
|
||||||
if latest_movement_entry:
|
|
||||||
location = latest_movement_entry[0][0]
|
|
||||||
employee = latest_movement_entry[0][1]
|
|
||||||
elif self.purpose in ['Transfer', 'Receipt']:
|
|
||||||
movement_entry = frappe.db.sql("""select source_location, from_employee from `tabAsset Movement`
|
|
||||||
where asset=%(asset)s and docstatus=2 and company=%(company)s and {0}
|
|
||||||
order by transaction_date asc limit 1""".format(cond), args)
|
|
||||||
if movement_entry:
|
|
||||||
location = movement_entry[0][0]
|
|
||||||
employee = movement_entry[0][1]
|
|
||||||
|
|
||||||
if not self.serial_no:
|
|
||||||
frappe.db.set_value("Asset", self.asset, "location", location)
|
|
||||||
|
|
||||||
if not employee and self.purpose in ['Receipt', 'Transfer']:
|
|
||||||
employee = self.to_employee
|
|
||||||
|
|
||||||
if self.serial_no:
|
|
||||||
for d in get_serial_nos(self.serial_no):
|
|
||||||
if (location or (self.purpose == 'Issue' and self.source_location)):
|
|
||||||
frappe.db.set_value('Serial No', d, 'location', location)
|
|
||||||
|
|
||||||
if employee or self.docstatus==2 or self.purpose == 'Issue':
|
|
||||||
frappe.db.set_value('Serial No', d, 'employee', employee)
|
|
||||||
|
|||||||
@@ -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}],
|
||||||
def test_movement_for_serialized_asset(self):
|
reference_doctype = 'Purchase Receipt', reference_name = pr.name)
|
||||||
asset_item = "Test Serialized Asset Item"
|
|
||||||
pr = make_purchase_receipt(item_code=asset_item, rate = 1000, qty=3, location = "Mumbai")
|
# after issuing asset should belong to an employee not at a location
|
||||||
asset_name = frappe.db.get_value('Asset', {'purchase_receipt': pr.name}, 'name')
|
self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), None)
|
||||||
|
self.assertEqual(frappe.db.get_value("Asset", asset.name, "custodian"), employee)
|
||||||
|
|
||||||
|
def test_last_movement_cancellation(self):
|
||||||
|
pr = make_purchase_receipt(item_code="Macbook Pro",
|
||||||
|
qty=1, rate=100000.0, location="Test Location")
|
||||||
|
|
||||||
|
asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
|
||||||
asset = frappe.get_doc('Asset', asset_name)
|
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"
|
||||||
})
|
})
|
||||||
asset.submit()
|
if asset.docstatus == 0:
|
||||||
serial_nos = frappe.db.get_value('Asset Movement', {'reference_name': pr.name}, 'serial_no')
|
asset.submit()
|
||||||
|
|
||||||
|
if not frappe.db.exists("Location", "Test Location 2"):
|
||||||
|
frappe.get_doc({
|
||||||
|
'doctype': 'Location',
|
||||||
|
'location_name': 'Test Location 2'
|
||||||
|
}).insert()
|
||||||
|
|
||||||
|
movement = frappe.get_doc({'doctype': 'Asset Movement', 'reference_name': pr.name })
|
||||||
|
self.assertRaises(frappe.ValidationError, movement.cancel)
|
||||||
|
|
||||||
mov1 = create_asset_movement(asset=asset_name, purpose = 'Transfer',
|
movement1 = create_asset_movement(purpose = 'Transfer', company = asset.company,
|
||||||
company=asset.company, source_location = "Mumbai", target_location="Pune", serial_no=serial_nos)
|
assets = [{ 'asset': asset.name , 'source_location': 'Test Location', 'target_location': 'Test Location 2'}],
|
||||||
self.assertEqual(mov1.target_location, "Pune")
|
reference_doctype = 'Purchase Receipt', reference_name = pr.name)
|
||||||
|
self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location 2")
|
||||||
|
|
||||||
serial_no = frappe.db.get_value('Serial No', {'asset': asset_name}, 'name')
|
movement1.cancel()
|
||||||
|
self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location")
|
||||||
employee = make_employee("testassetemp@example.com")
|
|
||||||
create_asset_movement(asset=asset_name, purpose = 'Transfer',
|
|
||||||
company=asset.company, serial_no=serial_no, to_employee=employee)
|
|
||||||
|
|
||||||
self.assertEqual(frappe.db.get_value('Serial No', serial_no, 'employee'), employee)
|
|
||||||
|
|
||||||
create_asset_movement(asset=asset_name, purpose = 'Transfer', company=asset.company,
|
|
||||||
serial_no=serial_no, from_employee=employee, to_employee="_T-Employee-00001")
|
|
||||||
|
|
||||||
self.assertEqual(frappe.db.get_value('Serial No', serial_no, 'location'), "Pune")
|
|
||||||
|
|
||||||
mov4 = create_asset_movement(asset=asset_name, purpose = 'Transfer',
|
|
||||||
company=asset.company, source_location = "Pune", target_location="Nagpur", serial_no=serial_nos)
|
|
||||||
self.assertEqual(mov4.target_location, "Nagpur")
|
|
||||||
self.assertEqual(frappe.db.get_value('Serial No', serial_no, 'location'), "Nagpur")
|
|
||||||
self.assertEqual(frappe.db.get_value('Serial No', serial_no, 'employee'), "_T-Employee-00001")
|
|
||||||
|
|
||||||
def create_asset_movement(**args):
|
def create_asset_movement(**args):
|
||||||
args = frappe._dict(args)
|
args = frappe._dict(args)
|
||||||
@@ -109,22 +113,14 @@ 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()
|
||||||
movement.submit()
|
movement.submit()
|
||||||
|
|
||||||
@@ -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.###'})
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
# import frappe
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
|
||||||
class AssetSettings(Document):
|
class AssetMovementItem(Document):
|
||||||
pass
|
pass
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
|
||||||
// For license information, please see license.txt
|
|
||||||
|
|
||||||
frappe.ui.form.on('Asset Settings', {
|
|
||||||
});
|
|
||||||
@@ -1,148 +0,0 @@
|
|||||||
{
|
|
||||||
"allow_copy": 0,
|
|
||||||
"allow_events_in_timeline": 0,
|
|
||||||
"allow_guest_to_view": 0,
|
|
||||||
"allow_import": 0,
|
|
||||||
"allow_rename": 0,
|
|
||||||
"beta": 0,
|
|
||||||
"creation": "2018-01-03 10:30:32.983381",
|
|
||||||
"custom": 0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "DocType",
|
|
||||||
"document_type": "",
|
|
||||||
"editable_grid": 1,
|
|
||||||
"engine": "InnoDB",
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fetch_if_empty": 0,
|
|
||||||
"fieldname": "depreciation_options",
|
|
||||||
"fieldtype": "Section Break",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Depreciation Options",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fetch_if_empty": 0,
|
|
||||||
"fieldname": "disable_cwip_accounting",
|
|
||||||
"fieldtype": "Check",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Disable CWIP Accounting",
|
|
||||||
"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": 1,
|
|
||||||
"istable": 0,
|
|
||||||
"max_attachments": 0,
|
|
||||||
"modified": "2019-05-26 18:31:19.930563",
|
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": "Assets",
|
|
||||||
"name": "Asset Settings",
|
|
||||||
"name_case": "",
|
|
||||||
"owner": "Administrator",
|
|
||||||
"permissions": [
|
|
||||||
{
|
|
||||||
"amend": 0,
|
|
||||||
"cancel": 0,
|
|
||||||
"create": 1,
|
|
||||||
"delete": 1,
|
|
||||||
"email": 1,
|
|
||||||
"export": 0,
|
|
||||||
"if_owner": 0,
|
|
||||||
"import": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print": 1,
|
|
||||||
"read": 1,
|
|
||||||
"report": 0,
|
|
||||||
"role": "System Manager",
|
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 1,
|
|
||||||
"submit": 0,
|
|
||||||
"write": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"amend": 0,
|
|
||||||
"cancel": 0,
|
|
||||||
"create": 1,
|
|
||||||
"delete": 1,
|
|
||||||
"email": 1,
|
|
||||||
"export": 0,
|
|
||||||
"if_owner": 0,
|
|
||||||
"import": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print": 1,
|
|
||||||
"read": 1,
|
|
||||||
"report": 0,
|
|
||||||
"role": "Accounts Manager",
|
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 1,
|
|
||||||
"submit": 0,
|
|
||||||
"write": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"quick_entry": 1,
|
|
||||||
"read_only": 0,
|
|
||||||
"read_only_onload": 0,
|
|
||||||
"show_name_in_global_search": 0,
|
|
||||||
"sort_field": "modified",
|
|
||||||
"sort_order": "DESC",
|
|
||||||
"track_changes": 1,
|
|
||||||
"track_seen": 0,
|
|
||||||
"track_views": 0
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
/* eslint-disable */
|
|
||||||
// rename this file from _test_[name] to test_[name] to activate
|
|
||||||
// and remove above this line
|
|
||||||
|
|
||||||
QUnit.test("test: Asset Settings", function (assert) {
|
|
||||||
let done = assert.async();
|
|
||||||
|
|
||||||
// number of asserts
|
|
||||||
assert.expect(1);
|
|
||||||
|
|
||||||
frappe.run_serially([
|
|
||||||
// insert a new Asset Settings
|
|
||||||
() => frappe.tests.make('Asset Settings', [
|
|
||||||
// values to be set
|
|
||||||
{key: 'value'}
|
|
||||||
]),
|
|
||||||
() => {
|
|
||||||
assert.equal(cur_frm.doc.key, 'value');
|
|
||||||
},
|
|
||||||
() => done()
|
|
||||||
]);
|
|
||||||
|
|
||||||
});
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
|
||||||
# See license.txt
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
class TestAssetSettings(unittest.TestCase):
|
|
||||||
pass
|
|
||||||
@@ -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",
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|
||||||
@@ -23,6 +24,12 @@ class AssetValueAdjustment(Document):
|
|||||||
frappe.throw(_("Cancel the journal entry {0} first").format(self.journal_entry))
|
frappe.throw(_("Cancel the journal entry {0} first").format(self.journal_entry))
|
||||||
|
|
||||||
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)
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -1,537 +1,168 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"creation": "2013-02-22 01:27:42",
|
||||||
"allow_events_in_timeline": 0,
|
"doctype": "DocType",
|
||||||
"allow_guest_to_view": 0,
|
"editable_grid": 1,
|
||||||
"allow_import": 0,
|
"engine": "InnoDB",
|
||||||
"allow_rename": 0,
|
"field_order": [
|
||||||
"beta": 0,
|
"main_item_code",
|
||||||
"creation": "2013-02-22 01:27:42",
|
"rm_item_code",
|
||||||
"custom": 0,
|
"description",
|
||||||
"docstatus": 0,
|
"batch_no",
|
||||||
"doctype": "DocType",
|
"serial_no",
|
||||||
"editable_grid": 1,
|
"col_break1",
|
||||||
"engine": "InnoDB",
|
"required_qty",
|
||||||
|
"consumed_qty",
|
||||||
|
"stock_uom",
|
||||||
|
"rate",
|
||||||
|
"amount",
|
||||||
|
"conversion_factor",
|
||||||
|
"current_stock",
|
||||||
|
"reference_name",
|
||||||
|
"bom_detail_no"
|
||||||
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "main_item_code",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Link",
|
||||||
"allow_on_submit": 0,
|
"in_list_view": 1,
|
||||||
"bold": 0,
|
"label": "Item Code",
|
||||||
"collapsible": 0,
|
"oldfieldname": "main_item_code",
|
||||||
"columns": 0,
|
"oldfieldtype": "Data",
|
||||||
"fieldname": "main_item_code",
|
"options": "Item",
|
||||||
"fieldtype": "Link",
|
"read_only": 1
|
||||||
"hidden": 0,
|
},
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Item Code",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"oldfieldname": "main_item_code",
|
|
||||||
"oldfieldtype": "Data",
|
|
||||||
"options": "Item",
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "rm_item_code",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Link",
|
||||||
"allow_on_submit": 0,
|
"in_list_view": 1,
|
||||||
"bold": 0,
|
"label": "Raw Material Item Code",
|
||||||
"collapsible": 0,
|
"oldfieldname": "rm_item_code",
|
||||||
"columns": 0,
|
"oldfieldtype": "Data",
|
||||||
"fieldname": "rm_item_code",
|
"options": "Item",
|
||||||
"fieldtype": "Link",
|
"read_only": 1
|
||||||
"hidden": 0,
|
},
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Raw Material Item Code",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"oldfieldname": "rm_item_code",
|
|
||||||
"oldfieldtype": "Data",
|
|
||||||
"options": "Item",
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "description",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Text Editor",
|
||||||
"allow_on_submit": 0,
|
"in_global_search": 1,
|
||||||
"bold": 0,
|
"in_list_view": 1,
|
||||||
"collapsible": 0,
|
"label": "Description",
|
||||||
"columns": 0,
|
"oldfieldname": "description",
|
||||||
"fieldname": "description",
|
"oldfieldtype": "Data",
|
||||||
"fieldtype": "Text Editor",
|
"print_width": "300px",
|
||||||
"hidden": 0,
|
"read_only": 1,
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 1,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Description",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"oldfieldname": "description",
|
|
||||||
"oldfieldtype": "Data",
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"print_width": "300px",
|
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0,
|
|
||||||
"width": "300px"
|
"width": "300px"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "batch_no",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Link",
|
||||||
"allow_on_submit": 0,
|
"label": "Batch No",
|
||||||
"bold": 0,
|
"no_copy": 1,
|
||||||
"collapsible": 0,
|
"options": "Batch"
|
||||||
"columns": 0,
|
},
|
||||||
"fieldname": "batch_no",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Batch No",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 1,
|
|
||||||
"options": "Batch",
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "serial_no",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Text",
|
||||||
"allow_on_submit": 0,
|
"label": "Serial No",
|
||||||
"bold": 0,
|
"no_copy": 1
|
||||||
"collapsible": 0,
|
},
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "serial_no",
|
|
||||||
"fieldtype": "Text",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Serial No",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 1,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "col_break1",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Column Break"
|
||||||
"allow_on_submit": 0,
|
},
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "col_break1",
|
|
||||||
"fieldtype": "Column Break",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "required_qty",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Float",
|
||||||
"allow_on_submit": 0,
|
"in_list_view": 1,
|
||||||
"bold": 0,
|
"label": "Required Qty",
|
||||||
"collapsible": 0,
|
"oldfieldname": "required_qty",
|
||||||
"columns": 0,
|
"oldfieldtype": "Currency",
|
||||||
"fieldname": "required_qty",
|
"read_only": 1
|
||||||
"fieldtype": "Float",
|
},
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Required Qty",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"oldfieldname": "required_qty",
|
|
||||||
"oldfieldtype": "Currency",
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "consumed_qty",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Float",
|
||||||
"allow_on_submit": 0,
|
"label": "Consumed Qty",
|
||||||
"bold": 0,
|
"oldfieldname": "consumed_qty",
|
||||||
"collapsible": 0,
|
"oldfieldtype": "Currency",
|
||||||
"columns": 0,
|
"read_only": 1,
|
||||||
"fieldname": "consumed_qty",
|
"reqd": 1
|
||||||
"fieldtype": "Float",
|
},
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Consumed Qty",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"oldfieldname": "consumed_qty",
|
|
||||||
"oldfieldtype": "Currency",
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "stock_uom",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Link",
|
||||||
"allow_on_submit": 0,
|
"label": "Stock Uom",
|
||||||
"bold": 0,
|
"oldfieldname": "stock_uom",
|
||||||
"collapsible": 0,
|
"oldfieldtype": "Data",
|
||||||
"columns": 0,
|
"options": "UOM",
|
||||||
"fieldname": "stock_uom",
|
"read_only": 1
|
||||||
"fieldtype": "Link",
|
},
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Stock Uom",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"oldfieldname": "stock_uom",
|
|
||||||
"oldfieldtype": "Data",
|
|
||||||
"options": "UOM",
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "rate",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Currency",
|
||||||
"allow_on_submit": 0,
|
"label": "Rate",
|
||||||
"bold": 0,
|
"oldfieldname": "rate",
|
||||||
"collapsible": 0,
|
"oldfieldtype": "Currency",
|
||||||
"columns": 0,
|
"options": "Company:company:default_currency",
|
||||||
"fieldname": "rate",
|
"read_only": 1
|
||||||
"fieldtype": "Currency",
|
},
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Rate",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"oldfieldname": "rate",
|
|
||||||
"oldfieldtype": "Currency",
|
|
||||||
"options": "Company:company:default_currency",
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "amount",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Currency",
|
||||||
"allow_on_submit": 0,
|
"label": "Amount",
|
||||||
"bold": 0,
|
"oldfieldname": "amount",
|
||||||
"collapsible": 0,
|
"oldfieldtype": "Currency",
|
||||||
"columns": 0,
|
"options": "Company:company:default_currency",
|
||||||
"fieldname": "amount",
|
"read_only": 1
|
||||||
"fieldtype": "Currency",
|
},
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Amount",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"oldfieldname": "amount",
|
|
||||||
"oldfieldtype": "Currency",
|
|
||||||
"options": "Company:company:default_currency",
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "conversion_factor",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Float",
|
||||||
"allow_on_submit": 0,
|
"hidden": 1,
|
||||||
"bold": 0,
|
"label": "Conversion Factor",
|
||||||
"collapsible": 0,
|
"oldfieldname": "conversion_factor",
|
||||||
"columns": 0,
|
"oldfieldtype": "Currency",
|
||||||
"fieldname": "conversion_factor",
|
"read_only": 1
|
||||||
"fieldtype": "Float",
|
},
|
||||||
"hidden": 1,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Conversion Factor",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"oldfieldname": "conversion_factor",
|
|
||||||
"oldfieldtype": "Currency",
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "current_stock",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Float",
|
||||||
"allow_on_submit": 0,
|
"in_list_view": 1,
|
||||||
"bold": 0,
|
"label": "Current Stock",
|
||||||
"collapsible": 0,
|
"oldfieldname": "current_stock",
|
||||||
"columns": 0,
|
"oldfieldtype": "Currency",
|
||||||
"fieldname": "current_stock",
|
"read_only": 1
|
||||||
"fieldtype": "Float",
|
},
|
||||||
"hidden": 1,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Current Stock",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"oldfieldname": "current_stock",
|
|
||||||
"oldfieldtype": "Currency",
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "reference_name",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Data",
|
||||||
"allow_on_submit": 0,
|
"hidden": 1,
|
||||||
"bold": 0,
|
"in_list_view": 1,
|
||||||
"collapsible": 0,
|
"label": "Reference Name",
|
||||||
"columns": 0,
|
"oldfieldname": "reference_name",
|
||||||
"fieldname": "reference_name",
|
"oldfieldtype": "Data",
|
||||||
"fieldtype": "Data",
|
"read_only": 1
|
||||||
"hidden": 1,
|
},
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Reference Name",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"oldfieldname": "reference_name",
|
|
||||||
"oldfieldtype": "Data",
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "bom_detail_no",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Data",
|
||||||
"allow_on_submit": 0,
|
"hidden": 1,
|
||||||
"bold": 0,
|
"in_list_view": 1,
|
||||||
"collapsible": 0,
|
"label": "BOM Detail No",
|
||||||
"columns": 0,
|
"oldfieldname": "bom_detail_no",
|
||||||
"fieldname": "bom_detail_no",
|
"oldfieldtype": "Data",
|
||||||
"fieldtype": "Data",
|
"read_only": 1
|
||||||
"hidden": 1,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "BOM Detail No",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"oldfieldname": "bom_detail_no",
|
|
||||||
"oldfieldtype": "Data",
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"has_web_view": 0,
|
"idx": 1,
|
||||||
"hide_heading": 0,
|
"istable": 1,
|
||||||
"hide_toolbar": 0,
|
"modified": "2019-11-21 16:25:29.909112",
|
||||||
"idx": 1,
|
"modified_by": "Administrator",
|
||||||
"image_view": 0,
|
"module": "Buying",
|
||||||
"in_create": 0,
|
"name": "Purchase Receipt Item Supplied",
|
||||||
"is_submittable": 0,
|
"owner": "wasim@webnotestech.com",
|
||||||
"issingle": 0,
|
"permissions": [],
|
||||||
"istable": 1,
|
"sort_field": "modified",
|
||||||
"max_attachments": 0,
|
"sort_order": "DESC",
|
||||||
"modified": "2019-01-07 16:51:59.536291",
|
"track_changes": 1
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": "Buying",
|
|
||||||
"name": "Purchase Receipt Item Supplied",
|
|
||||||
"owner": "wasim@webnotestech.com",
|
|
||||||
"permissions": [],
|
|
||||||
"quick_entry": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"read_only_onload": 0,
|
|
||||||
"show_name_in_global_search": 0,
|
|
||||||
"sort_field": "modified",
|
|
||||||
"sort_order": "DESC",
|
|
||||||
"track_changes": 1,
|
|
||||||
"track_seen": 0,
|
|
||||||
"track_views": 0
|
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
14
erpnext/change_log/v12/v12_2_0.md
Normal file
14
erpnext/change_log/v12/v12_2_0.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# Version 12.2.0 Release Notes
|
||||||
|
|
||||||
|
### Accounting
|
||||||
|
|
||||||
|
1. Fixed Asset
|
||||||
|
- "Enable CWIP" options moved to Asset Category from Asset Settings
|
||||||
|
- Removed Asset link from Purchase Receipt Item table
|
||||||
|
- Enhanced Asset master
|
||||||
|
- Asset Movement now handles movement of multiple assets
|
||||||
|
- Introduced monthly depreciation
|
||||||
|
2. GL Entries for Landed Cost Voucher now posted directly against individual Charges account
|
||||||
|
3. Optimization of BOM Update Tool
|
||||||
|
4. Syncing of Stock and Account balance is enforced, in case of perpetual inventory
|
||||||
|
5. Rendered email template in Email Campaign
|
||||||
@@ -21,10 +21,6 @@ def get_data():
|
|||||||
"name": "Asset Category",
|
"name": "Asset Category",
|
||||||
"onboard": 1,
|
"onboard": 1,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"type": "doctype",
|
|
||||||
"name": "Asset Settings",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "doctype",
|
"type": "doctype",
|
||||||
"name": "Asset Movement",
|
"name": "Asset Movement",
|
||||||
|
|||||||
@@ -46,6 +46,16 @@ def get_data():
|
|||||||
"name": "Contract",
|
"name": "Contract",
|
||||||
"description": _("Helps you keep tracks of Contracts based on Supplier, Customer and Employee"),
|
"description": _("Helps you keep tracks of Contracts based on Supplier, Customer and Employee"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "doctype",
|
||||||
|
"name": "Appointment",
|
||||||
|
"description" : _("Helps you manage appointments with your leads"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "doctype",
|
||||||
|
"name": "Newsletter",
|
||||||
|
"label": _("Newsletter"),
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -165,6 +175,11 @@ def get_data():
|
|||||||
"type": "doctype",
|
"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",
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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,24 +285,42 @@ 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"):
|
||||||
# if user changed the discount percentage then set user's discount percentage ?
|
self.apply_pricing_rule_on_items(item, ret)
|
||||||
item.set("pricing_rules", ret.get("pricing_rules"))
|
|
||||||
item.set("discount_percentage", ret.get("discount_percentage"))
|
|
||||||
item.set("discount_amount", ret.get("discount_amount"))
|
|
||||||
if ret.get("pricing_rule_for") == "Rate":
|
|
||||||
item.set("price_list_rate", ret.get("price_list_rate"))
|
|
||||||
|
|
||||||
if item.get("price_list_rate"):
|
|
||||||
item.rate = flt(item.price_list_rate *
|
|
||||||
(1.0 - (flt(item.discount_percentage) / 100.0)), item.precision("rate"))
|
|
||||||
|
|
||||||
if item.get('discount_amount'):
|
|
||||||
item.rate = item.price_list_rate - item.discount_amount
|
|
||||||
|
|
||||||
if self.doctype == "Purchase Invoice":
|
if self.doctype == "Purchase Invoice":
|
||||||
self.set_expense_account(for_validate)
|
self.set_expense_account(for_validate)
|
||||||
|
|
||||||
|
def apply_pricing_rule_on_items(self, item, pricing_rule_args):
|
||||||
|
if not pricing_rule_args.get("validate_applied_rule", 0):
|
||||||
|
# if user changed the discount percentage then set user's discount percentage ?
|
||||||
|
if pricing_rule_args.get("price_or_product_discount") == 'Price':
|
||||||
|
item.set("pricing_rules", pricing_rule_args.get("pricing_rules"))
|
||||||
|
item.set("discount_percentage", pricing_rule_args.get("discount_percentage"))
|
||||||
|
item.set("discount_amount", pricing_rule_args.get("discount_amount"))
|
||||||
|
if pricing_rule_args.get("pricing_rule_for") == "Rate":
|
||||||
|
item.set("price_list_rate", pricing_rule_args.get("price_list_rate"))
|
||||||
|
|
||||||
|
if item.get("price_list_rate"):
|
||||||
|
item.rate = flt(item.price_list_rate *
|
||||||
|
(1.0 - (flt(item.discount_percentage) / 100.0)), item.precision("rate"))
|
||||||
|
|
||||||
|
if item.get('discount_amount'):
|
||||||
|
item.rate = item.price_list_rate - item.discount_amount
|
||||||
|
|
||||||
|
elif pricing_rule_args.get('free_item'):
|
||||||
|
apply_pricing_rule_for_free_items(self, pricing_rule_args)
|
||||||
|
|
||||||
|
elif pricing_rule_args.get("validate_applied_rule"):
|
||||||
|
for pricing_rule in get_applied_pricing_rules(item):
|
||||||
|
pricing_rule_doc = frappe.get_cached_doc("Pricing Rule", pricing_rule)
|
||||||
|
for field in ['discount_percentage', 'discount_amount', 'rate']:
|
||||||
|
if item.get(field) < pricing_rule_doc.get(field):
|
||||||
|
title = get_link_to_form("Pricing Rule", pricing_rule)
|
||||||
|
|
||||||
|
frappe.msgprint(_("Row {0}: user has not applied the rule {1} on the item {2}")
|
||||||
|
.format(item.idx, frappe.bold(title), frappe.bold(item.item_code)))
|
||||||
|
|
||||||
def set_taxes(self):
|
def set_taxes(self):
|
||||||
if not self.meta.get_field("taxes"):
|
if not self.meta.get_field("taxes"):
|
||||||
return
|
return
|
||||||
@@ -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:
|
||||||
|
|||||||
@@ -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,57 +631,49 @@ 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:
|
||||||
continue
|
# need to delete movements to delete assets otherwise throws link exists error
|
||||||
|
movements = frappe.db.sql(
|
||||||
|
"""SELECT asm.name
|
||||||
|
FROM `tabAsset Movement` asm, `tabAsset Movement Item` asm_item
|
||||||
|
WHERE asm_item.parent=asm.name and asm_item.asset=%s""", asset.name, as_dict=1)
|
||||||
|
for movement in movements:
|
||||||
|
frappe.delete_doc('Asset Movement', movement.name, force=1)
|
||||||
|
frappe.delete_doc("Asset", asset.name, force=1)
|
||||||
|
continue
|
||||||
|
|
||||||
if self.docstatus in [0, 1] and not asset.get(field):
|
if self.docstatus in [0, 1] and not asset.get(field):
|
||||||
asset.set(field, self.name)
|
asset.set(field, self.name)
|
||||||
asset.purchase_date = self.posting_date
|
asset.purchase_date = self.posting_date
|
||||||
asset.supplier = self.supplier
|
asset.supplier = self.supplier
|
||||||
elif self.docstatus == 2:
|
elif self.docstatus == 2:
|
||||||
asset.set(field, None)
|
if asset.docstatus == 0:
|
||||||
asset.supplier = None
|
asset.set(field, None)
|
||||||
|
asset.supplier = None
|
||||||
|
if asset.docstatus == 1 and delete_asset:
|
||||||
|
frappe.throw(_('Cannot cancel this document as it is linked with submitted asset {0}.\
|
||||||
|
Please cancel the it to continue.').format(asset.name))
|
||||||
|
|
||||||
asset.flags.ignore_validate_update_after_submit = True
|
asset.flags.ignore_validate_update_after_submit = True
|
||||||
asset.flags.ignore_mandatory = True
|
asset.flags.ignore_mandatory = True
|
||||||
if asset.docstatus == 0:
|
if asset.docstatus == 0:
|
||||||
asset.flags.ignore_validate = True
|
asset.flags.ignore_validate = True
|
||||||
|
|
||||||
asset.save()
|
asset.save()
|
||||||
|
|
||||||
def delete_linked_asset(self):
|
def delete_linked_asset(self):
|
||||||
if self.doctype == 'Purchase Invoice' and not self.get('update_stock'):
|
if self.doctype == 'Purchase Invoice' and not self.get('update_stock'):
|
||||||
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 +747,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)
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
@@ -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'):
|
||||||
|
|||||||
0
erpnext/crm/doctype/appointment/__init__.py
Normal file
0
erpnext/crm/doctype/appointment/__init__.py
Normal file
17
erpnext/crm/doctype/appointment/appointment.js
Normal file
17
erpnext/crm/doctype/appointment/appointment.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
frappe.ui.form.on('Appointment', {
|
||||||
|
refresh: function(frm) {
|
||||||
|
if(frm.doc.lead){
|
||||||
|
frm.add_custom_button(frm.doc.lead,()=>{
|
||||||
|
frappe.set_route("Form", "Lead", frm.doc.lead);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if(frm.doc.calendar_event){
|
||||||
|
frm.add_custom_button(__(frm.doc.calendar_event),()=>{
|
||||||
|
frappe.set_route("Form", "Event", frm.doc.calendar_event);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
153
erpnext/crm/doctype/appointment/appointment.json
Normal file
153
erpnext/crm/doctype/appointment/appointment.json
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
{
|
||||||
|
"autoname": "format:APMT-{customer_name}-{####}",
|
||||||
|
"creation": "2019-08-27 10:48:27.926283",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"scheduled_time",
|
||||||
|
"status",
|
||||||
|
"customer_details_section",
|
||||||
|
"customer_name",
|
||||||
|
"customer_phone_number",
|
||||||
|
"customer_skype",
|
||||||
|
"customer_email",
|
||||||
|
"col_br_2",
|
||||||
|
"customer_details",
|
||||||
|
"linked_docs_section",
|
||||||
|
"lead",
|
||||||
|
"col_br_3",
|
||||||
|
"calendar_event"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "customer_details_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Customer Details"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "customer_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Name",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "customer_phone_number",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Phone Number"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "customer_skype",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Skype ID"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "customer_details",
|
||||||
|
"fieldtype": "Long Text",
|
||||||
|
"label": "Details"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "scheduled_time",
|
||||||
|
"fieldtype": "Datetime",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Scheduled Time",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "status",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Status",
|
||||||
|
"options": "Open\nUnverified\nClosed",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "lead",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Lead",
|
||||||
|
"options": "Lead"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "calendar_event",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Calendar Event",
|
||||||
|
"options": "Event"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "col_br_2",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "customer_email",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Email",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "linked_docs_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Linked Documents"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "col_br_3",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"modified": "2019-10-14 15:23:54.630731",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "CRM",
|
||||||
|
"name": "Appointment",
|
||||||
|
"name_case": "UPPER CASE",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "System Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Guest",
|
||||||
|
"share": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Sales Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Sales User",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"quick_entry": 1,
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
||||||
223
erpnext/crm/doctype/appointment/appointment.py
Normal file
223
erpnext/crm/doctype/appointment/appointment.py
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import urllib
|
||||||
|
from collections import Counter
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
from frappe import _
|
||||||
|
from frappe.model.document import Document
|
||||||
|
from frappe.utils import get_url
|
||||||
|
from frappe.utils.verified_command import verify_request, get_signed_params
|
||||||
|
|
||||||
|
|
||||||
|
class Appointment(Document):
|
||||||
|
|
||||||
|
def find_lead_by_email(self):
|
||||||
|
lead_list = frappe.get_list(
|
||||||
|
'Lead', filters={'email_id': self.customer_email}, ignore_permissions=True)
|
||||||
|
if lead_list:
|
||||||
|
return lead_list[0].name
|
||||||
|
return None
|
||||||
|
|
||||||
|
def before_insert(self):
|
||||||
|
number_of_appointments_in_same_slot = frappe.db.count(
|
||||||
|
'Appointment', filters={'scheduled_time': self.scheduled_time})
|
||||||
|
number_of_agents = frappe.db.get_single_value('Appointment Booking Settings', 'number_of_agents')
|
||||||
|
if not number_of_agents == 0:
|
||||||
|
if (number_of_appointments_in_same_slot >= number_of_agents):
|
||||||
|
frappe.throw('Time slot is not available')
|
||||||
|
# Link lead
|
||||||
|
if not self.lead:
|
||||||
|
self.lead = self.find_lead_by_email()
|
||||||
|
|
||||||
|
def after_insert(self):
|
||||||
|
if self.lead:
|
||||||
|
# Create Calendar event
|
||||||
|
self.auto_assign()
|
||||||
|
self.create_calendar_event()
|
||||||
|
else:
|
||||||
|
# Set status to unverified
|
||||||
|
self.status = 'Unverified'
|
||||||
|
# Send email to confirm
|
||||||
|
self.send_confirmation_email()
|
||||||
|
|
||||||
|
def send_confirmation_email(self):
|
||||||
|
verify_url = self._get_verify_url()
|
||||||
|
template = 'confirm_appointment'
|
||||||
|
args = {
|
||||||
|
"link":verify_url,
|
||||||
|
"site_url":frappe.utils.get_url(),
|
||||||
|
"full_name":self.customer_name,
|
||||||
|
}
|
||||||
|
frappe.sendmail(recipients=[self.customer_email],
|
||||||
|
template=template,
|
||||||
|
args=args,
|
||||||
|
subject=_('Appointment Confirmation'))
|
||||||
|
if frappe.session.user == "Guest":
|
||||||
|
frappe.msgprint(
|
||||||
|
'Please check your email to confirm the appointment')
|
||||||
|
else :
|
||||||
|
frappe.msgprint(
|
||||||
|
'Appointment was created. But no lead was found. Please check the email to confirm')
|
||||||
|
|
||||||
|
def on_change(self):
|
||||||
|
# Sync Calendar
|
||||||
|
if not self.calendar_event:
|
||||||
|
return
|
||||||
|
cal_event = frappe.get_doc('Event', self.calendar_event)
|
||||||
|
cal_event.starts_on = self.scheduled_time
|
||||||
|
cal_event.save(ignore_permissions=True)
|
||||||
|
|
||||||
|
|
||||||
|
def set_verified(self, email):
|
||||||
|
if not email == self.customer_email:
|
||||||
|
frappe.throw('Email verification failed.')
|
||||||
|
# Create new lead
|
||||||
|
self.create_lead_and_link()
|
||||||
|
# Remove unverified status
|
||||||
|
self.status = 'Open'
|
||||||
|
# Create calender event
|
||||||
|
self.auto_assign()
|
||||||
|
self.create_calendar_event()
|
||||||
|
self.save(ignore_permissions=True)
|
||||||
|
frappe.db.commit()
|
||||||
|
|
||||||
|
def create_lead_and_link(self):
|
||||||
|
# Return if already linked
|
||||||
|
if self.lead:
|
||||||
|
return
|
||||||
|
lead = frappe.get_doc({
|
||||||
|
'doctype': 'Lead',
|
||||||
|
'lead_name': self.customer_name,
|
||||||
|
'email_id': self.customer_email,
|
||||||
|
'notes': self.customer_details,
|
||||||
|
'phone': self.customer_phone_number,
|
||||||
|
})
|
||||||
|
lead.insert(ignore_permissions=True)
|
||||||
|
# Link lead
|
||||||
|
self.lead = lead.name
|
||||||
|
|
||||||
|
def auto_assign(self):
|
||||||
|
from frappe.desk.form.assign_to import add as add_assignemnt
|
||||||
|
existing_assignee = self.get_assignee_from_latest_opportunity()
|
||||||
|
if existing_assignee:
|
||||||
|
# If the latest opportunity is assigned to someone
|
||||||
|
# Assign the appointment to the same
|
||||||
|
add_assignemnt({
|
||||||
|
'doctype': self.doctype,
|
||||||
|
'name': self.name,
|
||||||
|
'assign_to': existing_assignee
|
||||||
|
})
|
||||||
|
return
|
||||||
|
if self._assign:
|
||||||
|
return
|
||||||
|
available_agents = _get_agents_sorted_by_asc_workload(
|
||||||
|
self.scheduled_time.date())
|
||||||
|
for agent in available_agents:
|
||||||
|
if(_check_agent_availability(agent, self.scheduled_time)):
|
||||||
|
agent = agent[0]
|
||||||
|
add_assignemnt({
|
||||||
|
'doctype': self.doctype,
|
||||||
|
'name': self.name,
|
||||||
|
'assign_to': agent
|
||||||
|
})
|
||||||
|
break
|
||||||
|
|
||||||
|
def get_assignee_from_latest_opportunity(self):
|
||||||
|
if not self.lead:
|
||||||
|
return None
|
||||||
|
if not frappe.db.exists('Lead', self.lead):
|
||||||
|
return None
|
||||||
|
opporutnities = frappe.get_list(
|
||||||
|
'Opportunity',
|
||||||
|
filters={
|
||||||
|
'party_name': self.lead,
|
||||||
|
},
|
||||||
|
ignore_permissions=True,
|
||||||
|
order_by='creation desc')
|
||||||
|
if not opporutnities:
|
||||||
|
return None
|
||||||
|
latest_opportunity = frappe.get_doc('Opportunity', opporutnities[0].name )
|
||||||
|
assignee = latest_opportunity._assign
|
||||||
|
if not assignee:
|
||||||
|
return None
|
||||||
|
assignee = frappe.parse_json(assignee)[0]
|
||||||
|
return assignee
|
||||||
|
|
||||||
|
def create_calendar_event(self):
|
||||||
|
if self.calendar_event:
|
||||||
|
return
|
||||||
|
appointment_event = frappe.get_doc({
|
||||||
|
'doctype': 'Event',
|
||||||
|
'subject': ' '.join(['Appointment with', self.customer_name]),
|
||||||
|
'starts_on': self.scheduled_time,
|
||||||
|
'status': 'Open',
|
||||||
|
'type': 'Public',
|
||||||
|
'send_reminder': frappe.db.get_single_value('Appointment Booking Settings', 'email_reminders'),
|
||||||
|
'event_participants': [dict(reference_doctype='Lead', reference_docname=self.lead)]
|
||||||
|
})
|
||||||
|
employee = _get_employee_from_user(self._assign)
|
||||||
|
if employee:
|
||||||
|
appointment_event.append('event_participants', dict(
|
||||||
|
reference_doctype='Employee',
|
||||||
|
reference_docname=employee.name))
|
||||||
|
appointment_event.insert(ignore_permissions=True)
|
||||||
|
self.calendar_event = appointment_event.name
|
||||||
|
self.save(ignore_permissions=True)
|
||||||
|
|
||||||
|
def _get_verify_url(self):
|
||||||
|
verify_route = '/book-appointment/verify'
|
||||||
|
params = {
|
||||||
|
'email': self.customer_email,
|
||||||
|
'appointment': self.name
|
||||||
|
}
|
||||||
|
return get_url(verify_route + '?' + get_signed_params(params))
|
||||||
|
|
||||||
|
|
||||||
|
def _get_agents_sorted_by_asc_workload(date):
|
||||||
|
appointments = frappe.db.get_list('Appointment', fields='*')
|
||||||
|
agent_list = _get_agent_list_as_strings()
|
||||||
|
if not appointments:
|
||||||
|
return agent_list
|
||||||
|
appointment_counter = Counter(agent_list)
|
||||||
|
for appointment in appointments:
|
||||||
|
assigned_to = frappe.parse_json(appointment._assign)
|
||||||
|
if not assigned_to:
|
||||||
|
continue
|
||||||
|
if (assigned_to[0] in agent_list) and appointment.scheduled_time.date() == date:
|
||||||
|
appointment_counter[assigned_to[0]] += 1
|
||||||
|
sorted_agent_list = appointment_counter.most_common()
|
||||||
|
sorted_agent_list.reverse()
|
||||||
|
return sorted_agent_list
|
||||||
|
|
||||||
|
|
||||||
|
def _get_agent_list_as_strings():
|
||||||
|
agent_list_as_strings = []
|
||||||
|
agent_list = frappe.get_doc('Appointment Booking Settings').agent_list
|
||||||
|
for agent in agent_list:
|
||||||
|
agent_list_as_strings.append(agent.user)
|
||||||
|
return agent_list_as_strings
|
||||||
|
|
||||||
|
|
||||||
|
def _check_agent_availability(agent_email, scheduled_time):
|
||||||
|
appointemnts_at_scheduled_time = frappe.get_list(
|
||||||
|
'Appointment', filters={'scheduled_time': scheduled_time})
|
||||||
|
for appointment in appointemnts_at_scheduled_time:
|
||||||
|
if appointment._assign == agent_email:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def _get_employee_from_user(user):
|
||||||
|
employee_docname = frappe.db.exists(
|
||||||
|
{'doctype': 'Employee', 'user_id': user})
|
||||||
|
if employee_docname:
|
||||||
|
# frappe.db.exists returns a tuple of a tuple
|
||||||
|
return frappe.get_doc('Employee', employee_docname[0][0])
|
||||||
|
return None
|
||||||
|
|
||||||
58
erpnext/crm/doctype/appointment/test_appointment.py
Normal file
58
erpnext/crm/doctype/appointment/test_appointment.py
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
|
# See license.txt
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
import unittest
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
|
||||||
|
def create_test_lead():
|
||||||
|
test_lead = frappe.db.exists({'doctype': 'Lead', 'lead_name': 'Test Lead'})
|
||||||
|
if test_lead:
|
||||||
|
return frappe.get_doc('Lead', test_lead[0][0])
|
||||||
|
test_lead = frappe.get_doc({
|
||||||
|
'doctype': 'Lead',
|
||||||
|
'lead_name': 'Test Lead',
|
||||||
|
'email_id': 'test@example.com'
|
||||||
|
})
|
||||||
|
test_lead.insert(ignore_permissions=True)
|
||||||
|
return test_lead
|
||||||
|
|
||||||
|
|
||||||
|
def create_test_appointments():
|
||||||
|
test_appointment = frappe.db.exists(
|
||||||
|
{'doctype': 'Appointment', 'scheduled_time':datetime.datetime.now(),'email':'test@example.com'})
|
||||||
|
if test_appointment:
|
||||||
|
return frappe.get_doc('Appointment', test_appointment[0][0])
|
||||||
|
test_appointment = frappe.get_doc({
|
||||||
|
'doctype': 'Appointment',
|
||||||
|
'email': 'test@example.com',
|
||||||
|
'status': 'Open',
|
||||||
|
'customer_name': 'Test Lead',
|
||||||
|
'customer_phone_number': '666',
|
||||||
|
'customer_skype': 'test',
|
||||||
|
'customer_email': 'test@example.com',
|
||||||
|
'scheduled_time': datetime.datetime.now()
|
||||||
|
})
|
||||||
|
test_appointment.insert()
|
||||||
|
return test_appointment
|
||||||
|
|
||||||
|
|
||||||
|
class TestAppointment(unittest.TestCase):
|
||||||
|
test_appointment = test_lead = None
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.test_lead = create_test_lead()
|
||||||
|
self.test_appointment = create_test_appointments()
|
||||||
|
|
||||||
|
def test_calendar_event_created(self):
|
||||||
|
cal_event = frappe.get_doc(
|
||||||
|
'Event', self.test_appointment.calendar_event)
|
||||||
|
self.assertEqual(cal_event.starts_on,
|
||||||
|
self.test_appointment.scheduled_time)
|
||||||
|
|
||||||
|
def test_lead_linked(self):
|
||||||
|
lead = frappe.get_doc('Lead', self.test_lead.name)
|
||||||
|
self.assertIsNotNone(lead)
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
frappe.ui.form.on('Appointment Booking Settings', 'validate',check_times);
|
||||||
|
function check_times(frm) {
|
||||||
|
$.each(frm.doc.availability_of_slots || [], function (i, d) {
|
||||||
|
let from_time = Date.parse('01/01/2019 ' + d.from_time);
|
||||||
|
let to_time = Date.parse('01/01/2019 ' + d.to_time);
|
||||||
|
if (from_time > to_time) {
|
||||||
|
frappe.throw(__(`In row ${i + 1} of Appointment Booking Slots : "To Time" must be later than "From Time"`));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
{
|
||||||
|
"creation": "2019-08-27 10:56:48.309824",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"enable_scheduling",
|
||||||
|
"agent_detail_section",
|
||||||
|
"availability_of_slots",
|
||||||
|
"number_of_agents",
|
||||||
|
"agent_list",
|
||||||
|
"holiday_list",
|
||||||
|
"appointment_details_section",
|
||||||
|
"appointment_duration",
|
||||||
|
"email_reminders",
|
||||||
|
"advance_booking_days",
|
||||||
|
"success_details",
|
||||||
|
"success_redirect_url"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "availability_of_slots",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"label": "Availability Of Slots",
|
||||||
|
"options": "Appointment Booking Slots",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "1",
|
||||||
|
"fieldname": "number_of_agents",
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"hidden": 1,
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Number of Concurrent Appointments",
|
||||||
|
"read_only": 1,
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "holiday_list",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Holiday List",
|
||||||
|
"options": "Holiday List",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "60",
|
||||||
|
"fieldname": "appointment_duration",
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"label": "Appointment Duration (In Minutes)",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"description": "Notify customer and agent via email on the day of the appointment.",
|
||||||
|
"fieldname": "email_reminders",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Notify Via Email"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "7",
|
||||||
|
"fieldname": "advance_booking_days",
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"label": "Number of days appointments can be booked in advance",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "agent_list",
|
||||||
|
"fieldtype": "Table MultiSelect",
|
||||||
|
"label": "Agents",
|
||||||
|
"options": "Assignment Rule User",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "enable_scheduling",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Enable Appointment Scheduling",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "agent_detail_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Agent Details"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "appointment_details_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Appointment Details"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "success_details",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Success Settings"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Leave blank for home.\nThis is relative to site URL, for example \"about\" will redirect to \"https://yoursitename.com/about\"",
|
||||||
|
"fieldname": "success_redirect_url",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Success Redirect URL"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"issingle": 1,
|
||||||
|
"modified": "2019-11-26 12:14:17.669366",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "CRM",
|
||||||
|
"name": "Appointment Booking Settings",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"role": "System Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"email": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"role": "Guest",
|
||||||
|
"share": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"email": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"role": "HR Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"role": "Sales Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"quick_entry": 1,
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
import frappe
|
||||||
|
from frappe import _
|
||||||
|
import datetime
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
|
class AppointmentBookingSettings(Document):
|
||||||
|
agent_list = [] #Hack
|
||||||
|
min_date = '01/01/1970 '
|
||||||
|
format_string = "%d/%m/%Y %H:%M:%S"
|
||||||
|
|
||||||
|
def validate(self):
|
||||||
|
self.validate_availability_of_slots()
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
self.number_of_agents = len(self.agent_list)
|
||||||
|
super(AppointmentBookingSettings, self).save()
|
||||||
|
|
||||||
|
def validate_availability_of_slots(self):
|
||||||
|
for record in self.availability_of_slots:
|
||||||
|
from_time = datetime.datetime.strptime(
|
||||||
|
self.min_date+record.from_time, self.format_string)
|
||||||
|
to_time = datetime.datetime.strptime(
|
||||||
|
self.min_date+record.to_time, self.format_string)
|
||||||
|
timedelta = to_time-from_time
|
||||||
|
self.validate_from_and_to_time(from_time, to_time)
|
||||||
|
self.duration_is_divisible(from_time, to_time)
|
||||||
|
|
||||||
|
def validate_from_and_to_time(self, from_time, to_time):
|
||||||
|
if from_time > to_time:
|
||||||
|
err_msg = _('<b>From Time</b> cannot be later than <b>To Time</b> for {0}').format(record.day_of_week)
|
||||||
|
frappe.throw(_(err_msg))
|
||||||
|
|
||||||
|
def duration_is_divisible(self, from_time, to_time):
|
||||||
|
timedelta = to_time - from_time
|
||||||
|
if timedelta.total_seconds() % (self.appointment_duration * 60):
|
||||||
|
frappe.throw(
|
||||||
|
_('The difference between from time and To Time must be a multiple of Appointment'))
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
|
# See license.txt
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
class TestAppointmentBookingSettings(unittest.TestCase):
|
||||||
|
pass
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
{
|
||||||
|
"creation": "2019-11-19 10:49:49.494927",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"day_of_week",
|
||||||
|
"from_time",
|
||||||
|
"to_time"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "day_of_week",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Day Of Week",
|
||||||
|
"options": "Sunday\nMonday\nTuesday\nWednesday\nThursday\nFriday\nSaturday",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "from_time",
|
||||||
|
"fieldtype": "Time",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "From Time ",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "to_time",
|
||||||
|
"fieldtype": "Time",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "To Time",
|
||||||
|
"reqd": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"istable": 1,
|
||||||
|
"modified": "2019-11-19 10:49:49.494927",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "CRM",
|
||||||
|
"name": "Appointment Booking Slots",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [],
|
||||||
|
"quick_entry": 1,
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
class AppointmentBookingSlots(Document):
|
||||||
|
pass
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
{
|
||||||
|
"creation": "2019-09-10 15:02:05.779434",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"day_of_week",
|
||||||
|
"from_time",
|
||||||
|
"to_time"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "day_of_week",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Day Of Week",
|
||||||
|
"options": "Sunday\nMonday\nTuesday\nWednesday\nThursday\nFriday\nSaturday",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "from_time",
|
||||||
|
"fieldtype": "Time",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "From Time",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "to_time",
|
||||||
|
"fieldtype": "Time",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "To Time",
|
||||||
|
"reqd": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"istable": 1,
|
||||||
|
"modified": "2019-09-10 15:05:20.406855",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "CRM",
|
||||||
|
"name": "Availability Of Slots",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [],
|
||||||
|
"quick_entry": 1,
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
class AvailabilityOfSlots(Document):
|
||||||
|
pass
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -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,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -299,7 +299,8 @@ scheduler_events = {
|
|||||||
"erpnext.quality_management.doctype.quality_review.quality_review.review",
|
"erpnext.quality_management.doctype.quality_review.quality_review.review",
|
||||||
"erpnext.support.doctype.service_level_agreement.service_level_agreement.check_agreement_status",
|
"erpnext.support.doctype.service_level_agreement.service_level_agreement.check_agreement_status",
|
||||||
"erpnext.crm.doctype.email_campaign.email_campaign.send_email_to_leads_or_contacts",
|
"erpnext.crm.doctype.email_campaign.email_campaign.send_email_to_leads_or_contacts",
|
||||||
"erpnext.crm.doctype.email_campaign.email_campaign.set_email_campaign_status"
|
"erpnext.crm.doctype.email_campaign.email_campaign.set_email_campaign_status",
|
||||||
|
"erpnext.selling.doctype.quotation.set_expired_status"
|
||||||
],
|
],
|
||||||
"daily_long": [
|
"daily_long": [
|
||||||
"erpnext.setup.doctype.email_digest.email_digest.send",
|
"erpnext.setup.doctype.email_digest.email_digest.send",
|
||||||
|
|||||||
@@ -19,14 +19,19 @@ def get_approvers(doctype, txt, searchfield, start, page_len, filters):
|
|||||||
approvers = []
|
approvers = []
|
||||||
department_details = {}
|
department_details = {}
|
||||||
department_list = []
|
department_list = []
|
||||||
employee_department = filters.get("department") or frappe.get_value("Employee", filters.get("employee"), "department")
|
employee = frappe.get_value("Employee", filters.get("employee"), ["department", "leave_approver"], as_dict=True)
|
||||||
|
|
||||||
|
employee_department = filters.get("department") or employee.department
|
||||||
if employee_department:
|
if employee_department:
|
||||||
department_details = frappe.db.get_value("Department", {"name": employee_department}, ["lft", "rgt"], as_dict=True)
|
department_details = frappe.db.get_value("Department", {"name": employee_department}, ["lft", "rgt"], as_dict=True)
|
||||||
if department_details:
|
if department_details:
|
||||||
department_list = frappe.db.sql("""select name from `tabDepartment` where lft <= %s
|
department_list = frappe.db.sql("""select name from `tabDepartment` where lft <= %s
|
||||||
and rgt >= %s
|
and rgt >= %s
|
||||||
and disabled=0
|
and disabled=0
|
||||||
order by lft desc""", (department_details.lft, department_details.rgt), as_list = True)
|
order by lft desc""", (department_details.lft, department_details.rgt), as_list=True)
|
||||||
|
|
||||||
|
if filters.get("doctype") == "Leave Application" and employee.leave_approver:
|
||||||
|
approvers.append(frappe.db.get_value("User", employee.leave_approver, ['name', 'first_name', 'last_name']))
|
||||||
|
|
||||||
if filters.get("doctype") == "Leave Application":
|
if filters.get("doctype") == "Leave Application":
|
||||||
parentfield = "leave_approvers"
|
parentfield = "leave_approvers"
|
||||||
@@ -41,4 +46,4 @@ def get_approvers(doctype, txt, searchfield, start, page_len, filters):
|
|||||||
and approver.parentfield = %s
|
and approver.parentfield = %s
|
||||||
and approver.approver=user.name""",(d, "%" + txt + "%", parentfield), as_list=True)
|
and approver.approver=user.name""",(d, "%" + txt + "%", parentfield), as_list=True)
|
||||||
|
|
||||||
return approvers
|
return set(tuple(approver) for approver in approvers)
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -140,32 +140,6 @@ class ExpenseClaim(AccountsController):
|
|||||||
"against": ",".join([d.default_account for d in self.expenses]),
|
"against": ",".join([d.default_account for d in self.expenses]),
|
||||||
"party_type": "Employee",
|
"party_type": "Employee",
|
||||||
"party": self.employee,
|
"party": self.employee,
|
||||||
"against_voucher_type": self.doctype,
|
|
||||||
"against_voucher": self.name
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
gl_entry.append(
|
|
||||||
self.get_gl_dict({
|
|
||||||
"account": data.advance_account,
|
|
||||||
"debit": data.allocated_amount,
|
|
||||||
"debit_in_account_currency": data.allocated_amount,
|
|
||||||
"against": self.payable_account,
|
|
||||||
"party_type": "Employee",
|
|
||||||
"party": self.employee,
|
|
||||||
"against_voucher_type": self.doctype,
|
|
||||||
"against_voucher": self.name
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
gl_entry.append(
|
|
||||||
self.get_gl_dict({
|
|
||||||
"account": self.payable_account,
|
|
||||||
"credit": data.allocated_amount,
|
|
||||||
"credit_in_account_currency": data.allocated_amount,
|
|
||||||
"against": data.advance_account,
|
|
||||||
"party_type": "Employee",
|
|
||||||
"party": self.employee,
|
|
||||||
"against_voucher_type": "Employee Advance",
|
"against_voucher_type": "Employee Advance",
|
||||||
"against_voucher": data.employee_advance
|
"against_voucher": data.employee_advance
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -1,298 +1,78 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"creation": "2018-05-24 07:20:04.255236",
|
||||||
"allow_guest_to_view": 0,
|
"doctype": "DocType",
|
||||||
"allow_import": 0,
|
"editable_grid": 1,
|
||||||
"allow_rename": 0,
|
"engine": "InnoDB",
|
||||||
"beta": 0,
|
"field_order": [
|
||||||
"creation": "2018-05-24 07:20:04.255236",
|
"item_code",
|
||||||
"custom": 0,
|
"item_name",
|
||||||
"docstatus": 0,
|
"column_break_3",
|
||||||
"doctype": "DocType",
|
"qty",
|
||||||
"document_type": "",
|
"rate",
|
||||||
"editable_grid": 1,
|
"ordered_qty",
|
||||||
"engine": "InnoDB",
|
"section_break_7",
|
||||||
|
"terms_and_conditions"
|
||||||
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "item_code",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Link",
|
||||||
"allow_on_submit": 0,
|
"in_list_view": 1,
|
||||||
"bold": 0,
|
"label": "Item Code",
|
||||||
"collapsible": 0,
|
"options": "Item",
|
||||||
"columns": 0,
|
"reqd": 1,
|
||||||
"fieldname": "item_code",
|
"search_index": 1
|
||||||
"fieldtype": "Link",
|
},
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Item Code",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Item",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fetch_from": "item_code.item_name",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldname": "item_name",
|
||||||
"allow_on_submit": 0,
|
"fieldtype": "Data",
|
||||||
"bold": 0,
|
"label": "Item Name"
|
||||||
"collapsible": 0,
|
},
|
||||||
"columns": 0,
|
|
||||||
"fetch_from": "item_code.item_name",
|
|
||||||
"fieldname": "item_name",
|
|
||||||
"fieldtype": "Data",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Item Name",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "column_break_3",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Column Break"
|
||||||
"allow_on_submit": 0,
|
},
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "column_break_3",
|
|
||||||
"fieldtype": "Column Break",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "qty",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Float",
|
||||||
"allow_on_submit": 0,
|
"in_list_view": 1,
|
||||||
"bold": 0,
|
"label": "Quantity"
|
||||||
"collapsible": 0,
|
},
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "qty",
|
|
||||||
"fieldtype": "Float",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Quantity",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "rate",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Currency",
|
||||||
"allow_on_submit": 0,
|
"in_list_view": 1,
|
||||||
"bold": 0,
|
"label": "Rate",
|
||||||
"collapsible": 0,
|
"reqd": 1
|
||||||
"columns": 0,
|
},
|
||||||
"fieldname": "rate",
|
|
||||||
"fieldtype": "Currency",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Rate",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "ordered_qty",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Float",
|
||||||
"allow_on_submit": 0,
|
"in_list_view": 1,
|
||||||
"bold": 0,
|
"label": "Ordered Quantity",
|
||||||
"collapsible": 0,
|
"no_copy": 1,
|
||||||
"columns": 0,
|
"read_only": 1
|
||||||
"fieldname": "ordered_qty",
|
},
|
||||||
"fieldtype": "Float",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Ordered Quantity",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 1,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "section_break_7",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Section Break"
|
||||||
"allow_on_submit": 0,
|
},
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "section_break_7",
|
|
||||||
"fieldtype": "Section Break",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "terms_and_conditions",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Text",
|
||||||
"allow_on_submit": 0,
|
"label": "Terms and Conditions"
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "terms_and_conditions",
|
|
||||||
"fieldtype": "Text",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Terms and Conditions",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"has_web_view": 0,
|
"istable": 1,
|
||||||
"hide_heading": 0,
|
"modified": "2019-11-18 19:37:46.245878",
|
||||||
"hide_toolbar": 0,
|
"modified_by": "Administrator",
|
||||||
"idx": 0,
|
"module": "Manufacturing",
|
||||||
"image_view": 0,
|
"name": "Blanket Order Item",
|
||||||
"in_create": 0,
|
"owner": "Administrator",
|
||||||
"is_submittable": 0,
|
"permissions": [],
|
||||||
"issingle": 0,
|
"quick_entry": 1,
|
||||||
"istable": 1,
|
"sort_field": "modified",
|
||||||
"max_attachments": 0,
|
"sort_order": "DESC",
|
||||||
"modified": "2018-06-14 07:04:14.050836",
|
"track_changes": 1
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": "Manufacturing",
|
|
||||||
"name": "Blanket Order Item",
|
|
||||||
"name_case": "",
|
|
||||||
"owner": "Administrator",
|
|
||||||
"permissions": [],
|
|
||||||
"quick_entry": 1,
|
|
||||||
"read_only": 0,
|
|
||||||
"read_only_onload": 0,
|
|
||||||
"show_name_in_global_search": 0,
|
|
||||||
"sort_field": "modified",
|
|
||||||
"sort_order": "DESC",
|
|
||||||
"track_changes": 1,
|
|
||||||
"track_seen": 0
|
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,12 @@ frappe.provide("erpnext.bom");
|
|||||||
|
|
||||||
frappe.ui.form.on("BOM", {
|
frappe.ui.form.on("BOM", {
|
||||||
setup: function(frm) {
|
setup: function(frm) {
|
||||||
|
frm.custom_make_buttons = {
|
||||||
|
'BOM': 'Duplicate BOM',
|
||||||
|
'Work Order': 'Work Order',
|
||||||
|
'Quality Inspection': 'Quality Inspection'
|
||||||
|
};
|
||||||
|
|
||||||
frm.set_query("bom_no", "items", function() {
|
frm.set_query("bom_no", "items", function() {
|
||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
@@ -85,9 +91,21 @@ frappe.ui.form.on("BOM", {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(frm.doc.docstatus!=0) {
|
if(frm.doc.docstatus!=0) {
|
||||||
frm.add_custom_button(__("Duplicate"), function() {
|
frm.add_custom_button(__("Duplicate BOM"), function() {
|
||||||
frm.copy_doc();
|
frm.copy_doc();
|
||||||
});
|
}, __("Create"));
|
||||||
|
|
||||||
|
frm.add_custom_button(__("Work Order"), function() {
|
||||||
|
frm.trigger("make_work_order");
|
||||||
|
}, __("Create"));
|
||||||
|
|
||||||
|
if (frm.doc.inspection_required) {
|
||||||
|
frm.add_custom_button(__("Quality Inspection"), function() {
|
||||||
|
frm.trigger("make_quality_inspection");
|
||||||
|
}, __("Create"));
|
||||||
|
}
|
||||||
|
|
||||||
|
frm.page.set_inner_btn_group_as_primary(__('Create'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(frm.doc.items && frm.doc.allow_alternative_item) {
|
if(frm.doc.items && frm.doc.allow_alternative_item) {
|
||||||
@@ -109,6 +127,41 @@ frappe.ui.form.on("BOM", {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
make_work_order: function(frm) {
|
||||||
|
const fields = [{
|
||||||
|
fieldtype: 'Float',
|
||||||
|
label: __('Qty To Manufacture'),
|
||||||
|
fieldname: 'qty',
|
||||||
|
reqd: 1,
|
||||||
|
default: 1
|
||||||
|
}];
|
||||||
|
|
||||||
|
frappe.prompt(fields, data => {
|
||||||
|
frappe.call({
|
||||||
|
method: "erpnext.manufacturing.doctype.work_order.work_order.make_work_order",
|
||||||
|
args: {
|
||||||
|
item: frm.doc.item,
|
||||||
|
qty: data.qty || 0.0,
|
||||||
|
project: frm.doc.project
|
||||||
|
},
|
||||||
|
freeze: true,
|
||||||
|
callback: function(r) {
|
||||||
|
if(r.message) {
|
||||||
|
var doc = frappe.model.sync(r.message)[0];
|
||||||
|
frappe.set_route("Form", doc.doctype, doc.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, __("Enter Value"), __("Create"));
|
||||||
|
},
|
||||||
|
|
||||||
|
make_quality_inspection: function(frm) {
|
||||||
|
frappe.model.open_mapped_doc({
|
||||||
|
method: "erpnext.stock.doctype.quality_inspection.quality_inspection.make_quality_inspection",
|
||||||
|
frm: frm
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
update_cost: function(frm) {
|
update_cost: function(frm) {
|
||||||
return frappe.call({
|
return frappe.call({
|
||||||
doc: frm.doc,
|
doc: frm.doc,
|
||||||
|
|||||||
@@ -3,33 +3,36 @@
|
|||||||
"creation": "2013-01-22 15:11:38",
|
"creation": "2013-01-22 15:11:38",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"document_type": "Setup",
|
"document_type": "Setup",
|
||||||
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
"item",
|
"item",
|
||||||
"item_name",
|
|
||||||
"image",
|
|
||||||
"uom",
|
|
||||||
"quantity",
|
"quantity",
|
||||||
|
"set_rate_of_sub_assembly_item_based_on_bom",
|
||||||
"cb0",
|
"cb0",
|
||||||
"is_active",
|
"is_active",
|
||||||
"is_default",
|
"is_default",
|
||||||
"with_operations",
|
|
||||||
"inspection_required",
|
|
||||||
"allow_alternative_item",
|
"allow_alternative_item",
|
||||||
"allow_same_item_multiple_times",
|
"image",
|
||||||
"set_rate_of_sub_assembly_item_based_on_bom",
|
"item_name",
|
||||||
"quality_inspection_template",
|
"uom",
|
||||||
"currency_detail",
|
"currency_detail",
|
||||||
"company",
|
"company",
|
||||||
"transfer_material_against",
|
"project",
|
||||||
"conversion_rate",
|
"conversion_rate",
|
||||||
"column_break_12",
|
"column_break_12",
|
||||||
"currency",
|
"currency",
|
||||||
"rm_cost_as_per",
|
"rm_cost_as_per",
|
||||||
"buying_price_list",
|
"buying_price_list",
|
||||||
"operations_section",
|
"section_break_21",
|
||||||
|
"with_operations",
|
||||||
|
"column_break_23",
|
||||||
|
"transfer_material_against",
|
||||||
"routing",
|
"routing",
|
||||||
|
"operations_section",
|
||||||
"operations",
|
"operations",
|
||||||
"materials_section",
|
"materials_section",
|
||||||
|
"inspection_required",
|
||||||
|
"quality_inspection_template",
|
||||||
"items",
|
"items",
|
||||||
"scrap_section",
|
"scrap_section",
|
||||||
"scrap_items",
|
"scrap_items",
|
||||||
@@ -41,14 +44,9 @@
|
|||||||
"base_operating_cost",
|
"base_operating_cost",
|
||||||
"base_raw_material_cost",
|
"base_raw_material_cost",
|
||||||
"base_scrap_material_cost",
|
"base_scrap_material_cost",
|
||||||
"total_cost_of_bom",
|
|
||||||
"total_cost",
|
|
||||||
"column_break_26",
|
"column_break_26",
|
||||||
|
"total_cost",
|
||||||
"base_total_cost",
|
"base_total_cost",
|
||||||
"more_info_section",
|
|
||||||
"project",
|
|
||||||
"amended_from",
|
|
||||||
"col_break23",
|
|
||||||
"section_break_25",
|
"section_break_25",
|
||||||
"description",
|
"description",
|
||||||
"column_break_27",
|
"column_break_27",
|
||||||
@@ -57,12 +55,14 @@
|
|||||||
"website_section",
|
"website_section",
|
||||||
"show_in_website",
|
"show_in_website",
|
||||||
"route",
|
"route",
|
||||||
|
"column_break_52",
|
||||||
"website_image",
|
"website_image",
|
||||||
"thumbnail",
|
"thumbnail",
|
||||||
"sb_web_spec",
|
"sb_web_spec",
|
||||||
"web_long_description",
|
|
||||||
"show_items",
|
"show_items",
|
||||||
"show_operations"
|
"show_operations",
|
||||||
|
"web_long_description",
|
||||||
|
"amended_from"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@@ -152,7 +152,7 @@
|
|||||||
"default": "0",
|
"default": "0",
|
||||||
"fieldname": "inspection_required",
|
"fieldname": "inspection_required",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Inspection Required"
|
"label": "Quality Inspection Required"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
@@ -160,12 +160,6 @@
|
|||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Allow Alternative Item"
|
"label": "Allow Alternative Item"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"default": "0",
|
|
||||||
"fieldname": "allow_same_item_multiple_times",
|
|
||||||
"fieldtype": "Check",
|
|
||||||
"label": "Allow Same Item Multiple Times"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_on_submit": 1,
|
"allow_on_submit": 1,
|
||||||
"default": "1",
|
"default": "1",
|
||||||
@@ -193,6 +187,7 @@
|
|||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"default": "Work Order",
|
||||||
"fieldname": "transfer_material_against",
|
"fieldname": "transfer_material_against",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"label": "Transfer Material Against",
|
"label": "Transfer Material Against",
|
||||||
@@ -235,10 +230,10 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "operations_section",
|
"fieldname": "operations_section",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"label": "Operations",
|
|
||||||
"oldfieldtype": "Section Break"
|
"oldfieldtype": "Section Break"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"depends_on": "with_operations",
|
||||||
"fieldname": "routing",
|
"fieldname": "routing",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Routing",
|
"label": "Routing",
|
||||||
@@ -335,10 +330,6 @@
|
|||||||
"options": "Company:company:default_currency",
|
"options": "Company:company:default_currency",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "total_cost_of_bom",
|
|
||||||
"fieldtype": "Section Break"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "total_cost",
|
"fieldname": "total_cost",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
@@ -359,10 +350,6 @@
|
|||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "more_info_section",
|
|
||||||
"fieldtype": "Section Break"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "project",
|
"fieldname": "project",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
@@ -381,10 +368,6 @@
|
|||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "col_break23",
|
|
||||||
"fieldtype": "Column Break"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "section_break_25",
|
"fieldname": "section_break_25",
|
||||||
"fieldtype": "Section Break"
|
"fieldtype": "Section Break"
|
||||||
@@ -481,13 +464,26 @@
|
|||||||
"fieldname": "show_operations",
|
"fieldname": "show_operations",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Show Operations"
|
"label": "Show Operations"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_21",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Operations"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_23",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_52",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-sitemap",
|
"icon": "fa fa-sitemap",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"image_field": "image",
|
"image_field": "image",
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"modified": "2019-07-30 17:00:09.665068",
|
"modified": "2019-11-22 14:35:12.142150",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "BOM",
|
"name": "BOM",
|
||||||
|
|||||||
@@ -96,6 +96,7 @@ class BOM(WebsiteGenerator):
|
|||||||
|
|
||||||
def get_routing(self):
|
def get_routing(self):
|
||||||
if self.routing:
|
if self.routing:
|
||||||
|
self.set("operations", [])
|
||||||
for d in frappe.get_all("BOM Operation", fields = ["*"],
|
for d in frappe.get_all("BOM Operation", fields = ["*"],
|
||||||
filters = {'parenttype': 'Routing', 'parent': self.routing}):
|
filters = {'parenttype': 'Routing', 'parent': self.routing}):
|
||||||
child = self.append('operations', d)
|
child = self.append('operations', d)
|
||||||
@@ -289,7 +290,7 @@ class BOM(WebsiteGenerator):
|
|||||||
if not valuation_rate:
|
if not valuation_rate:
|
||||||
valuation_rate = frappe.db.get_value("Item", args['item_code'], "valuation_rate")
|
valuation_rate = frappe.db.get_value("Item", args['item_code'], "valuation_rate")
|
||||||
|
|
||||||
return valuation_rate
|
return flt(valuation_rate)
|
||||||
|
|
||||||
def manage_default_bom(self):
|
def manage_default_bom(self):
|
||||||
""" Uncheck others if current one is selected as default or
|
""" Uncheck others if current one is selected as default or
|
||||||
@@ -362,15 +363,9 @@ class BOM(WebsiteGenerator):
|
|||||||
def validate_materials(self):
|
def validate_materials(self):
|
||||||
""" Validate raw material entries """
|
""" Validate raw material entries """
|
||||||
|
|
||||||
def get_duplicates(lst):
|
|
||||||
seen = set()
|
|
||||||
seen_add = seen.add
|
|
||||||
for item in lst:
|
|
||||||
if item.item_code in seen or seen_add(item.item_code):
|
|
||||||
yield item
|
|
||||||
|
|
||||||
if not self.get('items'):
|
if not self.get('items'):
|
||||||
frappe.throw(_("Raw Materials cannot be blank."))
|
frappe.throw(_("Raw Materials cannot be blank."))
|
||||||
|
|
||||||
check_list = []
|
check_list = []
|
||||||
for m in self.get('items'):
|
for m in self.get('items'):
|
||||||
if m.bom_no:
|
if m.bom_no:
|
||||||
@@ -379,16 +374,6 @@ class BOM(WebsiteGenerator):
|
|||||||
frappe.throw(_("Quantity required for Item {0} in row {1}").format(m.item_code, m.idx))
|
frappe.throw(_("Quantity required for Item {0} in row {1}").format(m.item_code, m.idx))
|
||||||
check_list.append(m)
|
check_list.append(m)
|
||||||
|
|
||||||
if not self.allow_same_item_multiple_times:
|
|
||||||
duplicate_items = list(get_duplicates(check_list))
|
|
||||||
if duplicate_items:
|
|
||||||
li = []
|
|
||||||
for i in duplicate_items:
|
|
||||||
li.append("{0} on row {1}".format(i.item_code, i.idx))
|
|
||||||
duplicate_list = '<br>' + '<br>'.join(li)
|
|
||||||
|
|
||||||
frappe.throw(_("Same item has been entered multiple times. {0}").format(duplicate_list))
|
|
||||||
|
|
||||||
def check_recursion(self, bom_list=[]):
|
def check_recursion(self, bom_list=[]):
|
||||||
""" Check whether recursion occurs in any bom"""
|
""" Check whether recursion occurs in any bom"""
|
||||||
bom_list = self.traverse_tree()
|
bom_list = self.traverse_tree()
|
||||||
|
|||||||
@@ -17,11 +17,13 @@ def get_data():
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
'label': _('Manufacture'),
|
'label': _('Manufacture'),
|
||||||
'items': ['BOM', 'Work Order', 'Job Card', 'Production Plan']
|
'items': ['BOM', 'Work Order', 'Job Card']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'label': _('Purchase'),
|
'label': _('Subcontract'),
|
||||||
'items': ['Purchase Order', 'Purchase Receipt', 'Purchase Invoice']
|
'items': ['Purchase Order', 'Purchase Receipt', 'Purchase Invoice']
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
'disable_create_buttons': ["Item", "Purchase Order", "Purchase Receipt",
|
||||||
|
"Purchase Invoice", "Job Card", "Stock Entry"]
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -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: {
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ class ProductionPlan(Document):
|
|||||||
self.get_mr_items()
|
self.get_mr_items()
|
||||||
|
|
||||||
def get_so_items(self):
|
def get_so_items(self):
|
||||||
so_list = [d.sales_order for d in self.get("sales_orders", []) if d.sales_order]
|
so_list = [d.sales_order for d in self.sales_orders if d.sales_order]
|
||||||
if not so_list:
|
if not so_list:
|
||||||
msgprint(_("Please enter Sales Orders in the above table"))
|
msgprint(_("Please enter Sales Orders in the above table"))
|
||||||
return []
|
return []
|
||||||
@@ -109,7 +109,7 @@ class ProductionPlan(Document):
|
|||||||
item_condition = ' and so_item.item_code = {0}'.format(frappe.db.escape(self.item_code))
|
item_condition = ' and so_item.item_code = {0}'.format(frappe.db.escape(self.item_code))
|
||||||
|
|
||||||
items = frappe.db.sql("""select distinct parent, item_code, warehouse,
|
items = frappe.db.sql("""select distinct parent, item_code, warehouse,
|
||||||
(qty - work_order_qty) * conversion_factor as pending_qty, name
|
(qty - work_order_qty) * conversion_factor as pending_qty, description, name
|
||||||
from `tabSales Order Item` so_item
|
from `tabSales Order Item` so_item
|
||||||
where parent in (%s) and docstatus = 1 and qty > work_order_qty
|
where parent in (%s) and docstatus = 1 and qty > work_order_qty
|
||||||
and exists (select name from `tabBOM` bom where bom.item=so_item.item_code
|
and exists (select name from `tabBOM` bom where bom.item=so_item.item_code
|
||||||
@@ -121,7 +121,7 @@ class ProductionPlan(Document):
|
|||||||
|
|
||||||
packed_items = frappe.db.sql("""select distinct pi.parent, pi.item_code, pi.warehouse as warehouse,
|
packed_items = frappe.db.sql("""select distinct pi.parent, pi.item_code, pi.warehouse as warehouse,
|
||||||
(((so_item.qty - so_item.work_order_qty) * pi.qty) / so_item.qty)
|
(((so_item.qty - so_item.work_order_qty) * pi.qty) / so_item.qty)
|
||||||
as pending_qty, pi.parent_item, so_item.name
|
as pending_qty, pi.parent_item, pi.description, so_item.name
|
||||||
from `tabSales Order Item` so_item, `tabPacked Item` pi
|
from `tabSales Order Item` so_item, `tabPacked Item` pi
|
||||||
where so_item.parent = pi.parent and so_item.docstatus = 1
|
where so_item.parent = pi.parent and so_item.docstatus = 1
|
||||||
and pi.parent_item = so_item.item_code
|
and pi.parent_item = so_item.item_code
|
||||||
@@ -134,7 +134,7 @@ class ProductionPlan(Document):
|
|||||||
self.calculate_total_planned_qty()
|
self.calculate_total_planned_qty()
|
||||||
|
|
||||||
def get_mr_items(self):
|
def get_mr_items(self):
|
||||||
mr_list = [d.material_request for d in self.get("material_requests", []) if d.material_request]
|
mr_list = [d.material_request for d in self.material_requests if d.material_request]
|
||||||
if not mr_list:
|
if not mr_list:
|
||||||
msgprint(_("Please enter Material Requests in the above table"))
|
msgprint(_("Please enter Material Requests in the above table"))
|
||||||
return []
|
return []
|
||||||
@@ -143,7 +143,7 @@ class ProductionPlan(Document):
|
|||||||
if self.item_code:
|
if self.item_code:
|
||||||
item_condition = " and mr_item.item_code ={0}".format(frappe.db.escape(self.item_code))
|
item_condition = " and mr_item.item_code ={0}".format(frappe.db.escape(self.item_code))
|
||||||
|
|
||||||
items = frappe.db.sql("""select distinct parent, name, item_code, warehouse,
|
items = frappe.db.sql("""select distinct parent, name, item_code, warehouse, description,
|
||||||
(qty - ordered_qty) as pending_qty
|
(qty - ordered_qty) as pending_qty
|
||||||
from `tabMaterial Request Item` mr_item
|
from `tabMaterial Request Item` mr_item
|
||||||
where parent in (%s) and docstatus = 1 and qty > ordered_qty
|
where parent in (%s) and docstatus = 1 and qty > ordered_qty
|
||||||
@@ -162,7 +162,7 @@ class ProductionPlan(Document):
|
|||||||
'include_exploded_items': 1,
|
'include_exploded_items': 1,
|
||||||
'warehouse': data.warehouse,
|
'warehouse': data.warehouse,
|
||||||
'item_code': data.item_code,
|
'item_code': data.item_code,
|
||||||
'description': item_details and item_details.description or '',
|
'description': data.description or item_details.description,
|
||||||
'stock_uom': item_details and item_details.stock_uom or '',
|
'stock_uom': item_details and item_details.stock_uom or '',
|
||||||
'bom_no': item_details and item_details.bom_no or '',
|
'bom_no': item_details and item_details.bom_no or '',
|
||||||
'planned_qty': data.pending_qty,
|
'planned_qty': data.pending_qty,
|
||||||
@@ -174,10 +174,12 @@ class ProductionPlan(Document):
|
|||||||
if self.get_items_from == "Sales Order":
|
if self.get_items_from == "Sales Order":
|
||||||
pi.sales_order = data.parent
|
pi.sales_order = data.parent
|
||||||
pi.sales_order_item = data.name
|
pi.sales_order_item = data.name
|
||||||
|
pi.description = data.description
|
||||||
|
|
||||||
elif self.get_items_from == "Material Request":
|
elif self.get_items_from == "Material Request":
|
||||||
pi.material_request = data.parent
|
pi.material_request = data.parent
|
||||||
pi.material_request_item = data.name
|
pi.material_request_item = data.name
|
||||||
|
pi.description = data.description
|
||||||
|
|
||||||
def calculate_total_planned_qty(self):
|
def calculate_total_planned_qty(self):
|
||||||
self.total_planned_qty = 0
|
self.total_planned_qty = 0
|
||||||
@@ -195,7 +197,6 @@ class ProductionPlan(Document):
|
|||||||
for data in self.po_items:
|
for data in self.po_items:
|
||||||
if data.name == production_plan_item:
|
if data.name == production_plan_item:
|
||||||
data.produced_qty = produced_qty
|
data.produced_qty = produced_qty
|
||||||
data.pending_qty = data.planned_qty - data.produced_qty
|
|
||||||
data.db_update()
|
data.db_update()
|
||||||
|
|
||||||
self.calculate_total_produced_qty()
|
self.calculate_total_produced_qty()
|
||||||
@@ -302,6 +303,7 @@ class ProductionPlan(Document):
|
|||||||
wo_list.extend(work_orders)
|
wo_list.extend(work_orders)
|
||||||
|
|
||||||
frappe.flags.mute_messages = False
|
frappe.flags.mute_messages = False
|
||||||
|
|
||||||
if wo_list:
|
if wo_list:
|
||||||
wo_list = ["""<a href="#Form/Work Order/%s" target="_blank">%s</a>""" % \
|
wo_list = ["""<a href="#Form/Work Order/%s" target="_blank">%s</a>""" % \
|
||||||
(p, p) for p in wo_list]
|
(p, p) for p in wo_list]
|
||||||
@@ -309,16 +311,15 @@ class ProductionPlan(Document):
|
|||||||
else :
|
else :
|
||||||
msgprint(_("No Work Orders created"))
|
msgprint(_("No Work Orders created"))
|
||||||
|
|
||||||
|
|
||||||
def make_work_order_for_sub_assembly_items(self, item):
|
def make_work_order_for_sub_assembly_items(self, item):
|
||||||
work_orders = []
|
work_orders = []
|
||||||
bom_data = {}
|
bom_data = {}
|
||||||
|
|
||||||
get_sub_assembly_items(item.get("bom_no"), bom_data, item.get("qty"))
|
get_sub_assembly_items(item.get("bom_no"), bom_data)
|
||||||
|
|
||||||
for key, data in bom_data.items():
|
for key, data in bom_data.items():
|
||||||
data.update({
|
data.update({
|
||||||
'qty': data.get("stock_qty"),
|
'qty': data.get("stock_qty") * item.get("qty"),
|
||||||
'production_plan': self.name,
|
'production_plan': self.name,
|
||||||
'company': self.company,
|
'company': self.company,
|
||||||
'fg_warehouse': item.get("fg_warehouse"),
|
'fg_warehouse': item.get("fg_warehouse"),
|
||||||
@@ -528,7 +529,6 @@ def get_material_request_items(row, sales_order,
|
|||||||
required_qty = ceil(required_qty)
|
required_qty = ceil(required_qty)
|
||||||
|
|
||||||
if required_qty > 0:
|
if required_qty > 0:
|
||||||
print(row)
|
|
||||||
return {
|
return {
|
||||||
'item_code': row.item_code,
|
'item_code': row.item_code,
|
||||||
'item_name': row.item_name,
|
'item_name': row.item_name,
|
||||||
@@ -561,7 +561,7 @@ def get_sales_orders(self):
|
|||||||
item_filter += " and so_item.item_code = %(item)s"
|
item_filter += " and so_item.item_code = %(item)s"
|
||||||
|
|
||||||
open_so = frappe.db.sql("""
|
open_so = frappe.db.sql("""
|
||||||
select distinct so.name, so.transaction_date, so.customer, so.base_grand_total as grand_total
|
select distinct so.name, so.transaction_date, so.customer, so.base_grand_total
|
||||||
from `tabSales Order` so, `tabSales Order Item` so_item
|
from `tabSales Order` so, `tabSales Order Item` so_item
|
||||||
where so_item.parent = so.name
|
where so_item.parent = so.name
|
||||||
and so.docstatus = 1 and so.status not in ("Stopped", "Closed")
|
and so.docstatus = 1 and so.status not in ("Stopped", "Closed")
|
||||||
@@ -625,7 +625,7 @@ def get_items_for_material_requests(doc, ignore_existing_ordered_qty=None):
|
|||||||
for data in po_items:
|
for data in po_items:
|
||||||
planned_qty = data.get('required_qty') or data.get('planned_qty')
|
planned_qty = data.get('required_qty') or data.get('planned_qty')
|
||||||
ignore_existing_ordered_qty = data.get('ignore_existing_ordered_qty') or ignore_existing_ordered_qty
|
ignore_existing_ordered_qty = data.get('ignore_existing_ordered_qty') or ignore_existing_ordered_qty
|
||||||
warehouse = warehouse or data.get("warehouse")
|
warehouse = data.get("warehouse") or warehouse
|
||||||
|
|
||||||
item_details = {}
|
item_details = {}
|
||||||
if data.get("bom") or data.get("bom_no"):
|
if data.get("bom") or data.get("bom_no"):
|
||||||
@@ -708,11 +708,11 @@ def get_item_data(item_code):
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
"bom_no": item_details.get("bom_no"),
|
"bom_no": item_details.get("bom_no"),
|
||||||
"stock_uom": item_details.get("stock_uom"),
|
"stock_uom": item_details.get("stock_uom")
|
||||||
"description": item_details.get("description")
|
# "description": item_details.get("description")
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_sub_assembly_items(bom_no, bom_data, qty):
|
def get_sub_assembly_items(bom_no, bom_data):
|
||||||
data = get_children('BOM', parent = bom_no)
|
data = get_children('BOM', parent = bom_no)
|
||||||
for d in data:
|
for d in data:
|
||||||
if d.expandable:
|
if d.expandable:
|
||||||
@@ -729,6 +729,6 @@ def get_sub_assembly_items(bom_no, bom_data, qty):
|
|||||||
})
|
})
|
||||||
|
|
||||||
bom_item = bom_data.get(key)
|
bom_item = bom_data.get(key)
|
||||||
bom_item["stock_qty"] += ((d.stock_qty * qty) / d.parent_bom_qty)
|
bom_item["stock_qty"] += d.stock_qty
|
||||||
|
|
||||||
get_sub_assembly_items(bom_item.get("bom_no"), bom_data, bom_item["stock_qty"])
|
get_sub_assembly_items(bom_item.get("bom_no"), bom_data)
|
||||||
|
|||||||
@@ -11,11 +11,9 @@ from erpnext.manufacturing.doctype.production_plan.production_plan import get_sa
|
|||||||
from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import create_stock_reconciliation
|
from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import create_stock_reconciliation
|
||||||
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
||||||
from erpnext.manufacturing.doctype.production_plan.production_plan import get_items_for_material_requests
|
from erpnext.manufacturing.doctype.production_plan.production_plan import get_items_for_material_requests
|
||||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory
|
|
||||||
|
|
||||||
class TestProductionPlan(unittest.TestCase):
|
class TestProductionPlan(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
set_perpetual_inventory(0)
|
|
||||||
for item in ['Test Production Item 1', 'Subassembly Item 1',
|
for item in ['Test Production Item 1', 'Subassembly Item 1',
|
||||||
'Raw Material Item 1', 'Raw Material Item 2']:
|
'Raw Material Item 1', 'Raw Material Item 2']:
|
||||||
create_item(item, valuation_rate=100)
|
create_item(item, valuation_rate=100)
|
||||||
|
|||||||
@@ -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));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -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:
|
||||||
@@ -599,6 +609,22 @@ def get_item_details(item, project = None):
|
|||||||
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def make_work_order(item, qty=0, project=None):
|
||||||
|
if not frappe.has_permission("Work Order", "write"):
|
||||||
|
frappe.throw(_("Not permitted"), frappe.PermissionError)
|
||||||
|
|
||||||
|
item_details = get_item_details(item, project)
|
||||||
|
|
||||||
|
wo_doc = frappe.new_doc("Work Order")
|
||||||
|
wo_doc.production_item = item
|
||||||
|
wo_doc.update(item_details)
|
||||||
|
if qty > 0:
|
||||||
|
wo_doc.qty = qty
|
||||||
|
wo_doc.get_items_and_operations_from_bom()
|
||||||
|
|
||||||
|
return wo_doc
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def check_if_scrap_warehouse_mandatory(bom_no):
|
def check_if_scrap_warehouse_mandatory(bom_no):
|
||||||
res = {"set_scrap_wh_mandatory": False }
|
res = {"set_scrap_wh_mandatory": False }
|
||||||
|
|||||||
@@ -638,10 +638,12 @@ 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_expense_account_in_landed_cost_voucher_taxes
|
erpnext.patches.v12_0.set_expense_account_in_landed_cost_voucher_taxes
|
||||||
erpnext.patches.v12_0.replace_accounting_with_accounts_in_home_settings
|
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
|
||||||
@@ -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:
|
||||||
|
|||||||
@@ -15,13 +15,6 @@ def execute():
|
|||||||
|
|
||||||
rename_field(doctype, "allow_transfer_for_manufacture", "include_item_in_manufacturing")
|
rename_field(doctype, "allow_transfer_for_manufacture", "include_item_in_manufacturing")
|
||||||
|
|
||||||
if frappe.db.has_column('BOM', 'allow_same_item_multiple_times'):
|
|
||||||
frappe.db.sql(""" UPDATE tabBOM
|
|
||||||
SET
|
|
||||||
allow_same_item_multiple_times = 0
|
|
||||||
WHERE
|
|
||||||
trim(coalesce(allow_same_item_multiple_times, '')) = '' """)
|
|
||||||
|
|
||||||
for doctype in ['BOM', 'Work Order']:
|
for doctype in ['BOM', 'Work Order']:
|
||||||
frappe.reload_doc('manufacturing', 'doctype', frappe.scrub(doctype))
|
frappe.reload_doc('manufacturing', 'doctype', frappe.scrub(doctype))
|
||||||
|
|
||||||
|
|||||||
@@ -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(" - ")
|
||||||
|
|||||||
@@ -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))
|
||||||
17
erpnext/patches/v12_0/set_cwip_and_delete_asset_settings.py
Normal file
17
erpnext/patches/v12_0/set_cwip_and_delete_asset_settings.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
import frappe
|
||||||
|
from frappe.utils import cint
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
'''Get 'Disable CWIP Accounting value' from Asset Settings, set it in 'Enable Capital Work in Progress Accounting' field
|
||||||
|
in Company, delete Asset Settings '''
|
||||||
|
|
||||||
|
if frappe.db.exists("DocType", "Asset Settings"):
|
||||||
|
frappe.reload_doctype("Asset Category")
|
||||||
|
cwip_value = frappe.db.get_single_value("Asset Settings", "disable_cwip_accounting")
|
||||||
|
|
||||||
|
frappe.db.sql("""UPDATE `tabAsset Category` SET enable_cwip_accounting = %s""", cint(cwip_value))
|
||||||
|
|
||||||
|
frappe.db.sql("""DELETE FROM `tabSingles` where doctype = 'Asset Settings'""")
|
||||||
|
frappe.delete_doc_if_exists("DocType", "Asset Settings")
|
||||||
@@ -3,8 +3,12 @@ from frappe.utils import flt
|
|||||||
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
|
||||||
|
|
||||||
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)
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user