Merge branch 'hotfix' into fec_improvement

This commit is contained in:
Nabin Hait
2019-04-11 11:46:43 +05:30
committed by GitHub
82 changed files with 3924 additions and 2532 deletions

View File

@@ -5,7 +5,7 @@ import frappe
from erpnext.hooks import regional_overrides
from frappe.utils import getdate
__version__ = '11.1.16'
__version__ = '11.1.20'
def get_default_company(user=None):
'''Get default company for user'''

View File

@@ -2,9 +2,9 @@ from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.utils import date_diff, add_months, today, getdate, add_days, flt
from frappe.utils import date_diff, add_months, today, getdate, add_days, flt, get_last_day
from erpnext.accounts.utils import get_account_currency
from erpnext.accounts.general_ledger import make_gl_entries
from frappe.email import sendmail_to_system_managers
def validate_service_stop_date(doc):
''' Validates service_stop_date for Purchase Invoice and Sales Invoice '''
@@ -33,47 +33,49 @@ def validate_service_stop_date(doc):
frappe.throw(_("Cannot change Service Stop Date for item in row {0}".format(item.idx)))
def convert_deferred_expense_to_expense(start_date=None, end_date=None):
# book the expense/income on the last day, but it will be trigger on the 1st of month at 12:00 AM
if not start_date:
start_date = add_months(today(), -1)
if not end_date:
end_date = add_days(today(), -1)
# check for the purchase invoice for which GL entries has to be done
invoices = frappe.db.sql_list('''
select distinct parent from `tabPurchase Invoice Item` where service_start_date<=%s and service_end_date>=%s
select distinct parent from `tabPurchase Invoice Item`
where service_start_date<=%s and service_end_date>=%s
and enable_deferred_expense = 1 and docstatus = 1 and ifnull(amount, 0) > 0
''', (end_date or today(), start_date or add_months(today(), -1)))
''', (end_date, start_date))
# For each invoice, book deferred expense
for invoice in invoices:
doc = frappe.get_doc("Purchase Invoice", invoice)
book_deferred_income_or_expense(doc, start_date, end_date)
book_deferred_income_or_expense(doc, end_date)
def convert_deferred_revenue_to_income(start_date=None, end_date=None):
# book the expense/income on the last day, but it will be trigger on the 1st of month at 12:00 AM
if not start_date:
start_date = add_months(today(), -1)
if not end_date:
end_date = add_days(today(), -1)
# check for the sales invoice for which GL entries has to be done
invoices = frappe.db.sql_list('''
select distinct parent from `tabSales Invoice Item` where service_start_date<=%s and service_end_date>=%s
select distinct parent from `tabSales Invoice Item`
where service_start_date<=%s and service_end_date>=%s
and enable_deferred_revenue = 1 and docstatus = 1 and ifnull(amount, 0) > 0
''', (end_date or today(), start_date or add_months(today(), -1)))
''', (end_date, start_date))
# For each invoice, book deferred revenue
for invoice in invoices:
doc = frappe.get_doc("Sales Invoice", invoice)
book_deferred_income_or_expense(doc, start_date, end_date)
book_deferred_income_or_expense(doc, end_date)
def get_booking_dates(doc, item, posting_date=None):
if not posting_date:
posting_date = add_days(today(), -1)
last_gl_entry = False
def get_booking_dates(doc, item, start_date=None, end_date=None):
deferred_account = "deferred_revenue_account" if doc.doctype=="Sales Invoice" else "deferred_expense_account"
last_gl_entry, skip = False, False
booking_end_date = getdate(add_days(today(), -1) if not end_date else end_date)
if booking_end_date < item.service_start_date or \
(item.service_stop_date and booking_end_date.month > item.service_stop_date.month):
return None, None, None, True
elif booking_end_date >= item.service_end_date:
last_gl_entry = True
booking_end_date = item.service_end_date
elif item.service_stop_date and item.service_stop_date <= booking_end_date:
last_gl_entry = True
booking_end_date = item.service_stop_date
booking_start_date = getdate(add_months(today(), -1) if not start_date else start_date)
booking_start_date = booking_start_date \
if booking_start_date > item.service_start_date else item.service_start_date
prev_gl_entry = frappe.db.sql('''
select name, posting_date from `tabGL Entry` where company=%s and account=%s and
@@ -81,17 +83,28 @@ def get_booking_dates(doc, item, start_date=None, end_date=None):
order by posting_date desc limit 1
''', (doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name), as_dict=True)
if not prev_gl_entry and item.service_start_date < booking_start_date:
booking_start_date = item.service_start_date
elif prev_gl_entry:
booking_start_date = getdate(add_days(prev_gl_entry[0].posting_date, 1))
skip = True if booking_start_date > booking_end_date else False
if prev_gl_entry:
start_date = getdate(add_days(prev_gl_entry[0].posting_date, 1))
else:
start_date = item.service_start_date
return last_gl_entry, booking_start_date, booking_end_date, skip
end_date = get_last_day(start_date)
if end_date >= item.service_end_date:
end_date = item.service_end_date
last_gl_entry = True
elif item.service_stop_date and end_date >= item.service_stop_date:
end_date = item.service_stop_date
last_gl_entry = True
def calculate_amount_and_base_amount(doc, item, last_gl_entry, total_days, total_booking_days):
account_currency = get_account_currency(item.expense_account)
if end_date > getdate(posting_date):
end_date = posting_date
if getdate(start_date) <= getdate(end_date):
return start_date, end_date, last_gl_entry
else:
return None, None, None
def calculate_amount(doc, item, last_gl_entry, total_days, total_booking_days, account_currency):
if doc.doctype == "Sales Invoice":
total_credit_debit, total_credit_debit_currency = "debit", "debit_in_account_currency"
deferred_account = "deferred_revenue_account"
@@ -123,28 +136,15 @@ def calculate_amount_and_base_amount(doc, item, last_gl_entry, total_days, total
return amount, base_amount
def book_deferred_income_or_expense(doc, start_date=None, end_date=None):
# book the expense/income on the last day, but it will be trigger on the 1st of month at 12:00 AM
# start_date: 1st of the last month or the start date
# end_date: end_date or today-1
def book_deferred_income_or_expense(doc, posting_date=None):
enable_check = "enable_deferred_revenue" \
if doc.doctype=="Sales Invoice" else "enable_deferred_expense"
gl_entries = []
for item in doc.get('items'):
if not item.get(enable_check): continue
skip = False
last_gl_entry, booking_start_date, booking_end_date, skip = \
get_booking_dates(doc, item, start_date, end_date)
if skip: continue
total_days = date_diff(item.service_end_date, item.service_start_date) + 1
total_booking_days = date_diff(booking_end_date, booking_start_date) + 1
def _book_deferred_revenue_or_expense(item):
start_date, end_date, last_gl_entry = get_booking_dates(doc, item, posting_date=posting_date)
if not (start_date and end_date): return
account_currency = get_account_currency(item.expense_account)
amount, base_amount = calculate_amount_and_base_amount(doc, item, last_gl_entry, total_days, total_booking_days)
if doc.doctype == "Sales Invoice":
against, project = doc.customer, doc.project
credit_account, debit_account = item.income_account, item.deferred_revenue_account
@@ -152,36 +152,62 @@ def book_deferred_income_or_expense(doc, start_date=None, end_date=None):
against, project = doc.supplier, item.project
credit_account, debit_account = item.deferred_expense_account, item.expense_account
# GL Entry for crediting the amount in the deferred expense
gl_entries.append(
doc.get_gl_dict({
"account": credit_account,
"against": against,
"credit": base_amount,
"credit_in_account_currency": amount,
"cost_center": item.cost_center,
"voucher_detail_no": item.name,
'posting_date': booking_end_date,
'project': project
}, account_currency)
)
# GL Entry to debit the amount from the expense
gl_entries.append(
doc.get_gl_dict({
"account": debit_account,
"against": against,
"debit": base_amount,
"debit_in_account_currency": amount,
"cost_center": item.cost_center,
"voucher_detail_no": item.name,
'posting_date': booking_end_date,
'project': project
}, account_currency)
)
total_days = date_diff(item.service_end_date, item.service_start_date) + 1
total_booking_days = date_diff(end_date, start_date) + 1
amount, base_amount = calculate_amount(doc, item, last_gl_entry,
total_days, total_booking_days, account_currency)
make_gl_entries(doc, credit_account, debit_account, against,
amount, base_amount, end_date, project, account_currency, item.cost_center, item.name)
if getdate(end_date) < getdate(posting_date) and not last_gl_entry:
_book_deferred_revenue_or_expense(item)
for item in doc.get('items'):
if item.get(enable_check):
_book_deferred_revenue_or_expense(item)
def make_gl_entries(doc, credit_account, debit_account, against,
amount, base_amount, posting_date, project, account_currency, cost_center, voucher_detail_no):
# GL Entry for crediting the amount in the deferred expense
from erpnext.accounts.general_ledger import make_gl_entries
gl_entries = []
gl_entries.append(
doc.get_gl_dict({
"account": credit_account,
"against": against,
"credit": base_amount,
"credit_in_account_currency": amount,
"cost_center": cost_center,
"voucher_detail_no": voucher_detail_no,
'posting_date': posting_date,
'project': project
}, account_currency)
)
# GL Entry to debit the amount from the expense
gl_entries.append(
doc.get_gl_dict({
"account": debit_account,
"against": against,
"debit": base_amount,
"debit_in_account_currency": amount,
"cost_center": cost_center,
"voucher_detail_no": voucher_detail_no,
'posting_date': posting_date,
'project': project
}, account_currency)
)
if gl_entries:
try:
make_gl_entries(gl_entries, cancel=(doc.docstatus == 2), merge_entries=True)
frappe.db.commit()
except:
frappe.db.rollback()
frappe.log_error(message = frappe.get_traceback(), title = _("Error while processing deferred accounting for {0}").format(doc.name))
title = _("Error while processing deferred accounting for {0}").format(doc.name)
traceback = frappe.get_traceback()
frappe.log_error(message=traceback , title=title)
sendmail_to_system_managers(title, traceback)

View File

@@ -98,6 +98,8 @@ class Account(NestedSet):
ancestors = get_root_company(self.company)
if ancestors:
if frappe.get_value("Company", self.company, "allow_account_creation_against_child_company"):
return
frappe.throw(_("Please add the account to root level Company - %s" % ancestors[0]))
else:
descendants = get_descendants_of('Company', self.company)
@@ -110,6 +112,8 @@ class Account(NestedSet):
["company", "name"], as_dict=True):
acc_name_map[d["company"]] = d["name"]
if not acc_name_map: return
for company in descendants:
doc = frappe.copy_doc(self)
doc.flags.ignore_root_company_validation = True

View File

@@ -23,6 +23,10 @@ frappe.treeview_settings["Account"] = {
if(r.message) {
let root_company = r.message.length ? r.message[0] : "";
me.page.fields_dict.root_company.set_value(root_company);
frappe.db.get_value("Company", {"name": company}, "allow_account_creation_against_child_company", (r) => {
frappe.flags.ignore_root_company_validation = r.allow_account_creation_against_child_company;
});
}
}
});
@@ -133,9 +137,10 @@ frappe.treeview_settings["Account"] = {
{
label:__("Add Child"),
condition: function(node) {
return frappe.boot.user.can_create.indexOf("Account") !== -1 &&
!frappe.treeview_settings['Account'].treeview.page.fields_dict.root_company.get_value() &&
node.expandable && !node.hide_add;
return frappe.boot.user.can_create.indexOf("Account") !== -1
&& (!frappe.treeview_settings['Account'].treeview.page.fields_dict.root_company.get_value()
|| frappe.flags.ignore_root_company_validation)
&& node.expandable && !node.hide_add;
},
click: function() {
var me = frappe.treeview_settings['Account'].treeview;

View File

@@ -144,7 +144,7 @@ def _make_test_records(verbose):
# related to Account Inventory Integration
["_Test Account Stock In Hand", "Current Assets", 0, None, None],
# fixed asset depreciation
["_Test Fixed Asset", "Current Assets", 0, "Fixed Asset", None],
["_Test Accumulated Depreciations", "Current Assets", 0, None, None],
@@ -181,13 +181,17 @@ def get_inventory_account(company, warehouse=None):
return account
def create_account(**kwargs):
account = frappe.get_doc(dict(
doctype = "Account",
account_name = kwargs.get('account_name'),
account_type = kwargs.get('account_type'),
parent_account = kwargs.get('parent_account'),
company = kwargs.get('company')
))
account.save()
return account.name
account = frappe.db.get_value("Account", filters={"account_name": kwargs.get("account_name"), "company": kwargs.get("company")})
if account:
return account
else:
account = frappe.get_doc(dict(
doctype = "Account",
account_name = kwargs.get('account_name'),
account_type = kwargs.get('account_type'),
parent_account = kwargs.get('parent_account'),
company = kwargs.get('company')
))
account.save()
return account.name

View File

@@ -23,36 +23,36 @@ class BankReconciliation(Document):
journal_entries = frappe.db.sql("""
select
"Journal Entry" as payment_document, t1.name as payment_entry,
t1.cheque_no as cheque_number, t1.cheque_date,
select
"Journal Entry" as payment_document, t1.name as payment_entry,
t1.cheque_no as cheque_number, t1.cheque_date,
sum(t2.debit_in_account_currency) as debit, sum(t2.credit_in_account_currency) as credit,
t1.posting_date, t2.against_account, t1.clearance_date, t2.account_currency
t1.posting_date, t2.against_account, t1.clearance_date, t2.account_currency
from
`tabJournal Entry` t1, `tabJournal Entry Account` t2
where
t2.parent = t1.name and t2.account = %s and t1.docstatus=1
and t1.posting_date >= %s and t1.posting_date <= %s
and t1.posting_date >= %s and t1.posting_date <= %s
and ifnull(t1.is_opening, 'No') = 'No' {0}
group by t2.account, t1.name
order by t1.posting_date ASC, t1.name DESC
""".format(condition), (self.bank_account, self.from_date, self.to_date), as_dict=1)
payment_entries = frappe.db.sql("""
select
"Payment Entry" as payment_document, name as payment_entry,
reference_no as cheque_number, reference_date as cheque_date,
if(paid_from=%(account)s, paid_amount, "") as credit,
if(paid_from=%(account)s, "", received_amount) as debit,
select
"Payment Entry" as payment_document, name as payment_entry,
reference_no as cheque_number, reference_date as cheque_date,
if(paid_from=%(account)s, paid_amount, 0) as credit,
if(paid_from=%(account)s, 0, received_amount) as debit,
posting_date, ifnull(party,if(paid_from=%(account)s,paid_to,paid_from)) as against_account, clearance_date,
if(paid_to=%(account)s, paid_to_account_currency, paid_from_account_currency) as account_currency
from `tabPayment Entry`
where
(paid_from=%(account)s or paid_to=%(account)s) and docstatus=1
and posting_date >= %(from)s and posting_date <= %(to)s {0}
order by
order by
posting_date ASC, name DESC
""".format(condition),
""".format(condition),
{"account":self.bank_account, "from":self.from_date, "to":self.to_date}, as_dict=1)
pos_entries = []
@@ -79,8 +79,12 @@ class BankReconciliation(Document):
for d in entries:
row = self.append('payment_entries', {})
amount = d.debit if d.debit else d.credit
d.amount = fmt_money(amount, 2, d.account_currency) + " " + (_("Dr") if d.debit else _("Cr"))
amount = flt(d.get('debit', 0)) - flt(d.get('credit', 0))
formatted_amount = fmt_money(abs(amount), 2, d.account_currency)
d.amount = formatted_amount + " " + (_("Dr") if amount > 0 else _("Cr"))
d.pop("credit")
d.pop("debit")
d.pop("account_currency")
@@ -103,10 +107,10 @@ class BankReconciliation(Document):
d.clearance_date = None
frappe.db.set_value(d.payment_document, d.payment_entry, "clearance_date", d.clearance_date)
frappe.db.sql("""update `tab{0}` set clearance_date = %s, modified = %s
where name=%s""".format(d.payment_document),
frappe.db.sql("""update `tab{0}` set clearance_date = %s, modified = %s
where name=%s""".format(d.payment_document),
(d.clearance_date, nowdate(), d.payment_entry))
clearance_date_updated = True
if clearance_date_updated:

View File

@@ -6,6 +6,7 @@ import frappe, erpnext
from frappe import _
from frappe.utils import flt, fmt_money, getdate, formatdate
from frappe.model.document import Document
from frappe.model.meta import get_field_precision
from erpnext.accounts.party import validate_party_gle_currency, validate_party_frozen_disabled
from erpnext.accounts.utils import get_account_currency
from erpnext.accounts.utils import get_fiscal_year
@@ -56,7 +57,7 @@ class GLEntry(Document):
.format(self.voucher_type, self.voucher_no, self.account))
# Zero value transaction is not allowed
if not (flt(self.debit) or flt(self.credit)):
if not (flt(self.debit, self.precision("debit")) or flt(self.credit, self.precision("credit"))):
frappe.throw(_("{0} {1}: Either debit or credit amount is required for {2}")
.format(self.voucher_type, self.voucher_no, self.account))
@@ -216,17 +217,23 @@ def validate_frozen_account(account, adv_adj=None):
def update_against_account(voucher_type, voucher_no):
entries = frappe.db.get_all("GL Entry",
filters={"voucher_type": voucher_type, "voucher_no": voucher_no},
fields=["name", "party", "against", "debit", "credit", "account"])
fields=["name", "party", "against", "debit", "credit", "account", "company"])
if not entries:
return
company_currency = erpnext.get_company_currency(entries[0].company)
precision = get_field_precision(frappe.get_meta("GL Entry")
.get_field("debit"), company_currency)
accounts_debited, accounts_credited = [], []
for d in entries:
if flt(d.debit > 0): accounts_debited.append(d.party or d.account)
if flt(d.credit) > 0: accounts_credited.append(d.party or d.account)
if flt(d.debit, precision) > 0: accounts_debited.append(d.party or d.account)
if flt(d.credit, precision) > 0: accounts_credited.append(d.party or d.account)
for d in entries:
if flt(d.debit > 0):
if flt(d.debit, precision) > 0:
new_against = ", ".join(list(set(accounts_credited)))
if flt(d.credit > 0):
if flt(d.credit, precision) > 0:
new_against = ", ".join(list(set(accounts_debited)))
if d.against != new_against:

View File

@@ -52,11 +52,6 @@ class JournalEntry(AccountsController):
self.update_loan()
self.update_inter_company_jv()
def before_print(self):
self.gl_entries = frappe.get_list("GL Entry",filters={"voucher_type": "Journal Entry",
"voucher_no": self.name} ,
fields=["account", "party_type", "party", "debit", "credit", "remarks"]
)
def get_title(self):
return self.pay_to_recd_from or self.accounts[0].account

View File

@@ -232,6 +232,13 @@ frappe.ui.form.on('Payment Entry', {
},
party_type: function(frm) {
let party_types = Object.keys(frappe.boot.party_account_types);
if(frm.doc.party_type && !party_types.includes(frm.doc.party_type)){
frm.set_value("party_type", "");
frappe.throw(__("Party can only be one of "+ party_types.join(", ")));
}
if(frm.doc.party) {
$.each(["party", "party_balance", "paid_from", "paid_to",
"paid_from_account_currency", "paid_from_account_balance",

File diff suppressed because it is too large Load Diff

View File

@@ -70,11 +70,6 @@ class PaymentEntry(AccountsController):
self.update_advance_paid()
self.update_expense_claim()
def before_print(self):
self.gl_entries = frappe.get_list("GL Entry",filters={"voucher_type": "Payment Entry",
"voucher_no": self.name} ,
fields=["account", "party_type", "party", "debit", "credit", "remarks"]
)
def on_cancel(self):
self.setup_party_account_field()
@@ -754,7 +749,7 @@ def get_outstanding_on_journal_entry(name):
@frappe.whitelist()
def get_reference_details(reference_doctype, reference_name, party_account_currency):
total_amount = outstanding_amount = exchange_rate = None
total_amount = outstanding_amount = exchange_rate = bill_no = None
ref_doc = frappe.get_doc(reference_doctype, reference_name)
company_currency = ref_doc.get("company_currency") or erpnext.get_company_currency(ref_doc.company)
@@ -788,6 +783,7 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
if reference_doctype in ("Sales Invoice", "Purchase Invoice"):
outstanding_amount = ref_doc.get("outstanding_amount")
bill_no = ref_doc.get("bill_no")
elif reference_doctype == "Expense Claim":
outstanding_amount = flt(ref_doc.get("total_sanctioned_amount")) \
- flt(ref_doc.get("total_amount+reimbursed")) - flt(ref_doc.get("total_advance_amount"))
@@ -804,7 +800,8 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
"due_date": ref_doc.get("due_date"),
"total_amount": total_amount,
"outstanding_amount": outstanding_amount,
"exchange_rate": exchange_rate
"exchange_rate": exchange_rate,
"bill_no": bill_no
})

View File

@@ -468,7 +468,7 @@ cur_frm.fields_dict["items"].grid.get_field("cost_center").get_query = function(
cur_frm.cscript.cost_center = function(doc, cdt, cdn){
var d = locals[cdt][cdn];
if(d.idx == 1 && d.cost_center){
if(d.cost_center){
var cl = doc.items || [];
for(var i = 0; i < cl.length; i++){
if(!cl[i].cost_center) cl[i].cost_center = d.cost_center;

View File

@@ -55,11 +55,6 @@ class PurchaseInvoice(BuyingController):
if not self.on_hold:
self.release_date = ''
def before_print(self):
self.gl_entries = frappe.get_list("GL Entry",filters={"voucher_type": "Purchase Invoice",
"voucher_no": self.name} ,
fields=["account", "party_type", "party", "debit", "credit"]
)
def invoice_is_blocked(self):
return self.on_hold and (not self.release_date or self.release_date > getdate(nowdate()))

View File

@@ -344,6 +344,7 @@ class TestPurchaseInvoice(unittest.TestCase):
pi = frappe.copy_doc(test_records[0])
pi.disable_rounded_total = 1
pi.allocate_advances_automatically = 0
pi.append("advances", {
"reference_type": "Journal Entry",
"reference_name": jv.name,
@@ -383,6 +384,7 @@ class TestPurchaseInvoice(unittest.TestCase):
pi = frappe.copy_doc(test_records[0])
pi.disable_rounded_total = 1
pi.allocate_advances_automatically = 0
pi.append("advances", {
"reference_type": "Journal Entry",
"reference_name": jv.name,
@@ -551,7 +553,7 @@ class TestPurchaseInvoice(unittest.TestCase):
sum(credit) as credit, debit_in_account_currency, credit_in_account_currency
from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=%s
group by account, voucher_no order by account asc;""", pi.name, as_dict=1)
stock_in_hand_account = get_inventory_account(pi.company, pi.get("items")[0].warehouse)
self.assertTrue(gl_entries)
@@ -634,7 +636,7 @@ class TestPurchaseInvoice(unittest.TestCase):
self.assertEqual(frappe.db.get_value("Serial No", pi.get("items")[0].rejected_serial_no,
"warehouse"), pi.get("items")[0].rejected_warehouse)
def test_outstanding_amount_after_advance_jv_cancelation(self):
from erpnext.accounts.doctype.journal_entry.test_journal_entry \
import test_records as jv_test_records
@@ -656,14 +658,14 @@ class TestPurchaseInvoice(unittest.TestCase):
pi.insert()
pi.submit()
pi.load_from_db()
#check outstanding after advance allocation
self.assertEqual(flt(pi.outstanding_amount), flt(pi.rounded_total - pi.total_advance))
#added to avoid Document has been modified exception
jv = frappe.get_doc("Journal Entry", jv.name)
jv.cancel()
pi.load_from_db()
#check outstanding after advance cancellation
self.assertEqual(flt(pi.outstanding_amount), flt(pi.rounded_total + pi.total_advance))
@@ -722,7 +724,7 @@ class TestPurchaseInvoice(unittest.TestCase):
shipping_rule = create_shipping_rule(shipping_rule_type = "Buying", shipping_rule_name = "Shipping Rule - Purchase Invoice Test")
pi = frappe.copy_doc(test_records[0])
pi.shipping_rule = shipping_rule.name
pi.insert()
@@ -740,14 +742,14 @@ class TestPurchaseInvoice(unittest.TestCase):
"tax_amount": shipping_amount,
"description": shipping_rule.name,
"add_deduct_tax": "Add"
}
}
pi.append("taxes", shipping_charge)
pi.save()
self.assertEqual(pi.net_total, 1250)
self.assertEqual(pi.total_taxes_and_charges, 462.3)
self.assertEqual(pi.grand_total, 1712.3)
self.assertEqual(pi.grand_total, 1712.3)
def test_make_pi_without_terms(self):
pi = make_purchase_invoice(do_not_save=1)

File diff suppressed because it is too large Load Diff

View File

@@ -205,11 +205,6 @@ class SalesInvoice(SellingController):
def before_cancel(self):
self.update_time_sheet(None)
def before_print(self):
self.gl_entries = frappe.get_list("GL Entry",filters={"voucher_type": "Sales Invoice",
"voucher_no": self.name} ,
fields=["account", "party_type", "party", "debit", "credit"]
)
def on_cancel(self):
self.check_close_sales_order("sales_order")

View File

@@ -14,8 +14,9 @@ from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_per
from erpnext.exceptions import InvalidAccountCurrency, InvalidCurrency
from erpnext.stock.doctype.serial_no.serial_no import SerialNoWarehouseError
from frappe.model.naming import make_autoname
from erpnext.accounts.doctype.account.test_account import get_inventory_account
from erpnext.accounts.doctype.account.test_account import get_inventory_account, create_account
from erpnext.controllers.taxes_and_totals import get_itemised_tax_breakup_data
from erpnext.stock.doctype.item.test_item import create_item
from six import iteritems
class TestSalesInvoice(unittest.TestCase):
def make(self):
@@ -762,7 +763,7 @@ class TestSalesInvoice(unittest.TestCase):
set_perpetual_inventory(0)
frappe.db.sql("delete from `tabPOS Profile`")
def test_pos_si_without_payment(self):
set_perpetual_inventory()
make_pos_profile()
@@ -854,6 +855,7 @@ class TestSalesInvoice(unittest.TestCase):
jv.submit()
si = frappe.copy_doc(test_records[0])
si.allocate_advances_automatically = 0
si.append("advances", {
"doctype": "Sales Invoice Advance",
"reference_type": "Journal Entry",
@@ -1514,6 +1516,56 @@ class TestSalesInvoice(unittest.TestCase):
accounts_settings.allow_cost_center_in_entry_of_bs_account = 0
accounts_settings.save()
def test_deferred_revenue(self):
deferred_account = create_account(account_name="Deferred Revenue",
parent_account="Current Liabilities - _TC", company="_Test Company")
item = create_item("_Test Item for Deferred Accounting")
item.enable_deferred_revenue = 1
item.deferred_revenue_account = deferred_account
item.no_of_months = 12
item.save()
si = create_sales_invoice(item=item.name, posting_date="2019-01-10", do_not_submit=True)
si.items[0].enable_deferred_revenue = 1
si.items[0].service_start_date = "2019-01-10"
si.items[0].service_end_date = "2019-03-15"
si.items[0].deferred_revenue_account = deferred_account
si.save()
si.submit()
from erpnext.accounts.deferred_revenue import convert_deferred_revenue_to_income
convert_deferred_revenue_to_income(start_date="2019-01-01", end_date="2019-01-31")
expected_gle = [
[deferred_account, 33.85, 0.0, "2019-01-31"],
["Sales - _TC", 0.0, 33.85, "2019-01-31"]
]
self.check_gl_entries(si.name, expected_gle, "2019-01-10")
convert_deferred_revenue_to_income(start_date="2019-01-01", end_date="2019-03-31")
expected_gle = [
[deferred_account, 43.08, 0.0, "2019-02-28"],
["Sales - _TC", 0.0, 43.08, "2019-02-28"],
[deferred_account, 23.07, 0.0, "2019-03-15"],
["Sales - _TC", 0.0, 23.07, "2019-03-15"]
]
self.check_gl_entries(si.name, expected_gle, "2019-01-31")
def check_gl_entries(self, voucher_no, expected_gle, posting_date):
gl_entries = frappe.db.sql("""select account, debit, credit, posting_date
from `tabGL Entry`
where voucher_type='Sales Invoice' and voucher_no=%s and posting_date > %s
order by posting_date asc, account asc""", (voucher_no, posting_date), as_dict=1)
for i, gle in enumerate(gl_entries):
self.assertEqual(expected_gle[i][0], gle.account)
self.assertEqual(expected_gle[i][1], gle.debit)
self.assertEqual(expected_gle[i][2], gle.credit)
self.assertEqual(getdate(expected_gle[i][3]), gle.posting_date)
def create_sales_invoice(**args):
si = frappe.new_doc("Sales Invoice")
@@ -1611,4 +1663,4 @@ def get_outstanding_amount(against_voucher_type, against_voucher, account, party
if against_voucher_type == 'Purchase Invoice':
bal = bal * -1
return bal
return bal

View File

@@ -333,6 +333,7 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
var me = this;
this.frm = {}
this.load_data(true);
this.frm.doc.offline_pos_name = '';
this.setup();
this.set_default_customer()
},
@@ -345,7 +346,6 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
if (load_doc) {
this.frm.doc = JSON.parse(localStorage.getItem('doc'));
this.frm.doc.offline_pos_name = null;
}
$.each(this.meta, function (i, data) {
@@ -641,7 +641,7 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
me.list_customers_btn.toggleClass("view_customer");
me.pos_bill.show();
me.list_customers_btn.show();
me.frm.doc.offline_pos_name = $(this).parents().attr('invoice-name')
me.frm.doc.offline_pos_name = $(this).parents().attr('invoice-name');
me.edit_record();
})
@@ -984,7 +984,7 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
}
if(!this.customer_doc.fields_dict.customer_pos_id.value) {
this.customer_doc.set_value("customer_pos_id", $.now())
this.customer_doc.set_value("customer_pos_id", frappe.datetime.now_datetime())
}
},
@@ -1686,10 +1686,18 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
create_invoice: function () {
var me = this;
var existing_pos_list = [];
var invoice_data = {};
this.si_docs = this.get_doc_from_localstorage();
if (this.frm.doc.offline_pos_name) {
if(this.si_docs) {
this.si_docs.forEach((row) => {
existing_pos_list.push(Object.keys(row));
});
}
if (this.frm.doc.offline_pos_name
&& in_list(existing_pos_list, this.frm.doc.offline_pos_name)) {
this.update_invoice()
//to retrieve and set the default payment
invoice_data[this.frm.doc.offline_pos_name] = this.frm.doc;
@@ -1698,8 +1706,8 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
this.frm.doc.paid_amount = this.frm.doc.net_total
this.frm.doc.outstanding_amount = 0
} else {
this.frm.doc.offline_pos_name = $.now();
} else if(!this.frm.doc.offline_pos_name) {
this.frm.doc.offline_pos_name = frappe.datetime.now_datetime();
this.frm.doc.posting_date = frappe.datetime.get_today();
this.frm.doc.posting_time = frappe.datetime.now_time();
this.frm.doc.pos_total_qty = this.frm.doc.qty_total;

View File

@@ -573,13 +573,17 @@ def get_party_shipping_address(doctype, name):
else:
return ''
def get_partywise_advanced_payment_amount(party_type="Customer"):
def get_partywise_advanced_payment_amount(party_type, posting_date = None):
cond = "1=1"
if posting_date:
cond = "posting_date <= '{0}'".format(posting_date)
data = frappe.db.sql(""" SELECT party, sum({0}) as amount
FROM `tabGL Entry`
WHERE
party_type = %s and against_voucher is null
GROUP BY party"""
.format(("credit") if party_type == "Customer" else "debit") , party_type)
and {1} GROUP BY party"""
.format(("credit") if party_type == "Customer" else "debit", cond) , party_type)
if data:
return frappe._dict(data)

View File

@@ -107,26 +107,28 @@
<thead>
<tr>
{% if(report.report_name === "Accounts Receivable" || report.report_name === "Accounts Payable") { %}
<th style="width: 7%">{%= __("Date") %}</th>
<th style="width: 7%">{%= __("Age (Days)") %}</th>
<th style="width: 13%">{%= __("Reference") %}</th>
{% if(report.report_name === "Accounts Receivable") { %}
<th style="width: 10%">{%= __("Sales Person") %}</th>
<th style="width: 9%">{%= __("Date") %}</th>
<th style="width: 5%">{%= __("Age (Days)") %}</th>
{% if(report.report_name === "Accounts Receivable" && filters.show_sales_person_in_print) { %}
<th style="width: 16%">{%= __("Reference") %}</th>
<th style="width: 10%">{%= __("Sales Person") %}</th>
{% } else { %}
<th style="width: 26%">{%= __("Reference") %}</th>
{% } %}
{% if(!filters.show_pdc_in_print) { %}
<th style="width: 20%">{%= (filters.customer || filters.supplier) ? __("Remarks"): __("Party") %}</th>
<th style="width: 20%">{%= (filters.customer || filters.supplier) ? __("Remarks"): __("Party") %}</th>
{% } %}
<th style="width: 10%; text-align: right">{%= __("Invoiced Amount") %}</th>
{% if(!filters.show_pdc_in_print) { %}
<th style="width: 10%; text-align: right">{%= __("Paid Amount") %}</th>
<th style="width: 10%; text-align: right">{%= report.report_name === "Accounts Receivable" ? __('Credit Note') : __('Debit Note') %}</th>
{% } %}
<th style="width: 15%; text-align: right">{%= __("Outstanding Amount") %}</th>
<th style="width: 10%; text-align: right">{%= __("Outstanding Amount") %}</th>
{% if(filters.show_pdc_in_print) { %}
{% if(report.report_name === "Accounts Receivable") { %}
<th style="width: 10%">{%= __("Customer LPO No.") %}</th>
{% } %}
<th style="width: 10%">{%= __("PDC/LC Date") %}</th>
<th style="width: 10%">{%= __("PDC/LC Ref") %}</th>
<th style="width: 10%">{%= __("PDC/LC Amount") %}</th>
<th style="width: 10%">{%= __("Remaining Balance") %}</th>
@@ -155,7 +157,7 @@
{%= data[i]["voucher_no"] %}
</td>
{% if(report.report_name === "Accounts Receivable") { %}
{% if(report.report_name === "Accounts Receivable" && filters.show_sales_person_in_print) { %}
<td>{%= data[i]["sales_person"] %}</td>
{% } %}
@@ -195,7 +197,6 @@
<td style="text-align: right">
{%= data[i]["po_no"] %}</td>
{% } %}
<td style="text-align: right">{%= frappe.datetime.str_to_user(data[i][("pdc/lc_date")]) %}</td>
<td style="text-align: right">{%= data[i][("pdc/lc_ref")] %}</td>
<td style="text-align: right">{%= format_currency(data[i][("pdc/lc_amount")], data[i]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i][("remaining_balance")], data[i]["currency"]) %}</td>
@@ -226,7 +227,6 @@
<td style="text-align: right">
{%= data[i][__("Customer LPO")] %}</td>
{% } %}
<td style="text-align: right">{%= frappe.datetime.str_to_user(data[i][__("PDC/LC Date")]) %}</td>
<td style="text-align: right">{%= data[i][("pdc/lc_ref")] %}</td>
<td style="text-align: right">{%= format_currency(data[i][("pdc/lc_amount")], data[i]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i][("remaining_balance")], data[i]["currency"]) %}</td>

View File

@@ -102,14 +102,19 @@ frappe.query_reports["Accounts Receivable"] = {
"fieldtype": "Link",
"options": "Sales Person"
},
{
"fieldname":"based_on_payment_terms",
"label": __("Based On Payment Terms"),
"fieldtype": "Check",
},
{
"fieldname":"show_pdc_in_print",
"label": __("Show PDC in Print"),
"fieldtype": "Check",
},
{
"fieldname":"based_on_payment_terms",
"label": __("Based On Payment Terms"),
"fieldname":"show_sales_person_in_print",
"label": __("Show Sales Person in Print"),
"fieldtype": "Check",
},
{

View File

@@ -136,7 +136,8 @@ class AccountsReceivableSummary(ReceivablePayableReport):
partywise_total = self.get_partywise_total(party_naming_by, args)
partywise_advance_amount = get_partywise_advanced_payment_amount(args.get("party_type")) or {}
partywise_advance_amount = get_partywise_advanced_payment_amount(args.get("party_type"),
self.filters.get("report_date")) or {}
for party, party_dict in iteritems(partywise_total):
row = [party]
@@ -144,7 +145,10 @@ class AccountsReceivableSummary(ReceivablePayableReport):
row += [self.get_party_name(args.get("party_type"), party)]
row += [partywise_advance_amount.get(party, 0)]
paid_amt = flt(party_dict.paid_amt - partywise_advance_amount.get(party, 0))
paid_amt = 0
if party_dict.paid_amt > 0:
paid_amt = flt(party_dict.paid_amt - partywise_advance_amount.get(party, 0))
row += [
party_dict.invoiced_amt, paid_amt, party_dict.credit_amt, party_dict.outstanding_amt,

View File

@@ -4,7 +4,7 @@
from __future__ import unicode_literals
import frappe
import erpnext
from frappe import _
from frappe import _, scrub
from frappe.utils import getdate, nowdate
from six import iteritems, itervalues
@@ -14,6 +14,9 @@ class PartyLedgerSummaryReport(object):
self.filters.from_date = getdate(self.filters.from_date or nowdate())
self.filters.to_date = getdate(self.filters.to_date or nowdate())
if not self.filters.get("company"):
self.filters["company"] = frappe.db.get_single_value('Global Defaults', 'default_company')
def run(self, args):
if self.filters.from_date > self.filters.to_date:
frappe.throw(_("From Date must be before To Date"))
@@ -21,10 +24,9 @@ class PartyLedgerSummaryReport(object):
self.filters.party_type = args.get("party_type")
self.party_naming_by = frappe.db.get_value(args.get("naming_by")[0], None, args.get("naming_by")[1])
discount_account_field = "discount_allowed_account" if self.filters.party_type == "Customer" \
else "discount_received_account"
self.round_off_account, self.write_off_account, self.discount_account = frappe.get_cached_value('Company',
self.filters.company, ["round_off_account", "write_off_account", discount_account_field])
self.get_gl_entries()
self.get_return_invoices()
self.get_party_adjustment_amounts()
columns = self.get_columns()
data = self.get_data()
@@ -48,7 +50,6 @@ class PartyLedgerSummaryReport(object):
})
credit_or_debit_note = "Credit Note" if self.filters.party_type == "Customer" else "Debit Note"
discount_allowed_or_received = "Discount Allowed" if self.filters.party_type == "Customer" else "Discount Received"
columns += [
{
@@ -79,27 +80,19 @@ class PartyLedgerSummaryReport(object):
"options": "currency",
"width": 120
},
{
"label": _(discount_allowed_or_received),
"fieldname": "discount_amount",
]
for account in self.party_adjustment_accounts:
columns.append({
"label": account,
"fieldname": "adj_" + scrub(account),
"fieldtype": "Currency",
"options": "currency",
"width": 120
},
{
"label": _("Write Off Amount"),
"fieldname": "write_off_amount",
"fieldtype": "Currency",
"options": "currency",
"width": 120
},
{
"label": _("Other Adjustments"),
"fieldname": "adjustment_amount",
"fieldtype": "Currency",
"options": "currency",
"width": 120
},
"width": 120,
"is_adjustment": 1
})
columns += [
{
"label": _("Closing Balance"),
"fieldname": "closing_balance",
@@ -119,17 +112,10 @@ class PartyLedgerSummaryReport(object):
return columns
def get_data(self):
if not self.filters.get("company"):
self.filters["company"] = frappe.db.get_single_value('Global Defaults', 'default_company')
company_currency = frappe.get_cached_value('Company', self.filters.get("company"), "default_currency")
invoice_dr_or_cr = "debit" if self.filters.party_type == "Customer" else "credit"
reverse_dr_or_cr = "credit" if self.filters.party_type == "Customer" else "debit"
self.get_gl_entries()
self.get_return_invoices()
self.get_party_adjustment_amounts()
self.party_data = frappe._dict({})
for gle in self.gl_entries:
self.party_data.setdefault(gle.party, frappe._dict({
@@ -146,7 +132,7 @@ class PartyLedgerSummaryReport(object):
amount = gle.get(invoice_dr_or_cr) - gle.get(reverse_dr_or_cr)
self.party_data[gle.party].closing_balance += amount
if gle.posting_date < self.filters.from_date:
if gle.posting_date < self.filters.from_date or gle.is_opening == "Yes":
self.party_data[gle.party].opening_balance += amount
else:
if amount > 0:
@@ -161,9 +147,10 @@ class PartyLedgerSummaryReport(object):
if row.opening_balance or row.invoiced_amount or row.paid_amount or row.return_amount or row.closing_amount:
total_party_adjustment = sum([amount for amount in itervalues(self.party_adjustment_details.get(party, {}))])
row.paid_amount -= total_party_adjustment
row.discount_amount = self.party_adjustment_details.get(party, {}).get(self.discount_account, 0)
row.write_off_amount = self.party_adjustment_details.get(party, {}).get(self.write_off_account, 0)
row.adjustment_amount = total_party_adjustment - row.discount_amount - row.write_off_amount
adjustments = self.party_adjustment_details.get(party, {})
for account in self.party_adjustment_accounts:
row["adj_" + scrub(account)] = adjustments.get(account, 0)
out.append(row)
@@ -182,7 +169,7 @@ class PartyLedgerSummaryReport(object):
self.gl_entries = frappe.db.sql("""
select
gle.posting_date, gle.party, gle.voucher_type, gle.voucher_no, gle.against_voucher_type,
gle.against_voucher, gle.debit, gle.credit {join_field}
gle.against_voucher, gle.debit, gle.credit, gle.is_opening {join_field}
from `tabGL Entry` gle
{join}
where
@@ -254,9 +241,10 @@ class PartyLedgerSummaryReport(object):
def get_party_adjustment_amounts(self):
conditions = self.prepare_conditions()
income_or_expense = "Expense" if self.filters.party_type == "Customer" else "Income"
income_or_expense = "Expense Account" if self.filters.party_type == "Customer" else "Income Account"
invoice_dr_or_cr = "debit" if self.filters.party_type == "Customer" else "credit"
reverse_dr_or_cr = "credit" if self.filters.party_type == "Customer" else "debit"
round_off_account = frappe.get_cached_value('Company', self.filters.company, "round_off_account")
gl_entries = frappe.db.sql("""
select
@@ -267,7 +255,7 @@ class PartyLedgerSummaryReport(object):
docstatus < 2
and (voucher_type, voucher_no) in (
select voucher_type, voucher_no from `tabGL Entry` gle, `tabAccount` acc
where acc.name = gle.account and acc.root_type = '{income_or_expense}'
where acc.name = gle.account and acc.account_type = '{income_or_expense}'
and gle.posting_date between %(from_date)s and %(to_date)s and gle.docstatus < 2
) and (voucher_type, voucher_no) in (
select voucher_type, voucher_no from `tabGL Entry` gle
@@ -277,6 +265,7 @@ class PartyLedgerSummaryReport(object):
""".format(conditions=conditions, income_or_expense=income_or_expense), self.filters, as_dict=True)
self.party_adjustment_details = {}
self.party_adjustment_accounts = set()
adjustment_voucher_entries = {}
for gle in gl_entries:
adjustment_voucher_entries.setdefault((gle.voucher_type, gle.voucher_no), [])
@@ -288,12 +277,12 @@ class PartyLedgerSummaryReport(object):
has_irrelevant_entry = False
for gle in voucher_gl_entries:
if gle.account == self.round_off_account:
if gle.account == round_off_account:
continue
elif gle.party:
parties.setdefault(gle.party, 0)
parties[gle.party] += gle.get(reverse_dr_or_cr) - gle.get(invoice_dr_or_cr)
elif frappe.get_cached_value("Account", gle.account, "root_type") == income_or_expense:
elif frappe.get_cached_value("Account", gle.account, "account_type") == income_or_expense:
accounts.setdefault(gle.account, 0)
accounts[gle.account] += gle.get(invoice_dr_or_cr) - gle.get(reverse_dr_or_cr)
else:
@@ -303,11 +292,13 @@ class PartyLedgerSummaryReport(object):
if len(parties) == 1:
party = parties.keys()[0]
for account, amount in iteritems(accounts):
self.party_adjustment_accounts.add(account)
self.party_adjustment_details.setdefault(party, {})
self.party_adjustment_details[party].setdefault(account, 0)
self.party_adjustment_details[party][account] += amount
elif len(accounts) == 1 and not has_irrelevant_entry:
account = accounts.keys()[0]
self.party_adjustment_accounts.add(account)
for party, amount in iteritems(parties):
self.party_adjustment_details.setdefault(party, {})
self.party_adjustment_details[party].setdefault(account, 0)

View File

@@ -54,8 +54,10 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
delivery_note, d.income_account, d.cost_center, d.stock_qty, d.stock_uom
]
row += [(d.base_net_rate * d.qty)/d.stock_qty, d.base_net_amount] \
if d.stock_uom != d.uom and d.stock_qty != 0 else [d.base_net_rate, d.base_net_amount]
if d.stock_uom != d.uom and d.stock_qty:
row += [(d.base_net_rate * d.qty)/d.stock_qty, d.base_net_amount]
else:
row += [d.base_net_rate, d.base_net_amount]
total_tax = 0
for tax in tax_columns:
@@ -108,13 +110,13 @@ def get_conditions(filters):
conditions += """ and exists(select name from `tabSales Invoice Payment`
where parent=`tabSales Invoice`.name
and ifnull(`tabSales Invoice Payment`.mode_of_payment, '') = %(mode_of_payment)s)"""
if filters.get("warehouse"):
conditions += """ and exists(select name from `tabSales Invoice Item`
where parent=`tabSales Invoice`.name
and ifnull(`tabSales Invoice Item`.warehouse, '') = %(warehouse)s)"""
if filters.get("brand"):
conditions += """ and exists(select name from `tabSales Invoice Item`
where parent=`tabSales Invoice`.name
@@ -131,10 +133,10 @@ def get_conditions(filters):
def get_items(filters, additional_query_columns):
conditions = get_conditions(filters)
match_conditions = frappe.build_match_conditions("Sales Invoice")
if match_conditions:
match_conditions = " and {0} ".format(match_conditions)
if additional_query_columns:
additional_query_columns = ', ' + ', '.join(additional_query_columns)

View File

@@ -133,6 +133,13 @@ def get_columns(filters):
"options": filters.get("based_on"),
"width": 300
},
{
"fieldname": "currency",
"label": _("Currency"),
"fieldtype": "Link",
"options": "Currency",
"hidden": 1
},
{
"fieldname": "income",
"label": _("Income"),
@@ -153,13 +160,6 @@ def get_columns(filters):
"fieldtype": "Currency",
"options": "currency",
"width": 120
},
{
"fieldname": "currency",
"label": _("Currency"),
"fieldtype": "Link",
"options": "Currency",
"hidden": 1
}
]
@@ -191,4 +191,4 @@ def set_gl_entries_by_account(company, from_date, to_date, based_on, gl_entries_
for entry in gl_entries:
gl_entries_by_account.setdefault(entry.based_on, []).append(entry)
return gl_entries_by_account
return gl_entries_by_account

View File

@@ -55,12 +55,15 @@ def get_result(filters):
supplier = supplier_map[d]
tds_doc = tds_docs[supplier.tax_withholding_category]
account = [i.account for i in tds_doc.accounts if i.company == filters.company][0]
account_list = [i.account for i in tds_doc.accounts if i.company == filters.company]
if account_list:
account = account_list[0]
for k in gle_map[d]:
if k.party == supplier_map[d] and k.credit > 0:
total_amount_credited += k.credit
elif k.account == account and k.credit > 0:
elif account_list and k.account == account and k.credit > 0:
tds_deducted = k.credit
total_amount_credited += k.credit

View File

@@ -31,7 +31,7 @@ frappe.ui.form.on('Asset', {
}
};
});
frm.set_query("cost_center", function() {
return {
"filters": {
@@ -206,12 +206,10 @@ frappe.ui.form.on('Asset', {
erpnext.asset.set_accululated_depreciation(frm);
},
depreciation_method: function(frm) {
frm.events.make_schedules_editable(frm);
},
make_schedules_editable: function(frm) {
var is_editable = frm.doc.depreciation_method==="Manual" ? 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.fields_dict["schedules"].grid.toggle_enable("schedule_date", is_editable);
frm.fields_dict["schedules"].grid.toggle_enable("depreciation_amount", is_editable);
@@ -296,6 +294,44 @@ frappe.ui.form.on('Asset', {
})
frm.toggle_reqd("finance_books", frm.doc.calculate_depreciation);
},
set_depreciation_rate: function(frm, row) {
if (row.total_number_of_depreciations && row.frequency_of_depreciation) {
frappe.call({
method: "get_depreciation_rate",
doc: frm.doc,
args: row,
callback: function(r) {
if (r.message) {
frappe.model.set_value(row.doctype, row.name, "rate_of_depreciation", r.message);
}
}
});
}
}
});
frappe.ui.form.on('Asset Finance Book', {
depreciation_method: function(frm, cdt, cdn) {
const row = locals[cdt][cdn];
frm.events.set_depreciation_rate(frm, row);
frm.events.make_schedules_editable(frm);
},
expected_value_after_useful_life: function(frm, cdt, cdn) {
const row = locals[cdt][cdn];
frm.events.set_depreciation_rate(frm, row);
},
frequency_of_depreciation: function(frm, cdt, cdn) {
const row = locals[cdt][cdn];
frm.events.set_depreciation_rate(frm, row);
},
total_number_of_depreciations: function(frm, cdt, cdn) {
const row = locals[cdt][cdn];
frm.events.set_depreciation_rate(frm, row);
}
});

View File

@@ -3,8 +3,9 @@
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe, erpnext
import frappe, erpnext, math, json
from frappe import _
from six import string_types
from frappe.utils import flt, add_months, cint, nowdate, getdate, today, date_diff
from frappe.model.document import Document
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
@@ -20,6 +21,7 @@ class Asset(AccountsController):
self.validate_item()
self.set_missing_values()
if self.calculate_depreciation:
self.set_depreciation_rate()
self.make_depreciation_schedule()
self.set_accumulated_depreciation()
else:
@@ -89,17 +91,22 @@ class Asset(AccountsController):
if self.is_existing_asset:
return
date = nowdate()
docname = self.purchase_receipt or self.purchase_invoice
if docname:
doctype = 'Purchase Receipt' if self.purchase_receipt else 'Purchase Invoice'
date = frappe.db.get_value(doctype, docname, 'posting_date')
if self.available_for_use_date and getdate(self.available_for_use_date) < getdate(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"))
def set_depreciation_rate(self):
for d in self.get("finance_books"):
d.rate_of_depreciation = self.get_depreciation_rate(d)
def make_depreciation_schedule(self):
if self.depreciation_method != 'Manual':
depreciation_method = [d.depreciation_method for d in self.finance_books]
if 'Manual' not in depreciation_method:
self.schedules = []
if not self.get("schedules") and self.available_for_use_date:
@@ -254,14 +261,16 @@ class Asset(AccountsController):
return flt(self.get('finance_books')[cint(idx)-1].value_after_depreciation)
def get_depreciation_amount(self, depreciable_value, total_number_of_depreciations, row):
percentage_value = 100.0 if row.depreciation_method == 'Written Down Value' else 200.0
if row.depreciation_method in ["Straight Line", "Manual"]:
amt = (flt(self.gross_purchase_amount) - flt(row.expected_value_after_useful_life) -
flt(self.opening_accumulated_depreciation))
factor = percentage_value / cint(total_number_of_depreciations)
depreciation_amount = flt(depreciable_value * factor / 100, 0)
value_after_depreciation = flt(depreciable_value) - depreciation_amount
if value_after_depreciation < flt(row.expected_value_after_useful_life):
depreciation_amount = flt(depreciable_value) - flt(row.expected_value_after_useful_life)
depreciation_amount = amt * row.rate_of_depreciation
else:
depreciation_amount = flt(depreciable_value) * (flt(row.rate_of_depreciation) / 100)
value_after_depreciation = flt(depreciable_value) - depreciation_amount
if value_after_depreciation < flt(row.expected_value_after_useful_life):
depreciation_amount = flt(depreciable_value) - flt(row.expected_value_after_useful_life)
return depreciation_amount
@@ -394,6 +403,32 @@ class Asset(AccountsController):
make_gl_entries(gl_entries)
self.db_set('booked_fixed_asset', 1)
def get_depreciation_rate(self, args):
if isinstance(args, string_types):
args = json.loads(args)
number_of_depreciations_booked = 0
if self.is_existing_asset:
number_of_depreciations_booked = self.number_of_depreciations_booked
float_precision = cint(frappe.db.get_default("float_precision")) or 2
tot_no_of_depreciation = flt(args.get("total_number_of_depreciations")) - flt(number_of_depreciations_booked)
if args.get("depreciation_method") in ["Straight Line", "Manual"]:
return 1.0 / tot_no_of_depreciation
if args.get("depreciation_method") == 'Double Declining Balance':
return 200.0 / args.get("total_number_of_depreciations")
if args.get("depreciation_method") == "Written Down Value" and not args.get("rate_of_depreciation"):
no_of_years = flt(args.get("total_number_of_depreciations") * flt(args.get("frequency_of_depreciation"))) / 12
value = flt(args.get("expected_value_after_useful_life")) / flt(self.gross_purchase_amount)
# square root of flt(salvage_value) / flt(asset_cost)
depreciation_rate = math.pow(value, 1.0/flt(no_of_years, 2))
return 100 * (1 - flt(depreciation_rate, float_precision))
def update_maintenance_status():
assets = frappe.get_all('Asset', filters = {'docstatus': 1, 'maintenance_required': 1})
@@ -480,7 +515,6 @@ def create_asset_adjustment(asset, asset_category, company):
@frappe.whitelist()
def transfer_asset(args):
import json
args = json.loads(args)
if args.get('serial_no'):
@@ -557,4 +591,4 @@ def make_journal_entry(asset_name):
return je
def is_cwip_accounting_disabled():
return cint(frappe.db.get_single_value("Asset Settings", "disable_cwip_accounting"))
return cint(frappe.db.get_single_value("Asset Settings", "disable_cwip_accounting"))

View File

@@ -160,9 +160,9 @@ class TestAsset(unittest.TestCase):
asset.save()
expected_schedules = [
["2020-06-06", 66667.0, 66667.0],
["2021-04-06", 22222.0, 88889.0],
["2022-02-06", 1111.0, 90000.0]
["2020-06-06", 66666.67, 66666.67],
["2021-04-06", 22222.22, 88888.89],
["2022-02-06", 1111.11, 90000.0]
]
schedules = [[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount]
@@ -192,8 +192,8 @@ class TestAsset(unittest.TestCase):
asset.save()
expected_schedules = [
["2020-06-06", 33333.0, 83333.0],
["2021-04-06", 6667.0, 90000.0]
["2020-06-06", 33333.33, 83333.33],
["2021-04-06", 6666.67, 90000.0]
]
schedules = [[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount]
@@ -209,7 +209,7 @@ class TestAsset(unittest.TestCase):
asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
asset = frappe.get_doc('Asset', asset_name)
asset.calculate_depreciation = 1
asset.purchase_date = '2020-06-06'
asset.purchase_date = '2020-01-30'
asset.is_existing_asset = 0
asset.available_for_use_date = "2020-01-30"
asset.append("finance_books", {
@@ -244,7 +244,7 @@ class TestAsset(unittest.TestCase):
asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
asset = frappe.get_doc('Asset', asset_name)
asset.calculate_depreciation = 1
asset.purchase_date = '2020-06-06'
asset.purchase_date = '2020-01-30'
asset.available_for_use_date = "2020-01-30"
asset.append("finance_books", {
"expected_value_after_useful_life": 10000,
@@ -277,6 +277,37 @@ class TestAsset(unittest.TestCase):
self.assertEqual(gle, expected_gle)
self.assertEqual(asset.get("value_after_depreciation"), 0)
def test_depreciation_entry_for_wdv(self):
pr = make_purchase_receipt(item_code="Macbook Pro",
qty=1, rate=8000.0, location="Test Location")
asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
asset = frappe.get_doc('Asset', asset_name)
asset.calculate_depreciation = 1
asset.available_for_use_date = '2030-06-06'
asset.purchase_date = '2030-06-06'
asset.append("finance_books", {
"expected_value_after_useful_life": 1000,
"depreciation_method": "Written Down Value",
"total_number_of_depreciations": 3,
"frequency_of_depreciation": 12,
"depreciation_start_date": "2030-12-31"
})
asset.save(ignore_permissions=True)
self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0)
expected_schedules = [
["2030-12-31", 4000.0, 4000.0],
["2031-12-31", 2000.0, 6000.0],
["2032-12-31", 1000.0, 7000.0],
]
schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)]
for d in asset.get("schedules")]
self.assertEqual(schedules, expected_schedules)
def test_depreciation_entry_cancellation(self):
pr = make_purchase_receipt(item_code="Macbook Pro",
qty=1, rate=100000.0, location="Test Location")

View File

@@ -1,5 +1,6 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
@@ -14,11 +15,13 @@
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "",
"fetch_if_empty": 0,
"fieldname": "finance_book",
"fieldtype": "Link",
"hidden": 0,
@@ -42,14 +45,17 @@
"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": "depreciation_method",
"fieldtype": "Select",
"hidden": 0,
@@ -73,14 +79,17 @@
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "total_number_of_depreciations",
"fieldtype": "Int",
"hidden": 0,
@@ -103,14 +112,17 @@
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_5",
"fieldtype": "Column Break",
"hidden": 0,
@@ -133,14 +145,17 @@
"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": "frequency_of_depreciation",
"fieldtype": "Int",
"hidden": 0,
@@ -163,15 +178,18 @@
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:parent.doctype == 'Asset'",
"fetch_if_empty": 0,
"fieldname": "depreciation_start_date",
"fieldtype": "Date",
"hidden": 0,
@@ -194,16 +212,19 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0",
"depends_on": "eval:parent.doctype == 'Asset'",
"fetch_if_empty": 0,
"fieldname": "expected_value_after_useful_life",
"fieldtype": "Currency",
"hidden": 0,
@@ -227,14 +248,17 @@
"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": "value_after_depreciation",
"fieldtype": "Currency",
"hidden": 1,
@@ -258,20 +282,54 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.depreciation_method == 'Written Down Value'",
"description": "In Percentage",
"fetch_if_empty": 0,
"fieldname": "rate_of_depreciation",
"fieldtype": "Percent",
"hidden": 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 of Depreciation",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2018-05-12 14:56:44.800046",
"modified": "2019-04-09 19:45:14.523488",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset Finance Book",
@@ -280,10 +338,10 @@
"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
"track_seen": 0,
"track_views": 0
}

View File

@@ -379,7 +379,9 @@ def make_purchase_invoice(source_name, target_doc=None):
def postprocess(source, target):
set_missing_values(source, target)
#Get the advance paid Journal Entries in Purchase Invoice Advance
target.set_advances()
if target.get("allocate_advances_automatically"):
target.set_advances()
def update_item(obj, target, source_parent):
target.amount = flt(obj.amount) - flt(obj.billed_amt)

View File

@@ -116,6 +116,12 @@ class AccountsController(TransactionBase):
self.validate_non_invoice_documents_schedule()
def before_print(self):
if self.doctype in ['Journal Entry', 'Payment Entry', 'Sales Invoice', 'Purchase Invoice']:
self.gl_entries = frappe.get_list("GL Entry", filters={
"voucher_type": self.doctype,
"voucher_no": self.name
}, fields=["account", "party_type", "party", "debit", "credit", "remarks"])
if self.doctype in ['Purchase Order', 'Sales Order', 'Sales Invoice', 'Purchase Invoice',
'Supplier Quotation', 'Purchase Receipt', 'Delivery Note', 'Quotation']:
if self.get("group_same_items"):

View File

@@ -2,8 +2,9 @@
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
import frappe, erpnext
from frappe import _
from frappe.model.meta import get_field_precision
from frappe.utils import flt, get_datetime, format_datetime
class StockOverReturnError(frappe.ValidationError): pass
@@ -116,6 +117,10 @@ def validate_quantity(doc, args, ref, valid_items, already_returned_items):
already_returned_data = already_returned_items.get(args.item_code) or {}
company_currency = erpnext.get_company_currency(doc.company)
stock_qty_precision = get_field_precision(frappe.get_meta(doc.doctype + " Item")
.get_field("stock_qty"), company_currency)
for column in fields:
returned_qty = flt(already_returned_data.get(column, 0)) if len(already_returned_data) > 0 else 0
@@ -126,7 +131,7 @@ def validate_quantity(doc, args, ref, valid_items, already_returned_items):
reference_qty = ref.get(column) * ref.get("conversion_factor", 1.0)
current_stock_qty = args.get(column) * args.get("conversion_factor", 1.0)
max_returnable_qty = flt(reference_qty) - returned_qty
max_returnable_qty = flt(reference_qty, stock_qty_precision) - returned_qty
label = column.replace('_', ' ').title()
if reference_qty:
@@ -135,7 +140,7 @@ def validate_quantity(doc, args, ref, valid_items, already_returned_items):
elif returned_qty >= reference_qty and args.get(column):
frappe.throw(_("Item {0} has already been returned")
.format(args.item_code), StockOverReturnError)
elif abs(current_stock_qty) > max_returnable_qty:
elif abs(flt(current_stock_qty, stock_qty_precision)) > max_returnable_qty:
frappe.throw(_("Row # {0}: Cannot return more than {1} for Item {2}")
.format(args.idx, max_returnable_qty, args.item_code), StockOverReturnError)
@@ -239,6 +244,10 @@ def make_return_doc(doctype, source_name, target_doc=None):
doc.paid_amount = -1 * source.paid_amount
doc.base_paid_amount = -1 * source.base_paid_amount
if doc.get("is_return") and hasattr(doc, "packed_items"):
for d in doc.get("packed_items"):
d.qty = d.qty * -1
doc.discount_amount = -1 * source.discount_amount
doc.run_method("calculate_taxes_and_totals")

View File

@@ -42,10 +42,10 @@ def create_variant_with_tables(item, args):
return variant
def make_item_variant():
frappe.delete_doc_if_exists("Item", "_Test Variant Item-S", force=1)
variant = create_variant_with_tables("_Test Variant Item", '{"Test Size": "Small"}')
variant.item_code = "_Test Variant Item-S"
variant.item_name = "_Test Variant Item-S"
frappe.delete_doc_if_exists("Item", "_Test Variant Item-XSL", force=1)
variant = create_variant_with_tables("_Test Variant Item", '{"Test Size": "Extra Small"}')
variant.item_code = "_Test Variant Item-XSL"
variant.item_name = "_Test Variant Item-XSL"
variant.save()
return variant

View File

@@ -162,6 +162,8 @@ def create_item_code(amazon_item_json, sku):
igroup.parent_item_group = mws_settings.item_group
igroup.insert()
item.append("item_defaults", {'company':mws_settings.company})
item.insert(ignore_permissions=True)
create_item_price(amazon_item_json, item.item_code)
@@ -213,7 +215,7 @@ def get_orders(after_date):
fulfillment_channels=["MFN", "AFN"],
lastupdatedafter=after_date,
orderstatus=statuses,
max_results='20')
max_results='50')
while True:
orders_list = []
@@ -432,8 +434,8 @@ def get_order_items(market_place_order_id):
return final_order_items
def get_item_code(order_item):
asin = order_item.ASIN
item_code = frappe.db.get_value("Item", {"amazon_item_code": asin}, "item_code")
sku = order_item.SellerSKU
item_code = frappe.db.get_value("Item", {"item_code": sku}, "item_code")
if item_code:
return item_code
@@ -451,11 +453,16 @@ def get_charges_and_fees(market_place_order_id):
shipment_item_list = return_as_list(shipment_event.ShipmentEvent.ShipmentItemList.ShipmentItem)
for shipment_item in shipment_item_list:
charges = return_as_list(shipment_item.ItemChargeList.ChargeComponent)
fees = return_as_list(shipment_item.ItemFeeList.FeeComponent)
charges, fees = []
if 'ItemChargeList' in shipment_item.keys():
charges = return_as_list(shipment_item.ItemChargeList.ChargeComponent)
if 'ItemFeeList' in shipment_item.keys():
fees = return_as_list(shipment_item.ItemFeeList.FeeComponent)
for charge in charges:
if(charge.ChargeType != "Principal"):
if(charge.ChargeType != "Principal") and float(charge.ChargeAmount.CurrencyAmount) != 0:
charge_account = get_account(charge.ChargeType)
charges_fees.get("charges").append({
"charge_type":"Actual",
@@ -465,13 +472,14 @@ def get_charges_and_fees(market_place_order_id):
})
for fee in fees:
fee_account = get_account(fee.FeeType)
charges_fees.get("fees").append({
"charge_type":"Actual",
"account_head": fee_account,
"tax_amount": fee.FeeAmount.CurrencyAmount,
"description": fee.FeeType + " for " + shipment_item.SellerSKU
})
if float(fee.FeeAmount.CurrencyAmount) != 0:
fee_account = get_account(fee.FeeType)
charges_fees.get("fees").append({
"charge_type":"Actual",
"account_head": fee_account,
"tax_amount": fee.FeeAmount.CurrencyAmount,
"description": fee.FeeType + " for " + shipment_item.SellerSKU
})
return charges_fees

View File

@@ -65,7 +65,7 @@ class WoocommerceSettings(Document):
if not frappe.get_value("Item Group",{"name": "WooCommerce Products"}):
item_group = frappe.new_doc("Item Group")
item_group.item_group_name = "WooCommerce Products"
item_group.parent_item_group = "All Item Groups"
item_group.parent_item_group = _("All Item Groups")
item_group.save()

View File

@@ -49,7 +49,7 @@ frappe.ui.form.on('Healthcare Service Unit Type', {
var disable = function(frm){
var doc = frm.doc;
frappe.call({
method: "erpnext.healthcare.doctype.healthcare_service_unit_type.healthcare_service_unit_type.disable_enable",
method: "erpnext.healthcare.doctype.healthcare_service_unit_type.healthcare_service_unit_type.disable_enable",
args: {status: 1, doc_name: doc.name, item: doc.item, is_billable: doc.is_billable},
callback: function(){
cur_frm.reload_doc();
@@ -60,7 +60,7 @@ var disable = function(frm){
var enable = function(frm){
var doc = frm.doc;
frappe.call({
method: "erpnext.healthcare.doctype.healthcare_service_unit_type.healthcare_service_unit_type.disable_enable",
method: "erpnext.healthcare.doctype.healthcare_service_unit_type.healthcare_service_unit_type.disable_enable",
args: {status: 0, doc_name: doc.name, item: doc.item, is_billable: doc.is_billable},
callback: function(){
cur_frm.reload_doc();

View File

@@ -111,7 +111,7 @@ def change_item_code(item, item_code, doc_name):
frappe.db.set_value("Healthcare Service Unit Type", doc_name, "item_code", item_code)
@frappe.whitelist()
def disable_enable(status, doc_name, item, is_billable):
def disable_enable(status, doc_name, item=None, is_billable=None):
frappe.db.set_value("Healthcare Service Unit Type", doc_name, "disabled", status)
if(is_billable == 1):
frappe.db.set_value("Item", item, "disabled", status)

View File

@@ -399,6 +399,19 @@ def get_leave_balance_on(employee, leave_type, date, allocation_records=None, do
return flt(allocation.total_leaves_allocated) - (flt(leaves_taken) + flt(leaves_encashed))
def get_total_allocated_leaves(employee, leave_type, date):
filters= {
'from_date': ['<=', date],
'to_date': ['>=', date],
'docstatus': 1,
'leave_type': leave_type,
'employee': employee
}
leave_allocation_records = frappe.db.get_all('Leave Allocation', filters=filters, fields=['total_leaves_allocated'])
return flt(leave_allocation_records[0]['total_leaves_allocated']) if leave_allocation_records else flt(0)
def get_leaves_for_period(employee, leave_type, from_date, to_date, status, docname=None):
leave_applications = frappe.db.sql("""
select name, employee, leave_type, from_date, to_date, total_leave_days
@@ -499,7 +512,7 @@ def add_department_leaves(events, start, end, employee, company):
department_employees = frappe.db.sql_list("""select name from tabEmployee where department=%s
and company=%s""", (department, company))
filter_conditions = "employee in (\"%s\")" % '", "'.join(department_employees)
filter_conditions = " and employee in (\"%s\")" % '", "'.join(department_employees)
add_leaves(events, start, end, filter_conditions=filter_conditions)
def add_leaves(events, start, end, filter_conditions=None):

View File

@@ -441,7 +441,7 @@ class SalarySlip(TransactionBase):
def calculate_net_pay(self):
if self.salary_structure:
self.calculate_component_amounts()
disable_rounded_total = cint(frappe.db.get_value("Global Defaults", None, "disable_rounded_total"))
precision = frappe.defaults.get_global_default("currency_precision")
self.total_deduction = 0
@@ -452,10 +452,10 @@ class SalarySlip(TransactionBase):
self.set_loan_repayment()
self.net_pay = flt(self.gross_pay) - (flt(self.total_deduction) + flt(self.total_loan_repayment))
self.net_pay = (flt(self.gross_pay) - (flt(self.total_deduction) + flt(self.total_loan_repayment))) * flt(self.payment_days / self.total_working_days)
self.rounded_total = rounded(self.net_pay,
self.precision("net_pay") if disable_rounded_total else 0)
if self.net_pay < 0:
frappe.throw(_("Net Pay cannnot be negative"))

View File

@@ -15,9 +15,11 @@ class TrainingFeedback(Document):
def on_submit(self):
training_event = frappe.get_doc("Training Event", self.training_event)
status = None
for e in training_event.employees:
if e.employee == self.employee:
training_event.status = 'Feedback Submitted'
status = 'Feedback Submitted'
break
training_event.save()
if status:
frappe.db.set_value("Training Event", self.training_event, "status", status)

View File

@@ -5,21 +5,21 @@ from __future__ import unicode_literals
import frappe
from frappe import _
from erpnext.hr.doctype.leave_application.leave_application \
import get_leave_allocation_records, get_leave_balance_on, get_approved_leaves_for_period
import get_leave_allocation_records, get_leave_balance_on, get_approved_leaves_for_period, get_total_allocated_leaves
def execute(filters=None):
leave_types = frappe.db.sql_list("select name from `tabLeave Type` order by name asc")
columns = get_columns(leave_types)
data = get_data(filters, leave_types)
return columns, data
def get_columns(leave_types):
columns = [
_("Employee") + ":Link/Employee:150",
_("Employee Name") + "::200",
_("Employee") + ":Link/Employee:150",
_("Employee Name") + "::200",
_("Department") +"::150"
]
@@ -27,18 +27,18 @@ def get_columns(leave_types):
columns.append(_(leave_type) + " " + _("Opening") + ":Float:160")
columns.append(_(leave_type) + " " + _("Taken") + ":Float:160")
columns.append(_(leave_type) + " " + _("Balance") + ":Float:160")
return columns
def get_data(filters, leave_types):
user = frappe.session.user
allocation_records_based_on_to_date = get_leave_allocation_records(filters.to_date)
allocation_records_based_on_from_date = get_leave_allocation_records(filters.from_date)
active_employees = frappe.get_all("Employee",
filters = { "status": "Active", "company": filters.company},
active_employees = frappe.get_all("Employee",
filters = { "status": "Active", "company": filters.company},
fields = ["name", "employee_name", "department", "user_id"])
data = []
for employee in active_employees:
leave_approvers = get_approvers(employee.department)
@@ -51,8 +51,7 @@ def get_data(filters, leave_types):
filters.from_date, filters.to_date)
# opening balance
opening = get_leave_balance_on(employee.name, leave_type, filters.from_date,
allocation_records_based_on_from_date.get(employee.name, frappe._dict()))
opening = get_total_allocated_leaves(employee.name, leave_type, filters.to_date)
# closing balance
closing = get_leave_balance_on(employee.name, leave_type, filters.to_date,
@@ -61,7 +60,7 @@ def get_data(filters, leave_types):
row += [opening, leaves_taken, closing]
data.append(row)
return data
def get_approvers(department):

View File

@@ -580,7 +580,7 @@ def get_list_context(context):
context.title = _("Bill of Materials")
# context.introduction = _('Boms')
def get_bom_items_as_dict(bom, company, qty=1, fetch_exploded=1, fetch_scrap_items=0, include_non_stock_items=False):
def get_bom_items_as_dict(bom, company, qty=1, fetch_exploded=1, fetch_scrap_items=0, include_non_stock_items=False, fetch_qty_in_stock_uom=True):
item_dict = {}
# Did not use qty_consumed_per_unit in the query, as it leads to rounding loss
@@ -588,7 +588,7 @@ def get_bom_items_as_dict(bom, company, qty=1, fetch_exploded=1, fetch_scrap_ite
bom_item.item_code,
bom_item.idx,
item.item_name,
sum(bom_item.stock_qty/ifnull(bom.quantity, 1)) * %(qty)s as qty,
sum(bom_item.{qty_field}/ifnull(bom.quantity, 1)) * %(qty)s as qty,
item.description,
item.image,
item.stock_uom,
@@ -616,16 +616,18 @@ def get_bom_items_as_dict(bom, company, qty=1, fetch_exploded=1, fetch_scrap_ite
query = query.format(table="BOM Explosion Item",
where_conditions="",
is_stock_item=is_stock_item,
qty_field="stock_qty",
select_columns = """, bom_item.source_warehouse, bom_item.operation, bom_item.include_item_in_manufacturing,
(Select idx from `tabBOM Item` where item_code = bom_item.item_code and parent = %(parent)s ) as idx""")
(Select idx from `tabBOM Item` where item_code = bom_item.item_code and parent = %(parent)s limit 1) as idx""")
items = frappe.db.sql(query, { "parent": bom, "qty": qty, "bom": bom, "company": company }, as_dict=True)
elif fetch_scrap_items:
query = query.format(table="BOM Scrap Item", where_conditions="", select_columns=", bom_item.idx", is_stock_item=is_stock_item)
query = query.format(table="BOM Scrap Item", where_conditions="", select_columns=", bom_item.idx", is_stock_item=is_stock_item, qty_field="stock_qty")
items = frappe.db.sql(query, { "qty": qty, "bom": bom, "company": company }, as_dict=True)
else:
query = query.format(table="BOM Item", where_conditions="", is_stock_item=is_stock_item,
select_columns = ", bom_item.source_warehouse, bom_item.idx, bom_item.operation, bom_item.include_item_in_manufacturing")
qty_field="stock_qty" if fetch_qty_in_stock_uom else "qty",
select_columns = ", bom_item.uom, bom_item.conversion_factor, bom_item.source_warehouse, bom_item.idx, bom_item.operation, bom_item.include_item_in_manufacturing")
items = frappe.db.sql(query, { "qty": qty, "bom": bom, "company": company }, as_dict=True)
for item in items:

View File

@@ -571,7 +571,7 @@ execute:frappe.delete_doc_if_exists("Page", "sales-analytics")
execute:frappe.delete_doc_if_exists("Page", "purchase-analytics")
execute:frappe.delete_doc_if_exists("Page", "stock-analytics")
execute:frappe.delete_doc_if_exists("Page", "production-analytics")
erpnext.patches.v11_0.ewaybill_fields_gst_india #2018-11-13 #2019-01-09
erpnext.patches.v11_0.ewaybill_fields_gst_india #2018-11-13 #2019-01-09 #2019-04-01
erpnext.patches.v11_0.drop_column_max_days_allowed
erpnext.patches.v11_0.change_healthcare_desktop_icons
erpnext.patches.v10_0.update_user_image_in_employee
@@ -587,5 +587,6 @@ erpnext.patches.v11_1.setup_guardian_role
execute:frappe.delete_doc('DocType', 'Notification Control')
erpnext.patches.v11_0.remove_barcodes_field_from_copy_fields_to_variants
erpnext.patches.v10_0.item_barcode_childtable_migrate # 16-02-2019
erpnext.patches.v11_0.make_italian_localization_fields # 01-03-2019
erpnext.patches.v11_1.make_job_card_time_logs
erpnext.patches.v11_0.make_italian_localization_fields # 26-03-2019
erpnext.patches.v11_1.make_job_card_time_logs
erpnext.patches.v11_1.set_variant_based_on

View File

@@ -6,7 +6,6 @@ from erpnext.regional.italy.setup import make_custom_fields, setup_report
from erpnext.regional.italy import state_codes
import frappe
def execute():
company = frappe.get_all('Company', filters = {'country': 'Italy'})
if not company:
@@ -27,4 +26,12 @@ def execute():
frappe.db.sql("""
UPDATE tabAddress set {condition} country_code = UPPER(ifnull((select code
from `tabCountry` where name = `tabAddress`.country), ''))
where country_code is null and state_code is null
""".format(condition=condition))
frappe.db.sql("""
UPDATE `tabSales Invoice Item` si, `tabSales Order` so
set si.customer_po_no = so.po_no, si.customer_po_date = so.po_date
WHERE
si.sales_order = so.name and so.po_no is not null
""")

View File

@@ -0,0 +1,11 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
def execute():
frappe.db.sql("""update tabItem set variant_based_on = 'Item Attribute'
where ifnull(variant_based_on, '') = ''
and (has_variants=1 or ifnull(variant_of, '') != '')
""")

View File

@@ -159,6 +159,13 @@ class Task(NestedSet):
self.update_nsm_model()
def update_status(self):
if self.status not in ('Cancelled', 'Closed') and self.exp_end_date:
from datetime import datetime
if self.exp_end_date < datetime.now().date():
self.db_set('status', 'Overdue')
self.update_project()
@frappe.whitelist()
def check_if_child_exists(name):
child_tasks = frappe.get_all("Task", filters={"parent_task": name})
@@ -186,10 +193,9 @@ def set_multiple_status(names, status):
task.save()
def set_tasks_as_overdue():
frappe.db.sql("""update tabTask set `status`='Overdue'
where exp_end_date is not null
and exp_end_date < CURDATE()
and `status` not in ('Closed', 'Cancelled')""")
tasks = frappe.get_all("Task", filters={'status':['not in',['Cancelled', 'Closed']]})
for task in tasks:
frappe.get_doc("Task", task.name).update_status()
@frappe.whitelist()
def get_children(doctype, parent, task=None, project=None, is_root=False):

View File

@@ -117,4 +117,4 @@ def create_task(subject, start=None, end=None, depends_on=None, project=None, sa
if save:
task.save()
return task
return task

View File

@@ -10,8 +10,8 @@ frappe.ui.form.on("Timesheet", {
filters:{
'status': 'Active'
}
}
}
};
};
frm.fields_dict['time_logs'].grid.get_field('task').get_query = function(frm, cdt, cdn) {
var child = locals[cdt][cdn];
@@ -20,33 +20,37 @@ frappe.ui.form.on("Timesheet", {
'project': child.project,
'status': ["!=", "Cancelled"]
}
}
}
};
};
frm.fields_dict['time_logs'].grid.get_field('project').get_query = function() {
return{
filters: {
'company': frm.doc.company
}
}
}
};
};
},
onload: function(frm){
if (frm.doc.__islocal && frm.doc.time_logs) {
calculate_time_and_amount(frm);
}
if (frm.is_new()) {
set_employee_and_company(frm);
}
},
refresh: function(frm) {
if(frm.doc.docstatus==1) {
if(frm.doc.per_billed < 100 && frm.doc.total_billable_hours && frm.doc.total_billable_hours > frm.doc.total_billed_hours){
frm.add_custom_button(__("Make Sales Invoice"), function() { frm.trigger("make_invoice") },
frm.add_custom_button(__("Make Sales Invoice"), function() { frm.trigger("make_invoice"); },
"fa fa-file-alt");
}
if(!frm.doc.salary_slip && frm.doc.employee){
frm.add_custom_button(__("Make Salary Slip"), function() { frm.trigger("make_salary_slip") },
frm.add_custom_button(__("Make Salary Slip"), function() { frm.trigger("make_salary_slip"); },
"fa fa-file-alt");
}
}
@@ -58,7 +62,7 @@ frappe.ui.form.on("Timesheet", {
if ((row.from_time <= frappe.datetime.now_datetime()) && !row.completed) {
button = 'Resume Timer';
}
})
});
frm.add_custom_button(__(button), function() {
var flag = true;
@@ -77,7 +81,7 @@ frappe.ui.form.on("Timesheet", {
erpnext.timesheet.timer(frm, row, timestamp);
flag = false;
}
})
});
// If no activities found to start a timer, create new
if (flag) {
erpnext.timesheet.timer(frm);
@@ -94,7 +98,7 @@ frappe.ui.form.on("Timesheet", {
frappe.db.get_value('Company', { 'company_name' : frm.doc.company }, 'standard_working_hours')
.then(({ message }) => {
(frappe.working_hours = message.standard_working_hours || 0);
});
});
},
make_invoice: function(frm) {
@@ -125,8 +129,8 @@ frappe.ui.form.on("Timesheet", {
frappe.set_route("Form", r.message.doctype, r.message.name);
}
}
})
})
});
});
dialog.show();
},
@@ -136,7 +140,7 @@ frappe.ui.form.on("Timesheet", {
frm: frm
});
},
})
});
frappe.ui.form.on("Timesheet Detail", {
time_logs_remove: function(frm) {
@@ -171,22 +175,22 @@ frappe.ui.form.on("Timesheet Detail", {
.find('[data-fieldname="timer"]')
.append(frappe.render_template("timesheet"));
frm.trigger("control_timer");
})
});
},
hours: function(frm, cdt, cdn) {
calculate_end_time(frm, cdt, cdn)
calculate_end_time(frm, cdt, cdn);
},
billing_hours: function(frm, cdt, cdn) {
calculate_billing_costing_amount(frm, cdt, cdn)
calculate_billing_costing_amount(frm, cdt, cdn);
},
billing_rate: function(frm, cdt, cdn) {
calculate_billing_costing_amount(frm, cdt, cdn)
calculate_billing_costing_amount(frm, cdt, cdn);
},
costing_rate: function(frm, cdt, cdn) {
calculate_billing_costing_amount(frm, cdt, cdn)
calculate_billing_costing_amount(frm, cdt, cdn);
},
billable: function(frm, cdt, cdn) {
@@ -212,7 +216,7 @@ frappe.ui.form.on("Timesheet Detail", {
calculate_billing_costing_amount(frm, cdt, cdn);
}
}
})
});
}
});
@@ -240,23 +244,23 @@ var calculate_end_time = function(frm, cdt, cdn) {
frm._setting_hours = true;
frappe.model.set_value(cdt, cdn, "to_time",
d.format(frappe.defaultDatetimeFormat)).then(() => {
frm._setting_hours = false;
});
frm._setting_hours = false;
});
}
}
}
};
var update_billing_hours = function(frm, cdt, cdn){
var child = locals[cdt][cdn];
if(!child.billable) frappe.model.set_value(cdt, cdn, 'billing_hours', 0.0);
}
};
var update_time_rates = function(frm, cdt, cdn){
var child = locals[cdt][cdn];
if(!child.billable){
frappe.model.set_value(cdt, cdn, 'billing_rate', 0.0);
}
}
};
var calculate_billing_costing_amount = function(frm, cdt, cdn){
var child = locals[cdt][cdn];
@@ -270,7 +274,7 @@ var calculate_billing_costing_amount = function(frm, cdt, cdn){
frappe.model.set_value(cdt, cdn, 'billing_amount', billing_amount);
frappe.model.set_value(cdt, cdn, 'costing_amount', costing_amount);
calculate_time_and_amount(frm);
}
};
var calculate_time_and_amount = function(frm) {
var tl = frm.doc.time_logs || [];
@@ -294,4 +298,17 @@ var calculate_time_and_amount = function(frm) {
frm.set_value("total_hours", total_working_hr);
frm.set_value("total_billable_amount", total_billable_amount);
frm.set_value("total_costing_amount", total_costing_amount);
}
};
// set employee (and company) to the one that's currently logged in
const set_employee_and_company = function(frm) {
const options = { user_id: frappe.session.user };
const fields = ['name', 'company'];
frappe.db.get_value('Employee', options, fields).then(({ message }) => {
if (message) {
// there is an employee with the currently logged in user_id
frm.set_value("employee", message.name);
frm.set_value("company", message.company);
}
});
};

View File

@@ -0,0 +1,147 @@
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.utils import time_diff_in_hours
def get_columns():
return [
{
"label": _("Employee ID"),
"fieldtype": "Link",
"fieldname": "employee",
"options": "Employee",
"width": 300
},
{
"label": _("Employee Name"),
"fieldtype": "data",
"fieldname": "employee_name",
"hidden": 1,
"width": 200
},
{
"label": _("Timesheet"),
"fieldtype": "Link",
"fieldname": "timesheet",
"options": "Timesheet",
"width": 150
},
{
"label": _("Billable Hours"),
"fieldtype": "Float",
"fieldname": "total_billable_hours",
"width": 50
},
{
"label": _("Working Hours"),
"fieldtype": "Float",
"fieldname": "total_hours",
"width": 50
},
{
"label": _("Amount"),
"fieldtype": "Currency",
"fieldname": "amount",
"width": 100
}
]
def get_data(filters):
data = []
record = get_records(filters)
billable_hours_worked = 0
hours_worked = 0
working_cost = 0
for entries in record:
total_hours = 0
total_billable_hours = 0
total_amount = 0
entries_exists = False
timesheet_details = get_timesheet_details(filters, entries.name)
for activity in timesheet_details:
entries_exists = True
time_start = activity.from_time
time_end = frappe.utils.add_to_date(activity.from_time, hours=activity.hours)
from_date = frappe.utils.get_datetime(filters.from_date)
to_date = frappe.utils.get_datetime(filters.to_date)
if time_start <= from_date and time_end >= from_date:
total_hours, total_billable_hours, total_amount = get_billable_and_total_hours(activity,
time_end, from_date, total_hours, total_billable_hours, total_amount)
billable_hours_worked += total_billable_hours
hours_worked += total_hours
working_cost += total_amount
elif time_start >= from_date and time_end >= to_date:
total_hours, total_billable_hours, total_amount = get_billable_and_total_hours(activity,
to_date, time_start, total_hours, total_billable_hours, total_amount)
billable_hours_worked += total_billable_hours
hours_worked += total_hours
working_cost += total_amount
elif time_start >= from_date and time_end <= to_date:
total_hours, total_billable_hours, total_amount = get_billable_and_total_hours(activity,
time_end, time_start, total_hours, total_billable_hours, total_amount)
billable_hours_worked += total_billable_hours
hours_worked += total_hours
working_cost += total_amount
row = {
"employee": entries.employee,
"employee_name": entries.employee_name,
"timesheet": entries.name,
"total_billable_hours": total_billable_hours,
"total_hours": total_hours,
"amount": total_amount
}
if entries_exists:
data.append(row)
entries_exists = False
total = {
"total_billable_hours": billable_hours_worked,
"total_hours": hours_worked,
"amount": working_cost
}
if billable_hours_worked !=0 or hours_worked !=0 or working_cost !=0:
data.append(total)
return data
def get_records(filters):
record_filters = [
["start_date", "<=", filters.to_date],
["end_date", ">=", filters.from_date],
["docstatus", "=", 1]
]
if "employee" in filters:
record_filters.append(["employee", "=", filters.employee])
return frappe.get_all("Timesheet", filters=record_filters, fields=[" * "] )
def get_billable_and_total_hours(activity, end, start, total_hours, total_billable_hours, total_amount):
total_hours += abs(time_diff_in_hours(end, start))
if activity.billable:
total_billable_hours += abs(time_diff_in_hours(end, start))
total_amount += total_billable_hours * activity.billing_rate
return total_hours, total_billable_hours, total_amount
def get_timesheet_details(filters, parent):
timesheet_details_filter = {"parent": parent}
if "project" in filters:
timesheet_details_filter["project"] = filters.project
return frappe.get_all(
"Timesheet Detail",
filters = timesheet_details_filter,
fields=["*"]
)

View File

@@ -0,0 +1,29 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
/* eslint-disable */
frappe.query_reports["Employee Billing Summary"] = {
"filters": [
{
fieldname: "employee",
label: __("Employee"),
fieldtype: "Link",
options: "Employee",
reqd: 1
},
{
fieldname:"from_date",
label: __("From Date"),
fieldtype: "Date",
default: frappe.datetime.add_months(frappe.datetime.month_start(), -1),
reqd: 1
},
{
fieldname:"to_date",
label: __("To Date"),
fieldtype: "Date",
default: frappe.datetime.add_days(frappe.datetime.month_start(), -1),
reqd: 1
},
]
}

View File

@@ -0,0 +1,36 @@
{
"add_total_row": 0,
"creation": "2019-03-08 15:08:19.929728",
"disable_prepared_report": 0,
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"idx": 0,
"is_standard": "Yes",
"modified": "2019-03-08 15:08:19.929728",
"modified_by": "Administrator",
"module": "Projects",
"name": "Employee Billing Summary",
"owner": "Administrator",
"prepared_report": 0,
"ref_doctype": "Timesheet",
"report_name": "Employee Billing Summary",
"report_type": "Script Report",
"roles": [
{
"role": "Projects User"
},
{
"role": "HR User"
},
{
"role": "Manufacturing User"
},
{
"role": "Employee"
},
{
"role": "Accounts User"
}
]
}

View File

@@ -0,0 +1,14 @@
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe import _
from erpnext.projects.report.billing_summary import get_columns, get_data
def execute(filters=None):
filters = frappe._dict(filters or {})
columns = get_columns()
data = get_data(filters)
return columns, data

View File

@@ -0,0 +1,29 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
/* eslint-disable */
frappe.query_reports["Project Billing Summary"] = {
"filters": [
{
fieldname: "project",
label: __("Project"),
fieldtype: "Link",
options: "Project",
reqd: 1
},
{
fieldname:"from_date",
label: __("From Date"),
fieldtype: "Date",
default: frappe.datetime.add_months(frappe.datetime.month_start(), -1),
reqd: 1
},
{
fieldname:"to_date",
label: __("To Date"),
fieldtype: "Date",
default: frappe.datetime.add_days(frappe.datetime.month_start(),-1),
reqd: 1
},
]
}

View File

@@ -0,0 +1,36 @@
{
"add_total_row": 0,
"creation": "2019-03-11 16:22:39.460524",
"disable_prepared_report": 0,
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"idx": 0,
"is_standard": "Yes",
"modified": "2019-03-11 16:22:39.460524",
"modified_by": "Administrator",
"module": "Projects",
"name": "Project Billing Summary",
"owner": "Administrator",
"prepared_report": 0,
"ref_doctype": "Timesheet",
"report_name": "Project Billing Summary",
"report_type": "Script Report",
"roles": [
{
"role": "Projects User"
},
{
"role": "HR User"
},
{
"role": "Manufacturing User"
},
{
"role": "Employee"
},
{
"role": "Accounts User"
}
]
}

View File

@@ -0,0 +1,14 @@
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe import _
from erpnext.projects.report.billing_summary import get_columns, get_data
def execute(filters=None):
filters = frappe._dict(filters or {})
columns = get_columns()
data = get_data(filters)
return columns, data

View File

@@ -93,7 +93,7 @@ def add_print_formats():
def make_custom_fields(update=True):
hsn_sac_field = dict(fieldname='gst_hsn_code', label='HSN/SAC',
fieldtype='Data', fetch_from='item_code.gst_hsn_code', insert_after='description',
allow_on_submit=1, print_hide=1)
allow_on_submit=1, print_hide=1, fetch_if_empty=1)
invoice_gst_fields = [
dict(fieldname='gst_section', label='GST Details', fieldtype='Section Break',
insert_after='language', print_hide=1, collapsible=1),
@@ -243,6 +243,7 @@ def make_custom_fields(update=True):
'Purchase Order Item': [hsn_sac_field],
'Purchase Receipt Item': [hsn_sac_field],
'Purchase Invoice Item': [hsn_sac_field],
'Material Request Item': [hsn_sac_field],
'Employee': [
dict(fieldname='ifsc_code', label='IFSC Code',
fieldtype='Data', insert_after='bank_ac_no', print_hide=1,

View File

@@ -95,13 +95,12 @@
<Cognome>{{ doc.customer_data.last_name }}</Cognome>
</Anagrafica>
{%- else %}
{%- if doc.customer_data.is_public_administration %}
<CodiceFiscale>{{ doc.customer_data.fiscal_code }}</CodiceFiscale>
{%- else %}
<IdFiscaleIVA>
<IdPaese>{{ doc.customer_address_data.country_code }}</IdPaese>
<IdCodice>{{ doc.tax_id | replace("IT","") }}</IdCodice>
</IdFiscaleIVA>
{%- if doc.customer_data.fiscal_code %}
<CodiceFiscale>{{ doc.customer_data.fiscal_code }}</CodiceFiscale>
{%- endif %}
<Anagrafica>
<Denominazione>{{ doc.customer_name }}</Denominazione>
@@ -128,22 +127,42 @@
<ImportoBollo>{{ format_float(doc.stamp_duty) }}</ImportoBollo>
</DatiBollo>
{%- endif %}
<ImportoTotaleDocumento>{{ format_float(doc.grand_total) }}</ImportoTotaleDocumento>
{%- if doc.discount_amount %}
<ScontoMaggiorazione>
{%- if doc.discount_amount > 0.0 %}
<Tipo>SC</Tipo>
{%- else %}
<Tipo>MG</Tipo>
{%- endif %}
{%- if doc.additional_discount_percentage > 0.0 %}
<Percentuale>{{ format_float(doc.additional_discount_percentage) }}</Percentuale>
{%- endif %}
<Importo>{{ format_float(doc.discount_amount) }}</Importo>
</ScontoMaggiorazione>
{%- endif %}
<ImportoTotaleDocumento>{{ format_float(doc.rounded_total or doc.grand_total) }}</ImportoTotaleDocumento>
<Causale>VENDITA</Causale>
</DatiGeneraliDocumento>
{%- if doc.po_no %}
<DatiOrdineAcquisto>
<IdDocumento>{{ doc.po_no }}</IdDocumento>
{%- if doc.po_date %}
<Data>{{ doc.po_date }}</Data>
{%- endif %}
</DatiOrdineAcquisto>
{%- endif %}
{%- for po_no, po_date in doc.customer_po_data.items() %}
<DatiOrdineAcquisto>
<IdDocumento>{{ po_no }}</IdDocumento>
<Data>{{ po_date }}</Data>
</DatiOrdineAcquisto>
{%- endfor %}
{%- if doc.is_return and doc.return_against_unamended %}
<DatiFattureCollegate>
<IdDocumento>{{ doc.return_against_unamended }}</IdDocumento>
</DatiFattureCollegate>
{%- endif %}
{%- for row in doc.e_invoice_items %}
{%- if row.delivery_note %}
<DatiDDT>
<NumeroDDT>{{ row.delivery_note }}</NumeroDDT>
<DataDDT>{{ frappe.db.get_value('Delivery Note', row.delivery_note, 'posting_date') }}</DataDDT>
<RiferimentoNumeroLinea>{{ row.idx }}</RiferimentoNumeroLinea>
</DatiDDT>
{%- endif %}
{%- endfor %}
{%- if doc.shipping_address_data %}
<DatiTrasporto>
<IndirizzoResa>
@@ -199,7 +218,9 @@
<ModalitaPagamento>{{ payment_term.mode_of_payment_code.split("-")[0] }}</ModalitaPagamento>
<DataScadenzaPagamento>{{ payment_term.due_date }}</DataScadenzaPagamento>
<ImportoPagamento>{{ format_float(payment_term.payment_amount) }}</ImportoPagamento>
<IstitutoFinanziario>{{ payment_term.bank_account_name }}</IstitutoFinanziario>
{%- if payment_term.bank_account_name %}
<IstitutoFinanziario>{{ payment_term.bank_account_name }}</IstitutoFinanziario>
{%- endif %}
{%- if payment_term.bank_account_iban %}
<IBAN>{{ payment_term.bank_account_iban }}</IBAN>
<ABI>{{ payment_term.bank_account_iban[5:10] }}</ABI>

View File

@@ -3,15 +3,26 @@ erpnext.setup_e_invoice_button = (doctype) => {
refresh: (frm) => {
if(frm.doc.docstatus == 1) {
frm.add_custom_button('Generate E-Invoice', () => {
var w = window.open(
frappe.urllib.get_full_url(
"/api/method/erpnext.regional.italy.utils.generate_single_invoice?"
+ "docname=" + frm.doc.name
)
)
if (!w) {
frappe.msgprint(__("Please enable pop-ups")); return;
}
frm.call({
method: "erpnext.regional.italy.utils.generate_single_invoice",
args: {
docname: frm.doc.name
},
callback: function(r) {
frm.reload_doc();
if(r.message) {
var w = window.open(
frappe.urllib.get_full_url(
"/api/method/erpnext.regional.italy.utils.download_e_invoice_file?"
+ "file_name=" + r.message
)
)
if (!w) {
frappe.msgprint(__("Please enable pop-ups")); return;
}
}
}
});
});
}
}

View File

@@ -26,6 +26,22 @@ def make_custom_fields(update=True):
print_hide=1, hidden=1, read_only=1, options="currency")
]
customer_po_fields = [
dict(fieldname='customer_po_details', label='Customer PO',
fieldtype='Section Break', insert_after='image'),
dict(fieldname='customer_po_no', label='Customer PO No',
fieldtype='Data', insert_after='customer_po_details',
fetch_from = 'sales_order.po_no',
print_hide=1, allow_on_submit=1, fetch_if_empty= 1, read_only=1, no_copy=1),
dict(fieldname='customer_po_clm_brk', label='',
fieldtype='Column Break', insert_after='customer_po_no',
print_hide=1, read_only=1),
dict(fieldname='customer_po_date', label='Customer PO Date',
fieldtype='Date', insert_after='customer_po_clm_brk',
fetch_from = 'sales_order.po_date',
print_hide=1, allow_on_submit=1, fetch_if_empty= 1, read_only=1, no_copy=1)
]
custom_fields = {
'Company': [
dict(fieldname='sb_e_invoicing', label='E-Invoicing',
@@ -128,7 +144,7 @@ def make_custom_fields(update=True):
'Purchase Invoice Item': invoice_item_fields,
'Sales Order Item': invoice_item_fields,
'Delivery Note Item': invoice_item_fields,
'Sales Invoice Item': invoice_item_fields,
'Sales Invoice Item': invoice_item_fields + customer_po_fields,
'Quotation Item': invoice_item_fields,
'Purchase Order Item': invoice_item_fields,
'Purchase Receipt Item': invoice_item_fields,

View File

@@ -4,6 +4,7 @@ import frappe, json, os
from frappe.utils import flt, cstr
from erpnext.controllers.taxes_and_totals import get_itemised_tax
from frappe import _
from six import string_types
from frappe.utils.file_manager import save_file, remove_file
from frappe.desk.form.load import get_attachments
from erpnext.regional.italy import state_codes
@@ -82,6 +83,14 @@ def prepare_invoice(invoice, progressive_number):
if item.tax_rate == 0.0 and item.tax_amount == 0.0:
item.tax_exemption_reason = tax_data["0.0"]["tax_exemption_reason"]
customer_po_data = {}
for d in invoice.e_invoice_items:
if (d.customer_po_no and d.customer_po_date
and d.customer_po_no not in customer_po_data):
customer_po_data[d.customer_po_no] = d.customer_po_date
invoice.customer_po_data = customer_po_data
return invoice
def get_conditions(filters):
@@ -134,6 +143,7 @@ def get_invoice_summary(items, taxes):
idx=len(items)+1,
item_code=reference_row.description,
item_name=reference_row.description,
description=reference_row.description,
rate=reference_row.tax_amount,
qty=1.0,
amount=reference_row.tax_amount,
@@ -142,7 +152,7 @@ def get_invoice_summary(items, taxes):
tax_amount=(reference_row.tax_amount * tax.rate) / 100,
net_amount=reference_row.tax_amount,
taxable_amount=reference_row.tax_amount,
item_tax_rate="{}",
item_tax_rate={tax.account_head: tax.rate},
charges=True
)
)
@@ -150,10 +160,16 @@ def get_invoice_summary(items, taxes):
#Check item tax rates if tax rate is zero.
if tax.rate == 0:
for item in items:
item_tax_rate = json.loads(item.item_tax_rate)
if tax.account_head in item_tax_rate:
item_tax_rate = item.item_tax_rate
if isinstance(item.item_tax_rate, string_types):
item_tax_rate = json.loads(item.item_tax_rate)
if item_tax_rate and tax.account_head in item_tax_rate:
key = cstr(item_tax_rate[tax.account_head])
summary_data.setdefault(key, {"tax_amount": 0.0, "taxable_amount": 0.0, "tax_exemption_reason": "", "tax_exemption_law": ""})
if key not in summary_data:
summary_data.setdefault(key, {"tax_amount": 0.0, "taxable_amount": 0.0,
"tax_exemption_reason": "", "tax_exemption_law": ""})
summary_data[key]["tax_amount"] += item.tax_amount
summary_data[key]["taxable_amount"] += item.net_amount
if key == "0.0":
@@ -198,19 +214,25 @@ def sales_invoice_validate(doc):
else:
doc.company_fiscal_regime = company_fiscal_regime
doc.company_tax_id = frappe.get_cached_value("Company", doc.company, 'tax_id')
doc.company_fiscal_code = frappe.get_cached_value("Company", doc.company, 'fiscal_code')
if not doc.company_tax_id and not doc.company_fiscal_code:
frappe.throw(_("Please set either the Tax ID or Fiscal Code on Company '%s'" % doc.company), title=_("E-Invoicing Information Missing"))
#Validate customer details
customer_type, is_public_administration = frappe.db.get_value("Customer", doc.customer, ["customer_type", "is_public_administration"])
if customer_type == _("Individual"):
customer = frappe.get_doc("Customer", doc.customer)
if customer.customer_type == _("Individual"):
doc.customer_fiscal_code = customer.fiscal_code
if not doc.customer_fiscal_code:
frappe.throw(_("Please set Fiscal Code for the customer '%s'" % doc.customer), title=_("E-Invoicing Information Missing"))
else:
if is_public_administration:
if customer.is_public_administration:
doc.customer_fiscal_code = customer.fiscal_code
if not doc.customer_fiscal_code:
frappe.throw(_("Please set Fiscal Code for the public administration '%s'" % doc.customer), title=_("E-Invoicing Information Missing"))
else:
doc.tax_id = customer.tax_id
if not doc.tax_id:
frappe.throw(_("Please set Tax ID for the customer '%s'" % doc.customer), title=_("E-Invoicing Information Missing"))
@@ -266,13 +288,18 @@ def prepare_and_attach_invoice(doc, replace=False):
def generate_single_invoice(docname):
doc = frappe.get_doc("Sales Invoice", docname)
e_invoice = prepare_and_attach_invoice(doc, True)
return e_invoice.file_name
@frappe.whitelist()
def download_e_invoice_file(file_name):
content = None
with open(frappe.get_site_path('private', 'files', e_invoice.file_name), "r") as f:
with open(frappe.get_site_path('private', 'files', file_name), "r") as f:
content = f.read()
frappe.local.response.filename = e_invoice.file_name
frappe.local.response.filename = file_name
frappe.local.response.filecontent = content
frappe.local.response.type = "download"

View File

@@ -612,7 +612,8 @@ def make_sales_invoice(source_name, target_doc=None, ignore_permissions=False):
def postprocess(source, target):
set_missing_values(source, target)
#Get the advance paid Journal Entries in Sales Invoice Advance
target.set_advances()
if target.get("allocate_advances_automatically"):
target.set_advances()
def set_missing_values(source, target):
target.is_pos = 0

View File

@@ -446,16 +446,15 @@ erpnext.pos.PointOfSale = class PointOfSale {
}
setup_company() {
this.company = frappe.sys_defaults.company;
return new Promise(resolve => {
if(!this.company) {
if(!frappe.sys_defaults.company) {
frappe.prompt({fieldname:"company", options: "Company", fieldtype:"Link",
label: __("Select Company"), reqd: 1}, (data) => {
this.company = data.company;
resolve(this.company);
}, __("Select Company"));
} else {
resolve(this.company);
resolve();
}
})
}
@@ -509,7 +508,9 @@ erpnext.pos.PointOfSale = class PointOfSale {
}
set_pos_profile_data() {
this.frm.doc.company = this.company;
if (this.company) {
this.frm.doc.company = this.company;
}
return new Promise(resolve => {
return this.frm.call({

View File

@@ -39,7 +39,7 @@ def get_items(start, page_length, price_list, item_group, search_value="", pos_p
if display_items_in_stock == 0:
res = frappe.db.sql("""select i.name as item_code, i.item_name, i.image as item_image,
res = frappe.db.sql("""select i.name as item_code, i.item_name, i.image as item_image, i.idx as idx,
i.is_stock_item, item_det.price_list_rate, item_det.currency
from `tabItem` i LEFT JOIN
(select item_code, price_list_rate, currency from
@@ -49,7 +49,7 @@ def get_items(start, page_length, price_list, item_group, search_value="", pos_p
where
i.disabled = 0 and i.has_variants = 0 and i.is_sales_item = 1
and i.item_group in (select name from `tabItem Group` where lft >= {lft} and rgt <= {rgt})
and {condition} limit {start}, {page_length}""".format(start=start,page_length=page_length,lft=lft, rgt=rgt, condition=condition),
and {condition} order by idx desc limit {start}, {page_length}""".format(start=start,page_length=page_length,lft=lft, rgt=rgt, condition=condition),
{
'item_code': item_code,
'price_list': price_list
@@ -60,7 +60,7 @@ def get_items(start, page_length, price_list, item_group, search_value="", pos_p
}
elif display_items_in_stock == 1:
query = """select i.name as item_code, i.item_name, i.image as item_image,
query = """select i.name as item_code, i.item_name, i.image as item_image, i.idx as idx,
i.is_stock_item, item_det.price_list_rate, item_det.currency
from `tabItem` i LEFT JOIN
(select item_code, price_list_rate, currency from
@@ -79,7 +79,7 @@ def get_items(start, page_length, price_list, item_group, search_value="", pos_p
where
i.disabled = 0 and i.has_variants = 0 and i.is_sales_item = 1
and i.item_group in (select name from `tabItem Group` where lft >= {lft} and rgt <= {rgt})
and {condition} limit {start}, {page_length}""".format
and {condition} order by idx desc limit {start}, {page_length}""".format
(start=start,page_length=page_length,lft=lft, rgt=rgt, condition=condition),
{
'item_code': item_code,

View File

@@ -1,6 +1,6 @@
[
{
"brand": "_Test Brand",
"brand": "_Test Brand",
"doctype": "Brand"
}
]

View File

@@ -22,6 +22,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "details",
"fieldtype": "Section Break",
"hidden": 0,
@@ -54,6 +55,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "company_name",
"fieldtype": "Data",
"hidden": 0,
@@ -88,6 +90,7 @@
"collapsible": 0,
"columns": 0,
"description": "",
"fetch_if_empty": 0,
"fieldname": "abbr",
"fieldtype": "Data",
"hidden": 0,
@@ -122,6 +125,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "eval:!doc.__islocal && in_list(frappe.user_roles, \"System Manager\")",
"fetch_if_empty": 0,
"fieldname": "change_abbr",
"fieldtype": "Button",
"hidden": 0,
@@ -153,6 +157,7 @@
"bold": 1,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "is_group",
"fieldtype": "Check",
"hidden": 0,
@@ -185,6 +190,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "default_finance_book",
"fieldtype": "Link",
"hidden": 0,
@@ -218,6 +224,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "cb0",
"fieldtype": "Column Break",
"hidden": 0,
@@ -248,6 +255,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "domain",
"fieldtype": "Link",
"hidden": 0,
@@ -280,6 +288,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "parent_company",
"fieldtype": "Link",
"hidden": 0,
@@ -313,6 +322,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "sb_about",
"fieldtype": "Section Break",
"hidden": 0,
@@ -345,6 +355,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "company_logo",
"fieldtype": "Attach Image",
"hidden": 0,
@@ -377,6 +388,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "company_description",
"fieldtype": "Text Editor",
"hidden": 0,
@@ -409,6 +421,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "sales_settings",
"fieldtype": "Section Break",
"hidden": 0,
@@ -441,6 +454,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "sales_monthly_history",
"fieldtype": "Small Text",
"hidden": 1,
@@ -473,6 +487,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "transactions_annual_history",
"fieldtype": "Code",
"hidden": 1,
@@ -505,6 +520,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "monthly_sales_target",
"fieldtype": "Currency",
"hidden": 0,
@@ -538,6 +554,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_goals",
"fieldtype": "Column Break",
"hidden": 0,
@@ -569,6 +586,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "total_monthly_sales",
"fieldtype": "Currency",
"hidden": 0,
@@ -602,6 +620,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "charts_section",
"fieldtype": "Section Break",
"hidden": 0,
@@ -633,6 +652,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "default_currency",
"fieldtype": "Link",
"hidden": 0,
@@ -665,6 +685,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "default_letter_head",
"fieldtype": "Link",
"hidden": 0,
@@ -698,6 +719,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "default_holiday_list",
"fieldtype": "Link",
"hidden": 0,
@@ -731,6 +753,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "standard_working_hours",
"fieldtype": "Float",
"hidden": 0,
@@ -763,6 +786,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "default_terms",
"fieldtype": "Link",
"hidden": 0,
@@ -795,6 +819,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_10",
"fieldtype": "Column Break",
"hidden": 0,
@@ -826,6 +851,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "country",
"fieldtype": "Link",
"hidden": 0,
@@ -858,6 +884,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "create_chart_of_accounts_based_on",
"fieldtype": "Select",
"hidden": 0,
@@ -892,6 +919,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.create_chart_of_accounts_based_on===\"Standard Template\"",
"fetch_if_empty": 0,
"fieldname": "chart_of_accounts",
"fieldtype": "Select",
"hidden": 0,
@@ -926,6 +954,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.create_chart_of_accounts_based_on===\"Existing Company\"",
"fetch_if_empty": 0,
"fieldname": "existing_company",
"fieldtype": "Link",
"hidden": 0,
@@ -959,6 +988,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "tax_id",
"fieldtype": "Data",
"hidden": 0,
@@ -991,6 +1021,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "date_of_establishment",
"fieldtype": "Date",
"hidden": 0,
@@ -1023,6 +1054,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "default_settings",
"fieldtype": "Section Break",
"hidden": 0,
@@ -1056,6 +1088,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "eval:!doc.__islocal",
"fetch_if_empty": 0,
"fieldname": "default_bank_account",
"fieldtype": "Link",
"hidden": 0,
@@ -1091,6 +1124,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "eval:!doc.__islocal",
"fetch_if_empty": 0,
"fieldname": "default_cash_account",
"fieldtype": "Link",
"hidden": 0,
@@ -1124,6 +1158,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "eval:!doc.__islocal",
"fetch_if_empty": 0,
"fieldname": "default_receivable_account",
"fieldtype": "Link",
"hidden": 0,
@@ -1158,6 +1193,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "round_off_account",
"fieldtype": "Link",
"hidden": 0,
@@ -1191,6 +1227,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "round_off_cost_center",
"fieldtype": "Link",
"hidden": 0,
@@ -1224,6 +1261,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "write_off_account",
"fieldtype": "Link",
"hidden": 0,
@@ -1257,6 +1295,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "discount_allowed_account",
"fieldtype": "Link",
"hidden": 0,
@@ -1290,6 +1329,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "discount_received_account",
"fieldtype": "Link",
"hidden": 0,
@@ -1323,6 +1363,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "exchange_gain_loss_account",
"fieldtype": "Link",
"hidden": 0,
@@ -1356,6 +1397,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "unrealized_exchange_gain_loss_account",
"fieldtype": "Link",
"hidden": 0,
@@ -1389,6 +1431,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break0",
"fieldtype": "Column Break",
"hidden": 0,
@@ -1414,6 +1457,40 @@
"unique": 0,
"width": "50%"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.parent_company",
"fetch_if_empty": 0,
"fieldname": "allow_account_creation_against_child_company",
"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": "Allow Account Creation Against Child Company",
"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,
@@ -1422,6 +1499,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "eval:!doc.__islocal",
"fetch_if_empty": 0,
"fieldname": "default_payable_account",
"fieldtype": "Link",
"hidden": 0,
@@ -1456,6 +1534,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "default_employee_advance_account",
"fieldtype": "Link",
"hidden": 0,
@@ -1490,6 +1569,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "eval:!doc.__islocal",
"fetch_if_empty": 0,
"fieldname": "default_expense_account",
"fieldtype": "Link",
"hidden": 0,
@@ -1523,6 +1603,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "eval:!doc.__islocal",
"fetch_if_empty": 0,
"fieldname": "default_income_account",
"fieldtype": "Link",
"hidden": 0,
@@ -1556,6 +1637,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "eval:!doc.__islocal",
"fetch_if_empty": 0,
"fieldname": "default_deferred_revenue_account",
"fieldtype": "Link",
"hidden": 0,
@@ -1590,6 +1672,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "eval:!doc.__islocal",
"fetch_if_empty": 0,
"fieldname": "default_deferred_expense_account",
"fieldtype": "Link",
"hidden": 0,
@@ -1624,6 +1707,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "eval:!doc.__islocal",
"fetch_if_empty": 0,
"fieldname": "default_payroll_payable_account",
"fieldtype": "Link",
"hidden": 0,
@@ -1658,6 +1742,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "eval:!doc.__islocal",
"fetch_if_empty": 0,
"fieldname": "default_expense_claim_payable_account",
"fieldtype": "Link",
"hidden": 0,
@@ -1691,6 +1776,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "section_break_22",
"fieldtype": "Section Break",
"hidden": 0,
@@ -1723,6 +1809,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "eval:!doc.__islocal",
"fetch_if_empty": 0,
"fieldname": "cost_center",
"fieldtype": "Link",
"hidden": 0,
@@ -1755,6 +1842,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_26",
"fieldtype": "Column Break",
"hidden": 0,
@@ -1787,6 +1875,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "eval:!doc.__islocal",
"fetch_if_empty": 0,
"fieldname": "credit_limit",
"fieldtype": "Currency",
"hidden": 0,
@@ -1822,6 +1911,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "",
"fetch_if_empty": 0,
"fieldname": "payment_terms",
"fieldtype": "Link",
"hidden": 0,
@@ -1856,6 +1946,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "eval:!doc.__islocal",
"fetch_if_empty": 0,
"fieldname": "auto_accounting_for_stock_settings",
"fieldtype": "Section Break",
"hidden": 0,
@@ -1888,6 +1979,7 @@
"collapsible": 0,
"columns": 0,
"default": "1",
"fetch_if_empty": 0,
"fieldname": "enable_perpetual_inventory",
"fieldtype": "Check",
"hidden": 0,
@@ -1920,6 +2012,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "default_inventory_account",
"fieldtype": "Link",
"hidden": 0,
@@ -1953,6 +2046,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "stock_adjustment_account",
"fieldtype": "Link",
"hidden": 0,
@@ -1985,6 +2079,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_32",
"fieldtype": "Column Break",
"hidden": 0,
@@ -2016,6 +2111,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "stock_received_but_not_billed",
"fieldtype": "Link",
"hidden": 0,
@@ -2048,6 +2144,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "expenses_included_in_valuation",
"fieldtype": "Link",
"hidden": 0,
@@ -2080,6 +2177,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "fixed_asset_depreciation_settings",
"fieldtype": "Section Break",
"hidden": 0,
@@ -2112,6 +2210,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "accumulated_depreciation_account",
"fieldtype": "Link",
"hidden": 0,
@@ -2145,6 +2244,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "depreciation_expense_account",
"fieldtype": "Link",
"hidden": 0,
@@ -2178,6 +2278,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "series_for_depreciation_entry",
"fieldtype": "Data",
"hidden": 0,
@@ -2210,6 +2311,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "expenses_included_in_asset_valuation",
"fieldtype": "Link",
"hidden": 0,
@@ -2243,6 +2345,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_40",
"fieldtype": "Column Break",
"hidden": 0,
@@ -2274,6 +2377,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "disposal_account",
"fieldtype": "Link",
"hidden": 0,
@@ -2307,6 +2411,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "depreciation_cost_center",
"fieldtype": "Link",
"hidden": 0,
@@ -2340,6 +2445,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "capital_work_in_progress_account",
"fieldtype": "Link",
"hidden": 0,
@@ -2373,6 +2479,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "asset_received_but_not_billed",
"fieldtype": "Link",
"hidden": 0,
@@ -2406,6 +2513,7 @@
"bold": 0,
"collapsible": 1,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "budget_detail",
"fieldtype": "Section Break",
"hidden": 0,
@@ -2438,6 +2546,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "exception_budget_approver_role",
"fieldtype": "Link",
"hidden": 0,
@@ -2472,6 +2581,7 @@
"collapsible": 0,
"columns": 0,
"description": "For reference only.",
"fetch_if_empty": 0,
"fieldname": "company_info",
"fieldtype": "Section Break",
"hidden": 0,
@@ -2503,6 +2613,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "date_of_incorporation",
"fieldtype": "Date",
"hidden": 0,
@@ -2535,6 +2646,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "address_html",
"fieldtype": "HTML",
"hidden": 0,
@@ -2566,6 +2678,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break1",
"fieldtype": "Column Break",
"hidden": 0,
@@ -2599,6 +2712,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.date_of_incorporation",
"fetch_if_empty": 0,
"fieldname": "date_of_commencement",
"fieldtype": "Date",
"hidden": 0,
@@ -2631,6 +2745,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "phone_no",
"fieldtype": "Data",
"hidden": 0,
@@ -2665,6 +2780,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "fax",
"fieldtype": "Data",
"hidden": 0,
@@ -2699,6 +2815,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "email",
"fieldtype": "Data",
"hidden": 0,
@@ -2733,6 +2850,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "website",
"fieldtype": "Data",
"hidden": 0,
@@ -2767,6 +2885,7 @@
"collapsible": 0,
"columns": 0,
"description": "",
"fetch_if_empty": 0,
"fieldname": "registration_info",
"fieldtype": "Section Break",
"hidden": 0,
@@ -2801,6 +2920,7 @@
"collapsible": 0,
"columns": 0,
"description": "Company registration numbers for your reference. Tax numbers etc.",
"fetch_if_empty": 0,
"fieldname": "registration_details",
"fieldtype": "Code",
"hidden": 0,
@@ -2834,6 +2954,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "delete_company_transactions",
"fieldtype": "Button",
"hidden": 0,
@@ -2866,6 +2987,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "lft",
"fieldtype": "Int",
"hidden": 1,
@@ -2898,6 +3020,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "rgt",
"fieldtype": "Int",
"hidden": 1,
@@ -2930,6 +3053,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "old_parent",
"fieldtype": "Data",
"hidden": 1,
@@ -2969,7 +3093,7 @@
"istable": 0,
"max_attachments": 0,
"menu_index": 0,
"modified": "2019-01-15 13:29:54.510379",
"modified": "2019-03-26 17:15:50.390548",
"modified_by": "Administrator",
"module": "Setup",
"name": "Company",

View File

@@ -377,7 +377,7 @@ def replace_abbr(company, old, new):
for d in doc:
_rename_record(d)
for dt in ["Warehouse", "Account", "Cost Center", "Department", "Location",
for dt in ["Warehouse", "Account", "Cost Center", "Department",
"Sales Taxes and Charges Template", "Purchase Taxes and Charges Template"]:
_rename_records(dt)
frappe.db.commit()

View File

@@ -12,7 +12,6 @@ from frappe.website.render import clear_cache
from frappe.website.doctype.website_slideshow.website_slideshow import get_slideshow
from erpnext.shopping_cart.product_info import set_product_info_for_website
from erpnext.utilities.product import get_qty_in_stock
from frappe.utils.html_utils import clean_html
class ItemGroup(NestedSet, WebsiteGenerator):
nsm_parent_field = 'parent_item_group'
@@ -27,7 +26,6 @@ class ItemGroup(NestedSet, WebsiteGenerator):
def validate(self):
super(ItemGroup, self).validate()
self.description = clean_html(self.description)
self.make_route()
def on_update(self):

View File

@@ -22,6 +22,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "name_and_employee_id",
"fieldtype": "Section Break",
"hidden": 0,
@@ -54,6 +55,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "sales_person_name",
"fieldtype": "Data",
"hidden": 0,
@@ -88,6 +90,7 @@
"collapsible": 0,
"columns": 0,
"description": "Select company name first.",
"fetch_if_empty": 0,
"fieldname": "parent_sales_person",
"fieldtype": "Link",
"hidden": 0,
@@ -122,6 +125,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "commission_rate",
"fieldtype": "Data",
"hidden": 0,
@@ -154,6 +158,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "is_group",
"fieldtype": "Check",
"hidden": 0,
@@ -189,6 +194,7 @@
"collapsible": 0,
"columns": 0,
"default": "1",
"fetch_if_empty": 0,
"fieldname": "enabled",
"fieldtype": "Check",
"hidden": 0,
@@ -221,6 +227,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "cb0",
"fieldtype": "Column Break",
"hidden": 0,
@@ -251,6 +258,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "employee",
"fieldtype": "Link",
"hidden": 0,
@@ -270,7 +278,7 @@
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
@@ -284,6 +292,7 @@
"collapsible": 0,
"columns": 0,
"fetch_from": "employee.department",
"fetch_if_empty": 0,
"fieldname": "department",
"fieldtype": "Link",
"hidden": 0,
@@ -317,6 +326,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "lft",
"fieldtype": "Int",
"hidden": 1,
@@ -350,6 +360,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "rgt",
"fieldtype": "Int",
"hidden": 1,
@@ -383,6 +394,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "old_parent",
"fieldtype": "Data",
"hidden": 1,
@@ -417,6 +429,7 @@
"collapsible": 0,
"columns": 0,
"description": "Set targets Item Group-wise for this Sales Person.",
"fetch_if_empty": 0,
"fieldname": "target_details_section_break",
"fieldtype": "Section Break",
"hidden": 0,
@@ -450,6 +463,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "targets",
"fieldtype": "Table",
"hidden": 0,
@@ -485,6 +499,7 @@
"collapsible": 0,
"columns": 0,
"description": "Select Monthly Distribution to unevenly distribute targets across months.",
"fetch_if_empty": 0,
"fieldname": "distribution_id",
"fieldtype": "Link",
"hidden": 0,
@@ -524,7 +539,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2019-01-30 11:28:16.966735",
"modified": "2019-04-09 20:03:35.967037",
"modified_by": "Administrator",
"module": "Setup",
"name": "Sales Person",
@@ -594,7 +609,7 @@
"search_fields": "parent_sales_person",
"show_name_in_global_search": 1,
"sort_order": "ASC",
"track_changes": 1,
"track_changes": 0,
"track_seen": 0,
"track_views": 0
}

View File

@@ -21,18 +21,18 @@ class SalesPerson(NestedSet):
self.load_dashboard_info()
def load_dashboard_info(self):
company_default_currency = get_default_currency()
company_default_currency = get_default_currency()
allocated_amount = frappe.db.sql("""
select sum(allocated_amount)
from `tabSales Team`
from `tabSales Team`
where sales_person = %s and docstatus=1 and parenttype = 'Sales Order'
""",(self.sales_person_name))
info = {}
info["allocated_amount"] = flt(allocated_amount[0][0]) if allocated_amount else 0
info["currency"] = company_default_currency
self.set_onload('dashboard_info', info)
def on_update(self):
@@ -48,10 +48,11 @@ class SalesPerson(NestedSet):
return frappe.db.get_value("User", user, "email") or user
def validate_employee_id(self):
sales_person = frappe.db.get_value("Sales Person", {"employee": self.employee})
if sales_person and sales_person != self.name:
frappe.throw(_("Another Sales Person {0} exists with the same Employee id").format(sales_person))
if self.employee:
sales_person = frappe.db.get_value("Sales Person", {"employee": self.employee})
if sales_person and sales_person != self.name:
frappe.throw(_("Another Sales Person {0} exists with the same Employee id").format(sales_person))
def on_doctype_update():
frappe.db.add_index("Sales Person", ["lft", "rgt"])
@@ -65,7 +66,7 @@ def get_timeline_data(doctype, name):
from
`tabSales Order` dt, `tabSales Team` st
where
st.sales_person = %s and st.parent = dt.name and dt.transaction_date > date_sub(curdate(), interval 1 year)
st.sales_person = %s and st.parent = dt.name and dt.transaction_date > date_sub(curdate(), interval 1 year)
group by dt.transaction_date ''', name)))
sales_invoice = dict(frappe.db.sql('''select
@@ -75,7 +76,7 @@ def get_timeline_data(doctype, name):
where
st.sales_person = %s and st.parent = dt.name and dt.posting_date > date_sub(curdate(), interval 1 year)
group by dt.posting_date ''', name))
for key in sales_invoice:
if out.get(key):
out[key] += sales_invoice[key]
@@ -97,5 +98,3 @@ def get_timeline_data(doctype, name):
out[key] = delivery_note[key]
return out

View File

@@ -23,6 +23,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "name_and_description_section",
"fieldtype": "Section Break",
"hidden": 0,
@@ -57,6 +58,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "naming_series",
"fieldtype": "Select",
"hidden": 0,
@@ -91,6 +93,7 @@
"collapsible": 0,
"columns": 0,
"description": "",
"fetch_if_empty": 0,
"fieldname": "item_code",
"fieldtype": "Data",
"hidden": 0,
@@ -127,6 +130,7 @@
"columns": 0,
"depends_on": "variant_of",
"description": "If item is a variant of another item then description, image, pricing, taxes etc will be set from the template unless explicitly specified",
"fetch_if_empty": 0,
"fieldname": "variant_of",
"fieldtype": "Link",
"hidden": 0,
@@ -160,6 +164,7 @@
"bold": 1,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "item_name",
"fieldtype": "Data",
"hidden": 0,
@@ -195,6 +200,7 @@
"collapsible": 0,
"columns": 0,
"description": "",
"fetch_if_empty": 0,
"fieldname": "item_group",
"fieldtype": "Link",
"hidden": 0,
@@ -230,6 +236,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "is_item_from_hub",
"fieldtype": "Check",
"hidden": 0,
@@ -263,6 +270,7 @@
"collapsible": 0,
"columns": 0,
"description": "",
"fetch_if_empty": 0,
"fieldname": "stock_uom",
"fieldtype": "Link",
"hidden": 0,
@@ -298,6 +306,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break0",
"fieldtype": "Column Break",
"hidden": 0,
@@ -329,6 +338,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "disabled",
"fieldtype": "Check",
"hidden": 0,
@@ -361,6 +371,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "allow_alternative_item",
"fieldtype": "Check",
"hidden": 0,
@@ -395,6 +406,7 @@
"columns": 0,
"default": "1",
"description": "",
"fetch_if_empty": 0,
"fieldname": "is_stock_item",
"fieldtype": "Check",
"hidden": 0,
@@ -431,6 +443,7 @@
"collapsible": 0,
"columns": 0,
"default": "1",
"fetch_if_empty": 0,
"fieldname": "include_item_in_manufacturing",
"fieldtype": "Check",
"hidden": 0,
@@ -464,6 +477,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "eval:(doc.__islocal&&doc.is_stock_item && !doc.has_serial_no && !doc.has_batch_no)",
"fetch_if_empty": 0,
"fieldname": "opening_stock",
"fieldtype": "Float",
"hidden": 0,
@@ -497,6 +511,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "is_stock_item",
"fetch_if_empty": 0,
"fieldname": "valuation_rate",
"fieldtype": "Currency",
"hidden": 0,
@@ -530,6 +545,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.__islocal",
"fetch_if_empty": 0,
"fieldname": "standard_rate",
"fieldtype": "Currency",
"hidden": 0,
@@ -562,6 +578,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "is_fixed_asset",
"fieldtype": "Check",
"hidden": 0,
@@ -595,6 +612,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "is_fixed_asset",
"fetch_if_empty": 0,
"fieldname": "asset_category",
"fieldtype": "Link",
"hidden": 0,
@@ -629,6 +647,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "is_fixed_asset",
"fetch_if_empty": 0,
"fieldname": "asset_naming_series",
"fieldtype": "Select",
"hidden": 0,
@@ -663,6 +682,7 @@
"columns": 0,
"depends_on": "eval:!doc.__islocal",
"description": "",
"fetch_if_empty": 0,
"fieldname": "tolerance",
"fieldtype": "Float",
"hidden": 0,
@@ -697,6 +717,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "image",
"fieldtype": "Attach Image",
"hidden": 1,
@@ -730,6 +751,7 @@
"bold": 0,
"collapsible": 1,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "section_break_11",
"fieldtype": "Section Break",
"hidden": 0,
@@ -762,6 +784,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "brand",
"fieldtype": "Link",
"hidden": 0,
@@ -797,6 +820,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "description",
"fieldtype": "Text Editor",
"hidden": 0,
@@ -831,6 +855,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "sb_barcodes",
"fieldtype": "Section Break",
"hidden": 0,
@@ -863,6 +888,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "barcodes",
"fieldtype": "Table",
"hidden": 0,
@@ -898,6 +924,7 @@
"collapsible_depends_on": "is_stock_item",
"columns": 0,
"depends_on": "is_stock_item",
"fetch_if_empty": 0,
"fieldname": "inventory_section",
"fieldtype": "Section Break",
"hidden": 0,
@@ -932,6 +959,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "shelf_life_in_days",
"fieldtype": "Int",
"hidden": 0,
@@ -966,6 +994,7 @@
"columns": 0,
"default": "2099-12-31",
"depends_on": "is_stock_item",
"fetch_if_empty": 0,
"fieldname": "end_of_life",
"fieldtype": "Date",
"hidden": 0,
@@ -1001,6 +1030,7 @@
"collapsible": 0,
"columns": 0,
"default": "Purchase",
"fetch_if_empty": 0,
"fieldname": "default_material_request_type",
"fieldtype": "Select",
"hidden": 0,
@@ -1035,6 +1065,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "is_stock_item",
"fetch_if_empty": 0,
"fieldname": "valuation_method",
"fieldtype": "Select",
"hidden": 0,
@@ -1069,6 +1100,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "is_stock_item",
"fetch_if_empty": 0,
"fieldname": "column_break1",
"fieldtype": "Column Break",
"hidden": 0,
@@ -1103,6 +1135,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.is_stock_item",
"fetch_if_empty": 0,
"fieldname": "warranty_period",
"fieldtype": "Data",
"hidden": 0,
@@ -1139,6 +1172,7 @@
"columns": 0,
"depends_on": "is_stock_item",
"description": "",
"fetch_if_empty": 0,
"fieldname": "weight_per_unit",
"fieldtype": "Float",
"hidden": 0,
@@ -1172,6 +1206,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.is_stock_item",
"fetch_if_empty": 0,
"fieldname": "weight_uom",
"fieldtype": "Link",
"hidden": 0,
@@ -1207,6 +1242,7 @@
"columns": 0,
"depends_on": "is_stock_item",
"description": "",
"fetch_if_empty": 0,
"fieldname": "reorder_section",
"fieldtype": "Section Break",
"hidden": 0,
@@ -1242,6 +1278,7 @@
"columns": 0,
"depends_on": "",
"description": "Will also apply for variants unless overrridden",
"fetch_if_empty": 0,
"fieldname": "reorder_levels",
"fieldtype": "Table",
"hidden": 0,
@@ -1276,6 +1313,7 @@
"collapsible": 1,
"columns": 0,
"depends_on": "",
"fetch_if_empty": 0,
"fieldname": "unit_of_measure_conversion",
"fieldtype": "Section Break",
"hidden": 0,
@@ -1310,6 +1348,7 @@
"columns": 0,
"depends_on": "",
"description": "Will also apply for variants",
"fetch_if_empty": 0,
"fieldname": "uoms",
"fieldtype": "Table",
"hidden": 0,
@@ -1347,6 +1386,7 @@
"collapsible_depends_on": "eval:doc.has_batch_no || doc.has_serial_no || doc.is_fixed_asset",
"columns": 0,
"depends_on": "eval:doc.is_stock_item || doc.is_fixed_asset",
"fetch_if_empty": 0,
"fieldname": "serial_nos_and_batches",
"fieldtype": "Section Break",
"hidden": 0,
@@ -1381,6 +1421,7 @@
"columns": 0,
"default": "",
"depends_on": "eval:doc.is_stock_item",
"fetch_if_empty": 0,
"fieldname": "has_batch_no",
"fieldtype": "Check",
"hidden": 0,
@@ -1418,6 +1459,7 @@
"columns": 0,
"depends_on": "has_batch_no",
"description": "",
"fetch_if_empty": 0,
"fieldname": "create_new_batch",
"fieldtype": "Check",
"hidden": 0,
@@ -1452,6 +1494,7 @@
"columns": 0,
"depends_on": "eval:doc.has_batch_no==1 && doc.create_new_batch==1",
"description": "Example: ABCD.#####. If series is set and Batch No is not mentioned in transactions,then automatic batch number will be created based on this series. If you always want to explicitly mention Batch No for this item,leave this blank. Note: this setting will take priority over the Naming Series Prefix in Stock Settings.",
"fetch_if_empty": 0,
"fieldname": "batch_number_series",
"fieldtype": "Data",
"hidden": 0,
@@ -1485,6 +1528,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "has_batch_no",
"fetch_if_empty": 0,
"fieldname": "has_expiry_date",
"fieldtype": "Check",
"hidden": 0,
@@ -1518,6 +1562,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "has_batch_no",
"fetch_if_empty": 0,
"fieldname": "retain_sample",
"fieldtype": "Check",
"hidden": 0,
@@ -1552,6 +1597,7 @@
"columns": 0,
"depends_on": "eval: (doc.retain_sample && doc.has_batch_no)",
"description": "Maximum sample quantity that can be retained",
"fetch_if_empty": 0,
"fieldname": "sample_quantity",
"fieldtype": "Int",
"hidden": 0,
@@ -1584,6 +1630,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_37",
"fieldtype": "Column Break",
"hidden": 0,
@@ -1618,6 +1665,7 @@
"default": "",
"depends_on": "eval:doc.is_stock_item || doc.is_fixed_asset",
"description": "",
"fetch_if_empty": 0,
"fieldname": "has_serial_no",
"fieldtype": "Check",
"hidden": 0,
@@ -1655,6 +1703,7 @@
"columns": 0,
"depends_on": "eval:doc.is_stock_item || doc.is_fixed_asset",
"description": "Example: ABCD.#####\nIf series is set and Serial No is not mentioned in transactions,then automatic serial number will be created based on this series. If you always want to explicitly mention Serial Nos for this item. leave this blank.",
"fetch_if_empty": 0,
"fieldname": "serial_no_series",
"fieldtype": "Data",
"hidden": 0,
@@ -1689,6 +1738,7 @@
"collapsible_depends_on": "attributes",
"columns": 0,
"depends_on": "",
"fetch_if_empty": 0,
"fieldname": "variants_section",
"fieldtype": "Section Break",
"hidden": 0,
@@ -1724,6 +1774,7 @@
"default": "0",
"depends_on": "eval:!doc.variant_of",
"description": "If this item has variants,then it cannot be selected in sales orders etc.",
"fetch_if_empty": 0,
"fieldname": "has_variants",
"fieldtype": "Check",
"hidden": 0,
@@ -1759,6 +1810,7 @@
"columns": 0,
"default": "Item Attribute",
"depends_on": "has_variants",
"fetch_if_empty": 0,
"fieldname": "variant_based_on",
"fieldtype": "Select",
"hidden": 0,
@@ -1781,7 +1833,7 @@
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 1,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
@@ -1793,6 +1845,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "eval:(doc.has_variants || doc.variant_of) && doc.variant_based_on==='Item Attribute'",
"fetch_if_empty": 0,
"fieldname": "attributes",
"fieldtype": "Table",
"hidden": 1,
@@ -1826,6 +1879,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "defaults",
"fieldtype": "Section Break",
"hidden": 0,
@@ -1858,6 +1912,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "item_defaults",
"fieldtype": "Table",
"hidden": 0,
@@ -1891,6 +1946,7 @@
"bold": 0,
"collapsible": 1,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "purchase_details",
"fieldtype": "Section Break",
"hidden": 0,
@@ -1926,6 +1982,7 @@
"collapsible": 0,
"columns": 0,
"default": "1",
"fetch_if_empty": 0,
"fieldname": "is_purchase_item",
"fieldtype": "Check",
"hidden": 0,
@@ -1958,6 +2015,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "purchase_uom",
"fieldtype": "Link",
"hidden": 0,
@@ -1994,6 +2052,7 @@
"default": "0.00",
"depends_on": "is_stock_item",
"description": "",
"fetch_if_empty": 0,
"fieldname": "min_order_qty",
"fieldtype": "Float",
"hidden": 0,
@@ -2028,6 +2087,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "safety_stock",
"fieldtype": "Float",
"hidden": 0,
@@ -2060,6 +2120,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "purchase_details_cb",
"fieldtype": "Column Break",
"hidden": 0,
@@ -2094,6 +2155,7 @@
"columns": 0,
"depends_on": "",
"description": "Average time taken by the supplier to deliver",
"fetch_if_empty": 0,
"fieldname": "lead_time_days",
"fieldtype": "Int",
"hidden": 0,
@@ -2129,6 +2191,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "",
"fetch_if_empty": 0,
"fieldname": "last_purchase_rate",
"fieldtype": "Float",
"hidden": 0,
@@ -2164,6 +2227,7 @@
"collapsible": 1,
"columns": 0,
"depends_on": "",
"fetch_if_empty": 0,
"fieldname": "supplier_details",
"fieldtype": "Section Break",
"hidden": 0,
@@ -2196,6 +2260,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "delivered_by_supplier",
"fieldtype": "Check",
"hidden": 0,
@@ -2229,6 +2294,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "",
"fetch_if_empty": 0,
"fieldname": "manufacturer",
"fieldtype": "Link",
"hidden": 0,
@@ -2263,6 +2329,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "",
"fetch_if_empty": 0,
"fieldname": "manufacturer_part_no",
"fieldtype": "Data",
"hidden": 0,
@@ -2296,6 +2363,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "",
"fetch_if_empty": 0,
"fieldname": "column_break2",
"fieldtype": "Column Break",
"hidden": 0,
@@ -2331,6 +2399,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "",
"fetch_if_empty": 0,
"fieldname": "supplier_items",
"fieldtype": "Table",
"hidden": 0,
@@ -2364,6 +2433,7 @@
"bold": 0,
"collapsible": 1,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "foreign_trade_details",
"fieldtype": "Section Break",
"hidden": 0,
@@ -2396,6 +2466,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "country_of_origin",
"fieldtype": "Link",
"hidden": 0,
@@ -2429,6 +2500,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_59",
"fieldtype": "Column Break",
"hidden": 0,
@@ -2460,6 +2532,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "customs_tariff_number",
"fieldtype": "Link",
"hidden": 0,
@@ -2493,6 +2566,7 @@
"bold": 0,
"collapsible": 1,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "sales_details",
"fieldtype": "Section Break",
"hidden": 0,
@@ -2527,6 +2601,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "sales_uom",
"fieldtype": "Link",
"hidden": 0,
@@ -2561,6 +2636,7 @@
"collapsible": 0,
"columns": 0,
"default": "1",
"fetch_if_empty": 0,
"fieldname": "is_sales_item",
"fieldtype": "Check",
"hidden": 0,
@@ -2594,6 +2670,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "",
"fetch_if_empty": 0,
"fieldname": "column_break3",
"fieldtype": "Column Break",
"hidden": 0,
@@ -2629,6 +2706,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "",
"fetch_if_empty": 0,
"fieldname": "max_discount",
"fieldtype": "Float",
"hidden": 0,
@@ -2663,6 +2741,7 @@
"bold": 0,
"collapsible": 1,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "deferred_revenue",
"fieldtype": "Section Break",
"hidden": 0,
@@ -2696,6 +2775,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "enable_deferred_revenue",
"fetch_if_empty": 0,
"fieldname": "deferred_revenue_account",
"fieldtype": "Link",
"hidden": 0,
@@ -2729,6 +2809,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "enable_deferred_revenue",
"fieldtype": "Check",
"hidden": 0,
@@ -2761,6 +2842,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_85",
"fieldtype": "Column Break",
"hidden": 0,
@@ -2793,6 +2875,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "enable_deferred_revenue",
"fetch_if_empty": 0,
"fieldname": "no_of_months",
"fieldtype": "Int",
"hidden": 0,
@@ -2825,6 +2908,7 @@
"bold": 0,
"collapsible": 1,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "deferred_expense_section",
"fieldtype": "Section Break",
"hidden": 0,
@@ -2858,6 +2942,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "enable_deferred_expense",
"fetch_if_empty": 0,
"fieldname": "deferred_expense_account",
"fieldtype": "Link",
"hidden": 0,
@@ -2891,6 +2976,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "enable_deferred_expense",
"fieldtype": "Check",
"hidden": 0,
@@ -2923,6 +3009,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_88",
"fieldtype": "Column Break",
"hidden": 0,
@@ -2955,6 +3042,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "enable_deferred_expense",
"fetch_if_empty": 0,
"fieldname": "no_of_months_exp",
"fieldtype": "Int",
"hidden": 0,
@@ -2987,6 +3075,7 @@
"bold": 0,
"collapsible": 1,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "customer_details",
"fieldtype": "Section Break",
"hidden": 0,
@@ -3021,6 +3110,7 @@
"columns": 0,
"depends_on": "",
"description": "",
"fetch_if_empty": 0,
"fieldname": "customer_items",
"fieldtype": "Table",
"hidden": 0,
@@ -3054,6 +3144,7 @@
"bold": 0,
"collapsible": 1,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "item_tax_section_break",
"fieldtype": "Section Break",
"hidden": 0,
@@ -3089,6 +3180,7 @@
"collapsible": 0,
"columns": 0,
"description": "Will also apply for variants",
"fetch_if_empty": 0,
"fieldname": "taxes",
"fieldtype": "Table",
"hidden": 0,
@@ -3124,6 +3216,7 @@
"bold": 0,
"collapsible": 1,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "inspection_criteria",
"fieldtype": "Section Break",
"hidden": 0,
@@ -3159,6 +3252,7 @@
"collapsible": 0,
"columns": 0,
"default": "",
"fetch_if_empty": 0,
"fieldname": "inspection_required_before_purchase",
"fieldtype": "Check",
"hidden": 0,
@@ -3194,6 +3288,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "inspection_required_before_delivery",
"fieldtype": "Check",
"hidden": 0,
@@ -3227,6 +3322,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "eval:(doc.inspection_required_before_purchase || doc.inspection_required_before_delivery)",
"fetch_if_empty": 0,
"fieldname": "quality_inspection_template",
"fieldtype": "Link",
"hidden": 0,
@@ -3261,6 +3357,7 @@
"collapsible": 1,
"columns": 0,
"depends_on": "is_stock_item",
"fetch_if_empty": 0,
"fieldname": "manufacturing",
"fieldtype": "Section Break",
"hidden": 0,
@@ -3296,6 +3393,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "",
"fetch_if_empty": 0,
"fieldname": "default_bom",
"fieldtype": "Link",
"hidden": 0,
@@ -3333,6 +3431,7 @@
"columns": 0,
"default": "",
"description": "If subcontracted to a vendor",
"fetch_if_empty": 0,
"fieldname": "is_sub_contracted_item",
"fieldtype": "Check",
"hidden": 0,
@@ -3368,6 +3467,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_74",
"fieldtype": "Column Break",
"hidden": 0,
@@ -3399,6 +3499,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "customer_code",
"fieldtype": "Data",
"hidden": 1,
@@ -3431,6 +3532,7 @@
"bold": 0,
"collapsible": 1,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "website_section",
"fieldtype": "Section Break",
"hidden": 0,
@@ -3465,6 +3567,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "eval:!doc.variant_of",
"fetch_if_empty": 0,
"fieldname": "show_in_website",
"fieldtype": "Check",
"hidden": 0,
@@ -3498,6 +3601,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "variant_of",
"fetch_if_empty": 0,
"fieldname": "show_variant_in_website",
"fieldtype": "Check",
"hidden": 0,
@@ -3531,6 +3635,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "eval: doc.show_in_website || doc.show_variant_in_website",
"fetch_if_empty": 0,
"fieldname": "route",
"fieldtype": "Small Text",
"hidden": 0,
@@ -3565,6 +3670,7 @@
"columns": 0,
"depends_on": "eval: doc.show_in_website || doc.show_variant_in_website",
"description": "Items with higher weightage will be shown higher",
"fetch_if_empty": 0,
"fieldname": "weightage",
"fieldtype": "Int",
"hidden": 0,
@@ -3599,6 +3705,7 @@
"columns": 0,
"depends_on": "eval: doc.show_in_website || doc.show_variant_in_website",
"description": "Show a slideshow at the top of the page",
"fetch_if_empty": 0,
"fieldname": "slideshow",
"fieldtype": "Link",
"hidden": 0,
@@ -3634,6 +3741,7 @@
"columns": 0,
"depends_on": "eval: doc.show_in_website || doc.show_variant_in_website",
"description": "Item Image (if not slideshow)",
"fetch_if_empty": 0,
"fieldname": "website_image",
"fieldtype": "Attach",
"hidden": 0,
@@ -3667,6 +3775,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "thumbnail",
"fieldtype": "Data",
"hidden": 0,
@@ -3699,6 +3808,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "cb72",
"fieldtype": "Column Break",
"hidden": 0,
@@ -3732,6 +3842,7 @@
"columns": 0,
"depends_on": "eval: doc.show_in_website || doc.show_variant_in_website",
"description": "Show \"In Stock\" or \"Not in Stock\" based on stock available in this warehouse.",
"fetch_if_empty": 0,
"fieldname": "website_warehouse",
"fieldtype": "Link",
"hidden": 0,
@@ -3767,6 +3878,7 @@
"columns": 0,
"depends_on": "eval: doc.show_in_website || doc.show_variant_in_website",
"description": "List this Item in multiple groups on the website.",
"fetch_if_empty": 0,
"fieldname": "website_item_groups",
"fieldtype": "Table",
"hidden": 0,
@@ -3802,6 +3914,7 @@
"collapsible_depends_on": "website_specifications",
"columns": 0,
"depends_on": "eval: doc.show_in_website || doc.show_variant_in_website",
"fetch_if_empty": 0,
"fieldname": "sb72",
"fieldtype": "Section Break",
"hidden": 0,
@@ -3835,6 +3948,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "eval: doc.show_in_website || doc.show_variant_in_website",
"fetch_if_empty": 0,
"fieldname": "copy_from_item_group",
"fieldtype": "Button",
"hidden": 0,
@@ -3868,6 +3982,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "eval: doc.show_in_website || doc.show_variant_in_website",
"fetch_if_empty": 0,
"fieldname": "website_specifications",
"fieldtype": "Table",
"hidden": 0,
@@ -3902,6 +4017,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "eval: doc.show_in_website || doc.show_variant_in_website",
"fetch_if_empty": 0,
"fieldname": "web_long_description",
"fieldtype": "Text Editor",
"hidden": 0,
@@ -3934,6 +4050,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "total_projected_qty",
"fieldtype": "Float",
"hidden": 1,
@@ -3967,6 +4084,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "eval:(!doc.is_item_from_hub)",
"fetch_if_empty": 0,
"fieldname": "hub_publishing_sb",
"fieldtype": "Section Break",
"hidden": 0,
@@ -4001,6 +4119,7 @@
"columns": 0,
"default": "0",
"description": "Publish Item to hub.erpnext.com",
"fetch_if_empty": 0,
"fieldname": "publish_in_hub",
"fieldtype": "Check",
"hidden": 0,
@@ -4033,6 +4152,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "hub_category_to_publish",
"fieldtype": "Data",
"hidden": 0,
@@ -4067,6 +4187,7 @@
"collapsible": 0,
"columns": 0,
"description": "Publish \"In Stock\" or \"Not in Stock\" on Hub based on stock available in this warehouse.",
"fetch_if_empty": 0,
"fieldname": "hub_warehouse",
"fieldtype": "Link",
"hidden": 0,
@@ -4101,6 +4222,7 @@
"collapsible": 0,
"columns": 0,
"default": "0",
"fetch_if_empty": 0,
"fieldname": "synced_with_hub",
"fieldtype": "Check",
"hidden": 0,
@@ -4139,7 +4261,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 1,
"modified": "2019-02-16 17:43:56.039611",
"modified": "2019-04-05 12:03:24.530849",
"modified_by": "Administrator",
"module": "Stock",
"name": "Item",

View File

@@ -9,11 +9,11 @@ import erpnext
import frappe
import copy
from erpnext.controllers.item_variant import (ItemVariantExistsError,
copy_attributes_to_variant, get_variant, make_variant_item_code, validate_item_variant_attributes)
copy_attributes_to_variant, get_variant, make_variant_item_code, validate_item_variant_attributes)
from erpnext.setup.doctype.item_group.item_group import (get_parent_item_groups, invalidate_cache_for)
from frappe import _, msgprint
from frappe.utils import (cint, cstr, flt, formatdate, get_timestamp, getdate,
now_datetime, random_string, strip)
now_datetime, random_string, strip)
from frappe.utils.html_utils import clean_html
from frappe.website.doctype.website_slideshow.website_slideshow import \
get_slideshow
@@ -49,9 +49,6 @@ class Item(WebsiteGenerator):
self.set_onload('stock_exists', self.stock_ledger_created())
self.set_asset_naming_series()
if self.is_fixed_asset:
asset = self.asset_exists()
self.set_onload("asset_exists", True if asset else False)
def set_asset_naming_series(self):
if not hasattr(self, '_asset_naming_series'):
@@ -118,9 +115,9 @@ class Item(WebsiteGenerator):
self.validate_has_variants()
self.validate_stock_exists_for_template_item()
self.validate_asset_exists_for_serialized_asset()
self.validate_attributes()
self.validate_variant_attributes()
self.validate_variant_based_on_change()
self.validate_website_image()
self.make_thumbnail()
self.validate_fixed_asset()
@@ -128,6 +125,7 @@ class Item(WebsiteGenerator):
self.validate_uom_conversion_factor()
self.validate_item_defaults()
self.update_defaults_from_item_group()
self.validate_stock_for_has_batch_and_has_serial()
if not self.get("__islocal"):
self.old_item_group = frappe.db.get_value(self.doctype, self.name, "item_group")
@@ -151,7 +149,7 @@ class Item(WebsiteGenerator):
'''Add a new price'''
if not price_list:
price_list = (frappe.db.get_single_value('Selling Settings', 'selling_price_list')
or frappe.db.get_value('Price List', _('Standard Selling')))
or frappe.db.get_value('Price List', _('Standard Selling')))
if price_list:
item_price = frappe.get_doc({
"doctype": "Item Price",
@@ -190,7 +188,7 @@ class Item(WebsiteGenerator):
def make_route(self):
if not self.route:
return cstr(frappe.db.get_value('Item Group', self.item_group,
'route')) + '/' + self.scrub((self.item_name if self.item_name else self.item_code) + '-' + random_string(5))
'route')) + '/' + self.scrub((self.item_name if self.item_name else self.item_code) + '-' + random_string(5))
def validate_website_image(self):
"""Validate if the website image is a public file"""
@@ -213,7 +211,7 @@ class Item(WebsiteGenerator):
if not file_doc:
if not auto_set_website_image:
frappe.msgprint(_("Website Image {0} attached to Item {1} cannot be found")
.format(self.website_image, self.name))
.format(self.website_image, self.name))
self.website_image = None
@@ -314,8 +312,8 @@ class Item(WebsiteGenerator):
# load variants
# also used in set_attribute_context
context.variants = frappe.get_all("Item",
filters={"variant_of": self.name, "show_variant_in_website": 1},
order_by="name asc")
filters={"variant_of": self.name, "show_variant_in_website": 1},
order_by="name asc")
variant = frappe.form_dict.variant
if not variant and context.variants:
@@ -326,7 +324,7 @@ class Item(WebsiteGenerator):
context.variant = frappe.get_doc("Item", variant)
for fieldname in ("website_image", "web_long_description", "description",
"website_specifications"):
"website_specifications"):
if context.variant.get(fieldname):
value = context.variant.get(fieldname)
if isinstance(value, list):
@@ -349,7 +347,7 @@ class Item(WebsiteGenerator):
# load attributes
for v in context.variants:
v.attributes = frappe.get_all("Item Variant Attribute",
fields=["attribute", "attribute_value"],
fields=["attribute", "attribute_value"],
filters={"parent": v.name})
for attr in v.attributes:
@@ -530,7 +528,7 @@ class Item(WebsiteGenerator):
warehouse += [d.get("warehouse")]
else:
frappe.throw(_("Row {0}: An Reorder entry already exists for this warehouse {1}")
.format(d.idx, d.warehouse), DuplicateReorderRows)
.format(d.idx, d.warehouse), DuplicateReorderRows)
if d.warehouse_reorder_level and not d.warehouse_reorder_qty:
frappe.throw(_("Row #{0}: Please set reorder quantity").format(d.idx))
@@ -550,7 +548,7 @@ class Item(WebsiteGenerator):
def update_item_price(self):
frappe.db.sql("""update `tabItem Price` set item_name=%s,
item_description=%s, brand=%s where item_code=%s""",
(self.item_name, self.description, self.brand, self.name))
(self.item_name, self.description, self.brand, self.name))
def on_trash(self):
super(Item, self).on_trash()
@@ -572,7 +570,7 @@ class Item(WebsiteGenerator):
new_properties = [cstr(d) for d in frappe.db.get_value("Item", new_name, field_list)]
if new_properties != [cstr(self.get(fld)) for fld in field_list]:
frappe.throw(_("To merge, following properties must be same for both items")
+ ": \n" + ", ".join([self.meta.get_label(fld) for fld in field_list]))
+ ": \n" + ", ".join([self.meta.get_label(fld) for fld in field_list]))
def after_rename(self, old_name, new_name, merge):
if self.route:
@@ -595,7 +593,7 @@ class Item(WebsiteGenerator):
item_wise_tax_detail.pop(old_name)
frappe.db.set_value(dt, d.name, "item_wise_tax_detail",
json.dumps(item_wise_tax_detail), update_modified=False)
json.dumps(item_wise_tax_detail), update_modified=False)
def set_last_purchase_rate(self, new_name):
last_purchase_rate = get_last_purchase_details(new_name).get("base_rate", 0)
@@ -623,7 +621,7 @@ class Item(WebsiteGenerator):
self.set("website_specifications", [])
if self.item_group:
for label, desc in frappe.db.get_values("Item Website Specification",
{"parent": self.item_group}, ["label", "description"]):
{"parent": self.item_group}, ["label", "description"]):
row = self.append("website_specifications")
row.label = label
row.description = desc
@@ -697,7 +695,7 @@ class Item(WebsiteGenerator):
def update_variants(self):
if self.flags.dont_update_variants or \
frappe.db.get_single_value('Item Variant Settings', 'do_not_update_variants'):
frappe.db.get_single_value('Item Variant Settings', 'do_not_update_variants'):
return
if self.has_variants:
variants = frappe.db.get_all("Item", fields=["item_code"], filters={"variant_of": self.name})
@@ -726,17 +724,10 @@ class Item(WebsiteGenerator):
frappe.throw(
_('Cannot change Attributes after stock transaction. Make a new Item and transfer stock to the new Item'))
def validate_asset_exists_for_serialized_asset(self):
if (not self.get("__islocal") and self.asset_exists() and
cint(self.has_serial_no) != cint(frappe.db.get_value('Item', self.name, 'has_serial_no'))):
frappe.throw(_("Asset is already exists against the item {0}, you cannot change the has serial no value")
.format(self.name))
def asset_exists(self):
if not hasattr(self, '_asset_created'):
self._asset_created = frappe.db.get_all("Asset",
filters={"item_code": self.name, "docstatus": 1}, limit=1)
return self._asset_created
def validate_variant_based_on_change(self):
if not self.is_new() and (self.variant_of or (self.has_variants and frappe.get_all("Item", {"variant_of": self.name}))):
if self.variant_based_on != frappe.db.get_value("Item", self.name, "variant_based_on"):
frappe.throw(_("Variant Based On cannot be changed"))
def validate_uom(self):
if not self.get("__islocal"):
@@ -748,7 +739,7 @@ class Item(WebsiteGenerator):
template_uom = frappe.db.get_value("Item", self.variant_of, "stock_uom")
if template_uom != self.stock_uom:
frappe.throw(_("Default Unit of Measure for Variant '{0}' must be same as in Template '{1}'")
.format(self.stock_uom, template_uom))
.format(self.stock_uom, template_uom))
def validate_uom_conversion_factor(self):
if self.uoms:
@@ -758,7 +749,13 @@ class Item(WebsiteGenerator):
d.conversion_factor = value
def validate_attributes(self):
if (self.has_variants or self.variant_of) and self.variant_based_on == 'Item Attribute':
if not (self.has_variants or self.variant_of):
return
if not self.variant_based_on:
self.variant_based_on = 'Item Attribute'
if self.variant_based_on == 'Item Attribute':
attributes = []
if not self.attributes:
frappe.throw(_("Attribute table is mandatory"))
@@ -780,10 +777,15 @@ class Item(WebsiteGenerator):
variant = get_variant(self.variant_of, args, self.name)
if variant:
frappe.throw(_("Item variant {0} exists with same attributes")
.format(variant), ItemVariantExistsError)
.format(variant), ItemVariantExistsError)
validate_item_variant_attributes(self, args)
def validate_stock_for_has_batch_and_has_serial(self):
if self.stock_ledger_created():
for value in ["has_batch_no", "has_serial_no"]:
if frappe.db.get_value("Item", self.name, value) != self.get_value(value):
frappe.throw(_("Cannot change {0} as Stock Transaction for Item {1} exist.".format(value, self.name)))
def get_timeline_data(doctype, name):
'''returns timeline data based on stock ledger entry'''
@@ -863,18 +865,18 @@ def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0):
limit 1""", (item_code, cstr(doc_name)), as_dict=1)
purchase_order_date = getdate(last_purchase_order and last_purchase_order[0].transaction_date
or "1900-01-01")
or "1900-01-01")
purchase_receipt_date = getdate(last_purchase_receipt and
last_purchase_receipt[0].posting_date or "1900-01-01")
last_purchase_receipt[0].posting_date or "1900-01-01")
if (purchase_order_date > purchase_receipt_date) or \
(last_purchase_order and not last_purchase_receipt):
(last_purchase_order and not last_purchase_receipt):
# use purchase order
last_purchase = last_purchase_order[0]
purchase_date = purchase_order_date
elif (purchase_receipt_date > purchase_order_date) or \
(last_purchase_receipt and not last_purchase_order):
(last_purchase_receipt and not last_purchase_order):
# use purchase receipt
last_purchase = last_purchase_receipt[0]
purchase_date = purchase_receipt_date
@@ -904,7 +906,7 @@ def invalidate_cache_for_item(doc):
invalidate_cache_for(doc, doc.item_group)
website_item_groups = list(set((doc.get("old_website_item_groups") or [])
+ [d.item_group for d in doc.get({"doctype": "Website Item Group"}) if d.item_group]))
+ [d.item_group for d in doc.get({"doctype": "Website Item Group"}) if d.item_group]))
for item_group in website_item_groups:
invalidate_cache_for(doc, item_group)
@@ -919,7 +921,7 @@ def check_stock_uom_with_bin(item, stock_uom):
matched = True
ref_uom = frappe.db.get_value("Stock Ledger Entry",
{"item_code": item}, "stock_uom")
{"item_code": item}, "stock_uom")
if ref_uom:
if cstr(ref_uom) != cstr(stock_uom):
@@ -928,7 +930,7 @@ def check_stock_uom_with_bin(item, stock_uom):
bin_list = frappe.db.sql("select * from tabBin where item_code=%s", item, as_dict=1)
for bin in bin_list:
if (bin.reserved_qty > 0 or bin.ordered_qty > 0 or bin.indented_qty > 0
or bin.planned_qty > 0) and cstr(bin.stock_uom) != cstr(stock_uom):
or bin.planned_qty > 0) and cstr(bin.stock_uom) != cstr(stock_uom):
matched = False
break
@@ -1005,4 +1007,4 @@ def update_variants(variants, template, publish_progress=True):
variant.save()
count+=1
if publish_progress:
frappe.publish_progress(count*100/len(variants), title = _("Updating Variants..."))
frappe.publish_progress(count*100/len(variants), title = _("Updating Variants..."))

View File

@@ -17,7 +17,7 @@ from erpnext.stock.get_item_details import get_item_details
from six import iteritems
test_ignore = ["BOM"]
test_dependencies = ["Warehouse", "Item Group"]
test_dependencies = ["Warehouse", "Item Group", "Brand"]
def make_item(item_code, properties=None):
if frappe.db.exists("Item", item_code):
@@ -393,3 +393,6 @@ def create_item(item_code, is_stock_item=None, valuation_rate=0, warehouse=None,
"company": "_Test Company"
})
item.save()
else:
item = frappe.get_doc("Item", item_code)
return item

View File

@@ -6,7 +6,8 @@
"item_attribute_values": [
{"attribute_value": "Small", "abbr": "S"},
{"attribute_value": "Medium", "abbr": "M"},
{"attribute_value": "Large", "abbr": "L"}
{"attribute_value": "Large", "abbr": "L"},
{"attribute_value": "Extra Small", "abbr": "XSL"}
]
},
{

View File

@@ -802,7 +802,7 @@ class StockEntry(StockController):
# item dict = { item_code: {qty, description, stock_uom} }
item_dict = get_bom_items_as_dict(self.bom_no, self.company, qty=qty,
fetch_exploded = self.use_multi_level_bom)
fetch_exploded = self.use_multi_level_bom, fetch_qty_in_stock_uom=False)
used_alternative_items = get_used_alternative_items(work_order = self.work_order)
for item in itervalues(item_dict):
@@ -1031,7 +1031,7 @@ class StockEntry(StockController):
se_child.item_code = item_dict[d].get('item_code') or cstr(d)
se_child.item_name = item_dict[d]["item_name"]
se_child.description = item_dict[d]["description"]
se_child.uom = stock_uom
se_child.uom = item_dict[d]["uom"] if item_dict[d].get("uom") else stock_uom
se_child.stock_uom = stock_uom
se_child.qty = flt(item_dict[d]["qty"], se_child.precision("qty"))
se_child.expense_account = item_dict[d].get("expense_account") or expense_account
@@ -1049,8 +1049,9 @@ class StockEntry(StockController):
se_child.t_warehouse = self.to_warehouse
# in stock uom
se_child.transfer_qty = flt(item_dict[d]["qty"], se_child.precision("qty"))
se_child.conversion_factor = 1.00
se_child.conversion_factor = flt(item_dict[d].get("conversion_factor")) or 1
se_child.transfer_qty = flt(item_dict[d]["qty"]*se_child.conversion_factor, se_child.precision("qty"))
# to be assigned for finished item
se_child.bom_no = bom_no

View File

@@ -51,6 +51,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "If blank, parent Warehouse Account or company default will be considered",
"fieldname": "warehouse_name",
"fieldtype": "Data",
"hidden": 0,
@@ -870,7 +871,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-08-29 06:26:48.647225",
"modified": "2018-08-29 06:26:49.647225",
"modified_by": "Administrator",
"module": "Stock",
"name": "Warehouse",

View File

@@ -606,28 +606,39 @@ def get_pos_profile_item_details(company, args, pos_profile=None, update_data=Fa
@frappe.whitelist()
def get_pos_profile(company, pos_profile=None, user=None):
if pos_profile:
return frappe.get_cached_doc('POS Profile', pos_profile)
if pos_profile: return frappe.get_cached_doc('POS Profile', pos_profile)
if not user:
user = frappe.session['user']
condition = "pfu.user = %(user)s AND pfu.default=1"
if user and company:
condition = "pfu.user = %(user)s AND pf.company = %(company)s AND pfu.default=1"
pos_profile = frappe.db.sql("""SELECT pf.*
FROM
`tabPOS Profile` pf LEFT JOIN `tabPOS Profile User` pfu
ON
pf.name = pfu.parent
WHERE
(
(pfu.user = %(user)s AND pf.company = %(company)s AND pfu.default=1)
OR (pfu.user = %(user)s AND pfu.default=1)
OR (ifnull(pfu.user, '') = '' AND pf.company = %(company)s)
) AND pf.disabled = 0
""", {
{cond} AND pf.disabled = 0
""".format(cond = condition), {
'user': user,
'company': company
}, as_dict=1)
if not pos_profile and company:
pos_profile = frappe.db.sql("""SELECT pf.*
FROM
`tabPOS Profile` pf LEFT JOIN `tabPOS Profile User` pfu
ON
pf.name = pfu.parent
WHERE
pf.company = %(company)s AND pf.disabled = 0
""", {
'company': company
}, as_dict=1)
return pos_profile and pos_profile[0] or None
def get_serial_nos_by_fifo(args, sales_order=None):

View File

@@ -60,7 +60,7 @@
{{doc.terms}}
</div>
<div class="cart-link">
<a href="#" onclick="show_terms();return false;">*{{ __("Terms and Conditions") }}</a>
<a href="#" onclick="show_terms();return false;">*{{ _("Terms and Conditions") }}</a>
</div>
{% endif %}

View File

@@ -32,7 +32,7 @@
<p class='text-muted'>Please update your GSTIN for us to issue correct tax invoice</p>
<form method='GET' action='/regional/india/update-gstin.html'>
<input type='hidden' value='{{ party.name }}' name='party'>
{% for address in party.__onload.addr_list %}
{% for address in party.get_onload('addr_list') %}
<div class='bordered' style='max-width: 300px; margin-bottom: 15px;'>
{{ address.display }}
<p><input type='text' class='form-control'