Merged develop in lms

This commit is contained in:
scmmishra 2019-04-09 11:39:12 +05:30
commit b7d9efe901
82 changed files with 1833 additions and 946 deletions

View File

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

View File

@ -0,0 +1,45 @@
frappe.dashboard_chart_sources["Account Balance Timeline"] = {
method_path: "erpnext.accounts.dashboard_chart_source.account_balance_timeline.account_balance_timeline.get",
filters: [
{
fieldname: "company",
label: __("Company"),
fieldtype: "Link",
options: "Company",
default: frappe.defaults.get_user_default("Company"),
reqd: 1
},
{
fieldname: "account",
label: __("Account"),
fieldtype: "Link",
options: "Account",
reqd: 1
},
{
fieldname: "timespan",
label: __("Period"),
fieldtype: "Select",
options: [
{value: "Last Year", label: __("Last Year")},
{value: "Last Quarter", label: __("Last Quarter")},
{value: "Last Month", label: __("Last Month")},
{value: "Last Week", label: __("Last Week")}
],
reqd: 1
},
{
fieldname: "timegrain",
label: __("Periodicity"),
fieldtype: "Select",
options: [
{value: "Quarterly", label: __("Quarterly")},
{value: "Monthly", label: __("Monthly")},
{value: "Weekly", label: __("Weekly")},
{value: "Daily", label: __("Daily")}
],
reqd: 1
},
],
is_time_series: true
};

View File

@ -0,0 +1,13 @@
{
"config": "{\n \"method_path\": \"erpnext.accounts.dashboard_chart_source.account_balance_timeline.account_balance_timeline.get\",\n\t\"filters\": [\n\t\t{\n\t\t\t\"fieldname\": \"company\",\n\t\t\t\"label\": \"Company\",\n\t\t\t\"fieldtype\": \"Link\",\n\t\t\t\"options\": \"Company\",\n\t\t\t\"reqd\": 1\n\t\t},\n\t\t{\n\t\t\t\"fieldname\": \"account\",\n\t\t\t\"label\": \"Account\",\n\t\t\t\"fieldtype\": \"Link\",\n\t\t\t\"options\": \"Account\",\n\t\t\t\"reqd\": 1\n\t\t},\n\t\t{\n\t\t\t\"fieldname\": \"timespan\",\n\t\t\t\"label\": \"Period\",\n\t\t\t\"fieldtype\": \"Select\",\n\t\t\t\"options\": [\n\t\t\t\t{\"value\": \"Last Year\", \"label\": \"Last Year\"},\n\t\t\t\t{\"value\": \"Last Quarter\", \"label\": \"Last Quarter\"},\n\t\t\t\t{\"value\": \"Last Month\", \"label\": \"Last Month\"},\n\t\t\t\t{\"value\": \"Last Week\", \"label\": \"Last Week\"}\n\t\t\t],\n\t\t\t\"reqd\": 1\n\t\t},\n\t\t{\n\t\t\t\"fieldname\": \"timegrain\",\n\t\t\t\"label\": \"Periodicity\",\n\t\t\t\"fieldtype\": \"Select\",\n\t\t\t\"options\": [\n\t\t\t\t{\"value\": \"Quarterly\", \"label\": \"Quarterly\"},\n\t\t\t\t{\"value\": \"Monthly\", \"label\": \"Monthly\"},\n\t\t\t\t{\"value\": \"Weekly\", \"label\": \"Weekly\"},\n\t\t\t\t{\"value\": \"Daily\", \"label\": \"Daily\"}\n\t\t\t],\n\t\t\t\"reqd\": 1\n\t\t}\n\t],\n\t\"is_time_series\": true\n}\n",
"creation": "2019-02-06 07:57:10.377718",
"docstatus": 0,
"doctype": "Dashboard Chart Source",
"idx": 0,
"modified": "2019-03-15 16:14:26.505986",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Account Balance Timeline",
"owner": "Administrator",
"source_name": "Account Balance Timeline"
}

View File

@ -0,0 +1,110 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
from itertools import groupby
from operator import itemgetter
import frappe
from frappe.core.page.dashboard.dashboard import cache_source
from frappe.utils import add_to_date, date_diff, getdate, nowdate
from erpnext.accounts.report.general_ledger.general_ledger import execute
from frappe.utils.nestedset import get_descendants_of
@frappe.whitelist()
@cache_source
def get(filters=None):
timespan = filters.get("timespan")
timegrain = filters.get("timegrain")
account = filters.get("account")
company = filters.get("company")
from_date = get_from_date_from_timespan(timespan)
to_date = nowdate()
# fetch dates to plot
dates = get_dates_from_timegrain(from_date, to_date, timegrain)
# get all the entries for this account and its descendants
gl_entries = get_gl_entries(account, to_date)
# compile balance values
result = build_result(account, dates, gl_entries)
return {
"labels": [r[0].strftime('%Y-%m-%d') for r in result],
"datasets": [{
"name": account,
"values": [r[1] for r in result]
}]
}
def build_result(account, dates, gl_entries):
result = [[getdate(date), 0.0] for date in dates]
root_type = frappe.db.get_value('Account', account, 'root_type')
# start with the first date
date_index = 0
# get balances in debit
for entry in gl_entries:
# entry date is after the current pointer, so move the pointer forward
while getdate(entry.posting_date) > result[date_index][0]:
date_index += 1
result[date_index][1] += entry.debit - entry.credit
# if account type is credit, switch balances
if root_type not in ('Asset', 'Expense'):
for r in result:
r[1] = -1 * r[1]
# for balance sheet accounts, the totals are cumulative
if root_type in ('Asset', 'Liability', 'Equity'):
for i, r in enumerate(result):
if i < 0:
r[1] = r[1] + result[i-1][1]
return result
def get_gl_entries(account, to_date):
child_accounts = get_descendants_of('Account', account, ignore_permissions=True)
child_accounts.append(account)
return frappe.db.get_all('GL Entry',
fields = ['posting_date', 'debit', 'credit'],
filters = [
dict(posting_date = ('<', to_date)),
dict(account = ('in', child_accounts))
],
order_by = 'posting_date asc')
def get_from_date_from_timespan(timespan):
days = months = years = 0
if "Last Week" == timespan:
days = -7
if "Last Month" == timespan:
months = -1
elif "Last Quarter" == timespan:
months = -3
elif "Last Year" == timespan:
years = -1
return add_to_date(None, years=years, months=months, days=days,
as_string=True, as_datetime=True)
def get_dates_from_timegrain(from_date, to_date, timegrain):
days = months = years = 0
if "Daily" == timegrain:
days = 1
elif "Weekly" == timegrain:
days = 7
elif "Monthly" == timegrain:
months = 1
elif "Quarterly" == timegrain:
months = 3
dates = [from_date]
while dates[-1] <= to_date:
dates.append(add_to_date(dates[-1], years=years, months=months, days=days))
return dates

View File

@ -2,9 +2,9 @@ from __future__ import unicode_literals
import frappe import frappe
from frappe import _ 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.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): def validate_service_stop_date(doc):
''' Validates service_stop_date for Purchase Invoice and Sales Invoice ''' ''' 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))) 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): 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 # check for the purchase invoice for which GL entries has to be done
invoices = frappe.db.sql_list(''' 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 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 each invoice, book deferred expense
for invoice in invoices: for invoice in invoices:
doc = frappe.get_doc("Purchase Invoice", invoice) 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): 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 # check for the sales invoice for which GL entries has to be done
invoices = frappe.db.sql_list(''' 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 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: for invoice in invoices:
doc = frappe.get_doc("Sales Invoice", invoice) 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" 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(''' prev_gl_entry = frappe.db.sql('''
select name, posting_date from `tabGL Entry` where company=%s and account=%s and 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 order by posting_date desc limit 1
''', (doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name), as_dict=True) ''', (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: if prev_gl_entry:
booking_start_date = item.service_start_date start_date = getdate(add_days(prev_gl_entry[0].posting_date, 1))
elif prev_gl_entry: else:
booking_start_date = getdate(add_days(prev_gl_entry[0].posting_date, 1)) start_date = item.service_start_date
skip = True if booking_start_date > booking_end_date else False
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): if end_date > getdate(posting_date):
account_currency = get_account_currency(item.expense_account) 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": if doc.doctype == "Sales Invoice":
total_credit_debit, total_credit_debit_currency = "debit", "debit_in_account_currency" total_credit_debit, total_credit_debit_currency = "debit", "debit_in_account_currency"
deferred_account = "deferred_revenue_account" 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 return amount, base_amount
def book_deferred_income_or_expense(doc, start_date=None, end_date=None): def book_deferred_income_or_expense(doc, posting_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
enable_check = "enable_deferred_revenue" \ enable_check = "enable_deferred_revenue" \
if doc.doctype=="Sales Invoice" else "enable_deferred_expense" if doc.doctype=="Sales Invoice" else "enable_deferred_expense"
gl_entries = [] def _book_deferred_revenue_or_expense(item):
for item in doc.get('items'): start_date, end_date, last_gl_entry = get_booking_dates(doc, item, posting_date=posting_date)
if not item.get(enable_check): continue if not (start_date and end_date): return
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
account_currency = get_account_currency(item.expense_account) 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": if doc.doctype == "Sales Invoice":
against, project = doc.customer, doc.project against, project = doc.customer, doc.project
credit_account, debit_account = item.income_account, item.deferred_revenue_account 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 against, project = doc.supplier, item.project
credit_account, debit_account = item.deferred_expense_account, item.expense_account credit_account, debit_account = item.deferred_expense_account, item.expense_account
# GL Entry for crediting the amount in the deferred expense total_days = date_diff(item.service_end_date, item.service_start_date) + 1
gl_entries.append( total_booking_days = date_diff(end_date, start_date) + 1
doc.get_gl_dict({
"account": credit_account, amount, base_amount = calculate_amount(doc, item, last_gl_entry,
"against": against, total_days, total_booking_days, account_currency)
"credit": base_amount,
"credit_in_account_currency": amount, make_gl_entries(doc, credit_account, debit_account, against,
"cost_center": item.cost_center, amount, base_amount, end_date, project, account_currency, item.cost_center, item.name)
"voucher_detail_no": item.name,
'posting_date': booking_end_date, if getdate(end_date) < getdate(posting_date) and not last_gl_entry:
'project': project _book_deferred_revenue_or_expense(item)
}, account_currency)
)
# GL Entry to debit the amount from the expense for item in doc.get('items'):
gl_entries.append( if item.get(enable_check):
doc.get_gl_dict({ _book_deferred_revenue_or_expense(item)
"account": debit_account,
"against": against, def make_gl_entries(doc, credit_account, debit_account, against,
"debit": base_amount, amount, base_amount, posting_date, project, account_currency, cost_center, voucher_detail_no):
"debit_in_account_currency": amount, # GL Entry for crediting the amount in the deferred expense
"cost_center": item.cost_center, from erpnext.accounts.general_ledger import make_gl_entries
"voucher_detail_no": item.name,
'posting_date': booking_end_date, gl_entries = []
'project': project gl_entries.append(
}, account_currency) 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: if gl_entries:
try: try:
make_gl_entries(gl_entries, cancel=(doc.docstatus == 2), merge_entries=True) make_gl_entries(gl_entries, cancel=(doc.docstatus == 2), merge_entries=True)
frappe.db.commit() frappe.db.commit()
except: except:
frappe.db.rollback() 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) ancestors = get_root_company(self.company)
if ancestors: 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])) frappe.throw(_("Please add the account to root level Company - %s" % ancestors[0]))
else: else:
descendants = get_descendants_of('Company', self.company) descendants = get_descendants_of('Company', self.company)
@ -110,6 +112,8 @@ class Account(NestedSet):
["company", "name"], as_dict=True): ["company", "name"], as_dict=True):
acc_name_map[d["company"]] = d["name"] acc_name_map[d["company"]] = d["name"]
if not acc_name_map: return
for company in descendants: for company in descendants:
doc = frappe.copy_doc(self) doc = frappe.copy_doc(self)
doc.flags.ignore_root_company_validation = True doc.flags.ignore_root_company_validation = True

View File

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

View File

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

View File

@ -23,6 +23,7 @@
"columns": 0, "columns": 0,
"default": "1", "default": "1",
"description": "If enabled, the system will post accounting entries for inventory automatically.", "description": "If enabled, the system will post accounting entries for inventory automatically.",
"fetch_if_empty": 0,
"fieldname": "auto_accounting_for_stock", "fieldname": "auto_accounting_for_stock",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 1, "hidden": 1,
@ -55,6 +56,7 @@
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"description": "Accounting entry frozen up to this date, nobody can do / modify entry except role specified below.", "description": "Accounting entry frozen up to this date, nobody can do / modify entry except role specified below.",
"fetch_if_empty": 0,
"fieldname": "acc_frozen_upto", "fieldname": "acc_frozen_upto",
"fieldtype": "Date", "fieldtype": "Date",
"hidden": 0, "hidden": 0,
@ -87,6 +89,7 @@
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"description": "Users with this role are allowed to set frozen accounts and create / modify accounting entries against frozen accounts", "description": "Users with this role are allowed to set frozen accounts and create / modify accounting entries against frozen accounts",
"fetch_if_empty": 0,
"fieldname": "frozen_accounts_modifier", "fieldname": "frozen_accounts_modifier",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0, "hidden": 0,
@ -121,6 +124,7 @@
"columns": 0, "columns": 0,
"default": "Billing Address", "default": "Billing Address",
"description": "Address used to determine Tax Category in transactions.", "description": "Address used to determine Tax Category in transactions.",
"fetch_if_empty": 0,
"fieldname": "determine_address_tax_category_from", "fieldname": "determine_address_tax_category_from",
"fieldtype": "Select", "fieldtype": "Select",
"hidden": 0, "hidden": 0,
@ -154,6 +158,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_4", "fieldname": "column_break_4",
"fieldtype": "Column Break", "fieldtype": "Column Break",
"hidden": 0, "hidden": 0,
@ -186,6 +191,7 @@
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"description": "Role that is allowed to submit transactions that exceed credit limits set.", "description": "Role that is allowed to submit transactions that exceed credit limits set.",
"fetch_if_empty": 0,
"fieldname": "credit_controller", "fieldname": "credit_controller",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0, "hidden": 0,
@ -218,6 +224,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_if_empty": 0,
"fieldname": "check_supplier_invoice_uniqueness", "fieldname": "check_supplier_invoice_uniqueness",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 0, "hidden": 0,
@ -250,6 +257,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_if_empty": 0,
"fieldname": "make_payment_via_journal_entry", "fieldname": "make_payment_via_journal_entry",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 0, "hidden": 0,
@ -283,6 +291,7 @@
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"default": "1", "default": "1",
"fetch_if_empty": 0,
"fieldname": "unlink_payment_on_cancellation_of_invoice", "fieldname": "unlink_payment_on_cancellation_of_invoice",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 0, "hidden": 0,
@ -316,6 +325,41 @@
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"default": "1", "default": "1",
"fetch_if_empty": 0,
"fieldname": "unlink_advance_payment_on_cancelation_of_order",
"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": "Unlink Advance Payment on Cancelation of Order",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "1",
"fetch_if_empty": 0,
"fieldname": "book_asset_depreciation_entry_automatically", "fieldname": "book_asset_depreciation_entry_automatically",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 0, "hidden": 0,
@ -348,6 +392,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_if_empty": 0,
"fieldname": "allow_cost_center_in_entry_of_bs_account", "fieldname": "allow_cost_center_in_entry_of_bs_account",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 0, "hidden": 0,
@ -381,6 +426,7 @@
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"default": "1", "default": "1",
"fetch_if_empty": 0,
"fieldname": "add_taxes_from_item_tax_template", "fieldname": "add_taxes_from_item_tax_template",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 0, "hidden": 0,
@ -413,6 +459,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_if_empty": 0,
"fieldname": "print_settings", "fieldname": "print_settings",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 0, "hidden": 0,
@ -445,6 +492,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_if_empty": 0,
"fieldname": "show_inclusive_tax_in_print", "fieldname": "show_inclusive_tax_in_print",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 0, "hidden": 0,
@ -477,6 +525,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_12", "fieldname": "column_break_12",
"fieldtype": "Column Break", "fieldtype": "Column Break",
"hidden": 0, "hidden": 0,
@ -508,6 +557,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_if_empty": 0,
"fieldname": "show_payment_schedule_in_print", "fieldname": "show_payment_schedule_in_print",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 0, "hidden": 0,
@ -540,6 +590,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_if_empty": 0,
"fieldname": "currency_exchange_section", "fieldname": "currency_exchange_section",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 0, "hidden": 0,
@ -573,6 +624,7 @@
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"default": "1", "default": "1",
"fetch_if_empty": 0,
"fieldname": "allow_stale", "fieldname": "allow_stale",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 0, "hidden": 0,
@ -607,6 +659,7 @@
"columns": 0, "columns": 0,
"default": "1", "default": "1",
"depends_on": "eval:doc.allow_stale==0", "depends_on": "eval:doc.allow_stale==0",
"fetch_if_empty": 0,
"fieldname": "stale_days", "fieldname": "stale_days",
"fieldtype": "Int", "fieldtype": "Int",
"hidden": 0, "hidden": 0,
@ -639,6 +692,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_if_empty": 0,
"fieldname": "report_settings_sb", "fieldname": "report_settings_sb",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 0, "hidden": 0,
@ -673,6 +727,7 @@
"columns": 0, "columns": 0,
"default": "0", "default": "0",
"description": "Only select if you have setup Cash Flow Mapper documents", "description": "Only select if you have setup Cash Flow Mapper documents",
"fetch_if_empty": 0,
"fieldname": "use_custom_cash_flow", "fieldname": "use_custom_cash_flow",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 0, "hidden": 0,
@ -700,17 +755,15 @@
} }
], ],
"has_web_view": 0, "has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0, "hide_toolbar": 0,
"icon": "icon-cog", "icon": "icon-cog",
"idx": 1, "idx": 1,
"image_view": 0,
"in_create": 0, "in_create": 0,
"is_submittable": 0, "is_submittable": 0,
"issingle": 1, "issingle": 1,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2019-01-07 00:42:34.510150", "modified": "2019-04-06 12:28:43.026250",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Accounts Settings", "name": "Accounts Settings",
@ -776,10 +829,9 @@
], ],
"quick_entry": 1, "quick_entry": 1,
"read_only": 0, "read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0, "show_name_in_global_search": 0,
"sort_order": "ASC", "sort_order": "ASC",
"track_changes": 1, "track_changes": 1,
"track_seen": 0, "track_seen": 0,
"track_views": 0 "track_views": 0
} }

View File

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

View File

@ -52,11 +52,6 @@ class JournalEntry(AccountsController):
self.update_loan() self.update_loan()
self.update_inter_company_jv() 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): def get_title(self):
return self.pay_to_recd_from or self.accounts[0].account return self.pay_to_recd_from or self.accounts[0].account

View File

@ -70,11 +70,6 @@ class PaymentEntry(AccountsController):
self.update_advance_paid() self.update_advance_paid()
self.update_expense_claim() 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): def on_cancel(self):
self.setup_party_account_field() self.setup_party_account_field()
@ -754,7 +749,7 @@ def get_outstanding_on_journal_entry(name):
@frappe.whitelist() @frappe.whitelist()
def get_reference_details(reference_doctype, reference_name, party_account_currency): 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) ref_doc = frappe.get_doc(reference_doctype, reference_name)
company_currency = ref_doc.get("company_currency") or erpnext.get_company_currency(ref_doc.company) 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"): if reference_doctype in ("Sales Invoice", "Purchase Invoice"):
outstanding_amount = ref_doc.get("outstanding_amount") outstanding_amount = ref_doc.get("outstanding_amount")
bill_no = ref_doc.get("bill_no")
elif reference_doctype == "Expense Claim": elif reference_doctype == "Expense Claim":
outstanding_amount = flt(ref_doc.get("total_sanctioned_amount")) \ outstanding_amount = flt(ref_doc.get("total_sanctioned_amount")) \
- flt(ref_doc.get("total_amount+reimbursed")) - flt(ref_doc.get("total_advance_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"), "due_date": ref_doc.get("due_date"),
"total_amount": total_amount, "total_amount": total_amount,
"outstanding_amount": outstanding_amount, "outstanding_amount": outstanding_amount,
"exchange_rate": exchange_rate "exchange_rate": exchange_rate,
"bill_no": bill_no
}) })

View File

@ -155,7 +155,6 @@ def pos_profile_query(doctype, txt, searchfield, start, page_len, filters):
def set_default_profile(pos_profile, company): def set_default_profile(pos_profile, company):
modified = now() modified = now()
user = frappe.session.user user = frappe.session.user
company = frappe.db.escape(company)
if pos_profile and company: if pos_profile and company:
frappe.db.sql(""" update `tabPOS Profile User` pfu, `tabPOS Profile` pf frappe.db.sql(""" update `tabPOS Profile User` pfu, `tabPOS Profile` pf

View File

@ -119,7 +119,7 @@ class PricingRule(Document):
#-------------------------------------------------------------------------------- #--------------------------------------------------------------------------------
@frappe.whitelist() @frappe.whitelist()
def apply_pricing_rule(args): def apply_pricing_rule(args, doc=None):
""" """
args = { args = {
"items": [{"doctype": "", "name": "", "item_code": "", "brand": "", "item_group": ""}, ...], "items": [{"doctype": "", "name": "", "item_code": "", "brand": "", "item_group": ""}, ...],
@ -139,6 +139,7 @@ def apply_pricing_rule(args):
"ignore_pricing_rule": "something" "ignore_pricing_rule": "something"
} }
""" """
if isinstance(args, string_types): if isinstance(args, string_types):
args = json.loads(args) args = json.loads(args)
@ -161,10 +162,11 @@ def apply_pricing_rule(args):
for item in item_list: for item in item_list:
args_copy = copy.deepcopy(args) args_copy = copy.deepcopy(args)
args_copy.update(item) args_copy.update(item)
data = get_pricing_rule_for_item(args_copy, item.get('price_list_rate')) data = get_pricing_rule_for_item(args_copy, item.get('price_list_rate'), doc=doc)
out.append(data) out.append(data)
if set_serial_nos_based_on_fifo and not args.get('is_return'): if not item.get("serial_no") and set_serial_nos_based_on_fifo and not args.get('is_return'):
out.append(get_serial_no_for_item(args_copy)) out[0].update(get_serial_no_for_item(args_copy))
return out return out
def get_serial_no_for_item(args): def get_serial_no_for_item(args):
@ -182,6 +184,12 @@ def get_serial_no_for_item(args):
def get_pricing_rule_for_item(args, price_list_rate=0, doc=None): def get_pricing_rule_for_item(args, price_list_rate=0, doc=None):
from erpnext.accounts.doctype.pricing_rule.utils import get_pricing_rules from erpnext.accounts.doctype.pricing_rule.utils import get_pricing_rules
if isinstance(doc, string_types):
doc = json.loads(doc)
if doc:
doc = frappe.get_doc(doc)
if (args.get('is_free_item') or if (args.get('is_free_item') or
args.get("parenttype") == "Material Request"): return {} args.get("parenttype") == "Material Request"): return {}
@ -227,11 +235,11 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None):
if pricing_rules: if pricing_rules:
rules = [] rules = []
item_details.discount_percentage = 0
item_details.discount_amount = 0
for pricing_rule in pricing_rules: for pricing_rule in pricing_rules:
if not pricing_rule or pricing_rule.get('suggestion'): continue if not pricing_rule or pricing_rule.get('suggestion'): continue
item_details.validate_applied_rule = pricing_rule.get("validate_applied_rule", 0)
rules.append(get_pricing_rule_details(args, pricing_rule)) rules.append(get_pricing_rule_details(args, pricing_rule))
if pricing_rule.mixed_conditions or pricing_rule.apply_rule_on_other: if pricing_rule.mixed_conditions or pricing_rule.apply_rule_on_other:
continue continue
@ -243,8 +251,8 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None):
item_details.has_pricing_rule = 1 item_details.has_pricing_rule = 1
# if discount is applied on the rate and not on price list rate # if discount is applied on the rate and not on price list rate
if price_list_rate: # if price_list_rate:
set_discount_amount(price_list_rate, item_details) # set_discount_amount(price_list_rate, item_details)
item_details.pricing_rules = ','.join([d.pricing_rule for d in rules]) item_details.pricing_rules = ','.join([d.pricing_rule for d in rules])
@ -264,7 +272,7 @@ def get_pricing_rule_details(args, pricing_rule):
'pricing_rule': pricing_rule.name, 'pricing_rule': pricing_rule.name,
'rate_or_discount': pricing_rule.rate_or_discount, 'rate_or_discount': pricing_rule.rate_or_discount,
'margin_type': pricing_rule.margin_type, 'margin_type': pricing_rule.margin_type,
'item_code': pricing_rule.item_code, 'item_code': pricing_rule.item_code or args.get("item_code"),
'child_docname': args.get('child_docname') 'child_docname': args.get('child_docname')
}) })
@ -296,6 +304,9 @@ def apply_price_discount_pricing_rule(pricing_rule, item_details, args):
discount_field = "{0}_on_rate".format(field) discount_field = "{0}_on_rate".format(field)
item_details[discount_field].append(pricing_rule.get(field, 0)) item_details[discount_field].append(pricing_rule.get(field, 0))
else: else:
if field not in item_details:
item_details.setdefault(field, 0)
item_details[field] += (pricing_rule.get(field, 0) item_details[field] += (pricing_rule.get(field, 0)
if pricing_rule else args.get(field, 0)) if pricing_rule else args.get(field, 0))
@ -308,6 +319,7 @@ def set_discount_amount(rate, item_details):
item_details.rate = rate item_details.rate = rate
def remove_pricing_rule_for_item(pricing_rules, item_details, item_code=None): def remove_pricing_rule_for_item(pricing_rules, item_details, item_code=None):
from erpnext.accounts.doctype.pricing_rule.utils import get_apply_on_and_items
for d in pricing_rules.split(','): for d in pricing_rules.split(','):
if not d: continue if not d: continue
pricing_rule = frappe.get_doc('Pricing Rule', d) pricing_rule = frappe.get_doc('Pricing Rule', d)
@ -327,6 +339,11 @@ def remove_pricing_rule_for_item(pricing_rules, item_details, item_code=None):
item_details.remove_free_item = (item_code if pricing_rule.get('same_item') item_details.remove_free_item = (item_code if pricing_rule.get('same_item')
else pricing_rule.get('free_item')) else pricing_rule.get('free_item'))
if pricing_rule.get("mixed_conditions") or pricing_rule.get("apply_rule_on_other"):
apply_on, items = get_apply_on_and_items(pricing_rule, item_details)
item_details.apply_on = apply_on
item_details.applied_on_items = ','.join(items)
item_details.pricing_rules = '' item_details.pricing_rules = ''
return item_details return item_details
@ -368,35 +385,6 @@ def make_pricing_rule(doctype, docname):
return doc return doc
@frappe.whitelist()
def get_free_items(pricing_rules, item_row):
if isinstance(item_row, string_types):
item_row = json.loads(item_row)
free_items = []
pricing_rules = list(set(pricing_rules.split(',')))
for d in pricing_rules:
pr_doc = frappe.get_doc('Pricing Rule', d)
if pr_doc.price_or_product_discount == 'Product':
item = (item_row.get('item_code') if pr_doc.same_item
else pr_doc.free_item)
if not item: return free_items
doc = frappe.get_doc('Item', item)
free_items.append({
'item_code': item,
'item_name': doc.item_name,
'description': doc.description,
'qty': pr_doc.free_qty,
'uom': pr_doc.free_item_uom,
'rate': pr_doc.free_item_rate or 0,
'is_free_item': 1
})
return free_items
def get_item_uoms(doctype, txt, searchfield, start, page_len, filters): def get_item_uoms(doctype, txt, searchfield, start, page_len, filters):
items = [filters.get('value')] items = [filters.get('value')]
if filters.get('apply_on') != 'Item Code': if filters.get('apply_on') != 'Item Code':

View File

@ -4,490 +4,531 @@
# For license information, please see license.txt # For license information, please see license.txt
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe, copy, json
import json
import copy
from frappe import throw, _ from frappe import throw, _
from six import string_types
from frappe.utils import flt, cint, get_datetime from frappe.utils import flt, cint, get_datetime
from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses
from erpnext.stock.get_item_details import get_conversion_factor from erpnext.stock.get_item_details import get_conversion_factor
class MultiplePricingRuleConflict(frappe.ValidationError): pass class MultiplePricingRuleConflict(frappe.ValidationError): pass
apply_on_table = {
'Item Code': 'items',
'Item Group': 'item_groups',
'Brand': 'brands'
}
def get_pricing_rules(args, doc=None): def get_pricing_rules(args, doc=None):
pricing_rules = [] pricing_rules = []
values = {} values = {}
for apply_on in ['Item Code', 'Item Group', 'Brand']: for apply_on in ['Item Code', 'Item Group', 'Brand']:
pricing_rules.extend(_get_pricing_rules(apply_on, args, values)) pricing_rules.extend(_get_pricing_rules(apply_on, args, values))
if pricing_rules and not apply_multiple_pricing_rules(pricing_rules): if pricing_rules and not apply_multiple_pricing_rules(pricing_rules):
break break
rules = [] rules = []
if not pricing_rules: return [] if not pricing_rules: return []
if apply_multiple_pricing_rules(pricing_rules): if apply_multiple_pricing_rules(pricing_rules):
for pricing_rule in pricing_rules: for pricing_rule in pricing_rules:
pricing_rule = filter_pricing_rules(args, pricing_rule, doc) pricing_rule = filter_pricing_rules(args, pricing_rule, doc)
if pricing_rule: if pricing_rule:
rules.append(pricing_rule) rules.append(pricing_rule)
else: else:
rules.append(filter_pricing_rules(args, pricing_rules, doc)) pricing_rule = filter_pricing_rules(args, pricing_rules, doc)
if pricing_rule:
rules.append(pricing_rule)
return rules return rules
def _get_pricing_rules(apply_on, args, values): def _get_pricing_rules(apply_on, args, values):
apply_on_field = frappe.scrub(apply_on) apply_on_field = frappe.scrub(apply_on)
if not args.get(apply_on_field): return [] if not args.get(apply_on_field): return []
child_doc = '`tabPricing Rule {0}`'.format(apply_on) child_doc = '`tabPricing Rule {0}`'.format(apply_on)
conditions = item_variant_condition = item_conditions = "" conditions = item_variant_condition = item_conditions = ""
values[apply_on_field] = args.get(apply_on_field) values[apply_on_field] = args.get(apply_on_field)
if apply_on_field in ['item_code', 'brand']: if apply_on_field in ['item_code', 'brand']:
item_conditions = "{child_doc}.{apply_on_field}= %({apply_on_field})s".format(child_doc=child_doc, item_conditions = "{child_doc}.{apply_on_field}= %({apply_on_field})s".format(child_doc=child_doc,
apply_on_field = apply_on_field) apply_on_field = apply_on_field)
if apply_on_field == 'item_code': if apply_on_field == 'item_code':
if "variant_of" not in args: if "variant_of" not in args:
args.variant_of = frappe.get_cached_value("Item", args.item_code, "variant_of") args.variant_of = frappe.get_cached_value("Item", args.item_code, "variant_of")
if args.variant_of: if args.variant_of:
item_variant_condition = ' or {child_doc}.item_code=%(variant_of)s '.format(child_doc=child_doc) item_variant_condition = ' or {child_doc}.item_code=%(variant_of)s '.format(child_doc=child_doc)
values['variant_of'] = args.variant_of values['variant_of'] = args.variant_of
elif apply_on_field == 'item_group': elif apply_on_field == 'item_group':
item_conditions = _get_tree_conditions(args, "Item Group", child_doc, False) item_conditions = _get_tree_conditions(args, "Item Group", child_doc, False)
conditions += get_other_conditions(conditions, values, args) conditions += get_other_conditions(conditions, values, args)
warehouse_conditions = _get_tree_conditions(args, "Warehouse", '`tabPricing Rule`') warehouse_conditions = _get_tree_conditions(args, "Warehouse", '`tabPricing Rule`')
if warehouse_conditions: if warehouse_conditions:
warehouse_conditions = " and {0}".format(warehouse_conditions) warehouse_conditions = " and {0}".format(warehouse_conditions)
if not args.price_list: args.price_list = None if not args.price_list: args.price_list = None
conditions += " and ifnull(`tabPricing Rule`.for_price_list, '') in (%(price_list)s, '')" conditions += " and ifnull(`tabPricing Rule`.for_price_list, '') in (%(price_list)s, '')"
values["price_list"] = args.get("price_list") values["price_list"] = args.get("price_list")
pricing_rules = frappe.db.sql("""select `tabPricing Rule`.*, pricing_rules = frappe.db.sql("""select `tabPricing Rule`.*,
{child_doc}.{apply_on_field}, {child_doc}.uom {child_doc}.{apply_on_field}, {child_doc}.uom
from `tabPricing Rule`, {child_doc} from `tabPricing Rule`, {child_doc}
where ({item_conditions} or (`tabPricing Rule`.apply_rule_on_other is not null where ({item_conditions} or (`tabPricing Rule`.apply_rule_on_other is not null
and `tabPricing Rule`.{apply_on_other_field}=%({apply_on_field})s) {item_variant_condition}) and `tabPricing Rule`.{apply_on_other_field}=%({apply_on_field})s) {item_variant_condition})
and {child_doc}.parent = `tabPricing Rule`.name and {child_doc}.parent = `tabPricing Rule`.name
and `tabPricing Rule`.disable = 0 and and `tabPricing Rule`.disable = 0 and
`tabPricing Rule`.{transaction_type} = 1 {warehouse_cond} {conditions} `tabPricing Rule`.{transaction_type} = 1 {warehouse_cond} {conditions}
order by `tabPricing Rule`.priority desc, order by `tabPricing Rule`.priority desc,
`tabPricing Rule`.name desc""".format( `tabPricing Rule`.name desc""".format(
child_doc = child_doc, child_doc = child_doc,
apply_on_field = apply_on_field, apply_on_field = apply_on_field,
item_conditions = item_conditions, item_conditions = item_conditions,
item_variant_condition = item_variant_condition, item_variant_condition = item_variant_condition,
transaction_type = args.transaction_type, transaction_type = args.transaction_type,
warehouse_cond = warehouse_conditions, warehouse_cond = warehouse_conditions,
apply_on_other_field = "other_{0}".format(apply_on_field), apply_on_other_field = "other_{0}".format(apply_on_field),
conditions = conditions), values, as_dict=1) or [] conditions = conditions), values, as_dict=1) or []
return pricing_rules return pricing_rules
def apply_multiple_pricing_rules(pricing_rules): def apply_multiple_pricing_rules(pricing_rules):
apply_multiple_rule = [d.apply_multiple_pricing_rules apply_multiple_rule = [d.apply_multiple_pricing_rules
for d in pricing_rules if d.apply_multiple_pricing_rules] for d in pricing_rules if d.apply_multiple_pricing_rules]
if not apply_multiple_rule: return False if not apply_multiple_rule: return False
if (apply_multiple_rule if (apply_multiple_rule
and len(apply_multiple_rule) == len(pricing_rules)): and len(apply_multiple_rule) == len(pricing_rules)):
return True return True
def _get_tree_conditions(args, parenttype, table, allow_blank=True): def _get_tree_conditions(args, parenttype, table, allow_blank=True):
field = frappe.scrub(parenttype) field = frappe.scrub(parenttype)
condition = "" condition = ""
if args.get(field): if args.get(field):
if not frappe.flags.tree_conditions: if not frappe.flags.tree_conditions:
frappe.flags.tree_conditions = {} frappe.flags.tree_conditions = {}
key = (parenttype, args.get(field)) key = (parenttype, args.get(field))
if key in frappe.flags.tree_conditions: if key in frappe.flags.tree_conditions:
return frappe.flags.tree_conditions[key] return frappe.flags.tree_conditions[key]
try: try:
lft, rgt = frappe.db.get_value(parenttype, args.get(field), ["lft", "rgt"]) lft, rgt = frappe.db.get_value(parenttype, args.get(field), ["lft", "rgt"])
except TypeError: except TypeError:
frappe.throw(_("Invalid {0}").format(args.get(field))) frappe.throw(_("Invalid {0}").format(args.get(field)))
parent_groups = frappe.db.sql_list("""select name from `tab%s` parent_groups = frappe.db.sql_list("""select name from `tab%s`
where lft<=%s and rgt>=%s""" % (parenttype, '%s', '%s'), (lft, rgt)) where lft<=%s and rgt>=%s""" % (parenttype, '%s', '%s'), (lft, rgt))
if parent_groups: if parent_groups:
if allow_blank: parent_groups.append('') if allow_blank: parent_groups.append('')
condition = "ifnull({table}.{field}, '') in ({parent_groups})".format( condition = "ifnull({table}.{field}, '') in ({parent_groups})".format(
table=table, table=table,
field=field, field=field,
parent_groups=", ".join([frappe.db.escape(d) for d in parent_groups]) parent_groups=", ".join([frappe.db.escape(d) for d in parent_groups])
) )
frappe.flags.tree_conditions[key] = condition frappe.flags.tree_conditions[key] = condition
return condition return condition
def get_other_conditions(conditions, values, args): def get_other_conditions(conditions, values, args):
for field in ["company", "customer", "supplier", "campaign", "sales_partner"]: for field in ["company", "customer", "supplier", "campaign", "sales_partner"]:
if args.get(field): if args.get(field):
conditions += " and ifnull(`tabPricing Rule`.{0}, '') in (%({1})s, '')".format(field, field) conditions += " and ifnull(`tabPricing Rule`.{0}, '') in (%({1})s, '')".format(field, field)
values[field] = args.get(field) values[field] = args.get(field)
else: else:
conditions += " and ifnull(`tabPricing Rule`.{0}, '') = ''".format(field) conditions += " and ifnull(`tabPricing Rule`.{0}, '') = ''".format(field)
for parenttype in ["Customer Group", "Territory", "Supplier Group"]: for parenttype in ["Customer Group", "Territory", "Supplier Group"]:
group_condition = _get_tree_conditions(args, parenttype, '`tabPricing Rule`') group_condition = _get_tree_conditions(args, parenttype, '`tabPricing Rule`')
if group_condition: if group_condition:
conditions += " and " + group_condition conditions += " and " + group_condition
if args.get("transaction_date"): if args.get("transaction_date"):
conditions += """ and %(transaction_date)s between ifnull(`tabPricing Rule`.valid_from, '2000-01-01') conditions += """ and %(transaction_date)s between ifnull(`tabPricing Rule`.valid_from, '2000-01-01')
and ifnull(`tabPricing Rule`.valid_upto, '2500-12-31')""" and ifnull(`tabPricing Rule`.valid_upto, '2500-12-31')"""
values['transaction_date'] = args.get('transaction_date') values['transaction_date'] = args.get('transaction_date')
return conditions return conditions
def filter_pricing_rules(args, pricing_rules, doc=None): def filter_pricing_rules(args, pricing_rules, doc=None):
if not isinstance(pricing_rules, list): if not isinstance(pricing_rules, list):
pricing_rules = [pricing_rules] pricing_rules = [pricing_rules]
original_pricing_rule = copy.copy(pricing_rules) original_pricing_rule = copy.copy(pricing_rules)
# filter for qty # filter for qty
if pricing_rules: if pricing_rules:
stock_qty = flt(args.get('stock_qty')) stock_qty = flt(args.get('stock_qty'))
amount = flt(args.get('price_list_rate')) * flt(args.get('qty')) amount = flt(args.get('price_list_rate')) * flt(args.get('qty'))
if pricing_rules[0].apply_rule_on_other: if pricing_rules[0].apply_rule_on_other:
field = frappe.scrub(pricing_rules[0].apply_rule_on_other) field = frappe.scrub(pricing_rules[0].apply_rule_on_other)
if (field and pricing_rules[0].get('other_' + field) != args.get(field)): return if (field and pricing_rules[0].get('other_' + field) != args.get(field)): return
pr_doc = frappe.get_doc('Pricing Rule', pricing_rules[0].name) pr_doc = frappe.get_doc('Pricing Rule', pricing_rules[0].name)
if pricing_rules[0].mixed_conditions and doc: if pricing_rules[0].mixed_conditions and doc:
stock_qty, amount = get_qty_and_rate_for_mixed_conditions(doc, pr_doc) stock_qty, amount = get_qty_and_rate_for_mixed_conditions(doc, pr_doc, args)
elif pricing_rules[0].is_cumulative: elif pricing_rules[0].is_cumulative:
items = [args.get(frappe.scrub(pr_doc.get('apply_on')))] items = [args.get(frappe.scrub(pr_doc.get('apply_on')))]
data = get_qty_amount_data_for_cumulative(pr_doc, args, items) data = get_qty_amount_data_for_cumulative(pr_doc, args, items)
if data: if data:
stock_qty += data[0] stock_qty += data[0]
amount += data[1] amount += data[1]
if pricing_rules[0].apply_rule_on_other and not pricing_rules[0].mixed_conditions and doc: if pricing_rules[0].apply_rule_on_other and not pricing_rules[0].mixed_conditions and doc:
pricing_rules = get_qty_and_rate_for_other_item(doc, pr_doc, pricing_rules) or [] pricing_rules = get_qty_and_rate_for_other_item(doc, pr_doc, pricing_rules) or []
else: else:
pricing_rules = filter_pricing_rules_for_qty_amount(stock_qty, amount, pricing_rules, args) pricing_rules = filter_pricing_rules_for_qty_amount(stock_qty, amount, pricing_rules, args)
if not pricing_rules: if not pricing_rules:
for d in original_pricing_rule: for d in original_pricing_rule:
if not d.threshold_percentage: continue if not d.threshold_percentage: continue
msg = validate_quantity_and_amount_for_suggestion(d, stock_qty, msg = validate_quantity_and_amount_for_suggestion(d, stock_qty,
amount, args.get('item_code'), args.get('transaction_type')) amount, args.get('item_code'), args.get('transaction_type'))
if msg: if msg:
return {'suggestion': msg, 'item_code': args.get('item_code')} return {'suggestion': msg, 'item_code': args.get('item_code')}
# add variant_of property in pricing rule # add variant_of property in pricing rule
for p in pricing_rules: for p in pricing_rules:
if p.item_code and args.variant_of: if p.item_code and args.variant_of:
p.variant_of = args.variant_of p.variant_of = args.variant_of
else: else:
p.variant_of = None p.variant_of = None
# find pricing rule with highest priority # find pricing rule with highest priority
if pricing_rules: if pricing_rules:
max_priority = max([cint(p.priority) for p in pricing_rules]) max_priority = max([cint(p.priority) for p in pricing_rules])
if max_priority: if max_priority:
pricing_rules = list(filter(lambda x: cint(x.priority)==max_priority, pricing_rules)) pricing_rules = list(filter(lambda x: cint(x.priority)==max_priority, pricing_rules))
# apply internal priority # apply internal priority
all_fields = ["item_code", "item_group", "brand", "customer", "customer_group", "territory", all_fields = ["item_code", "item_group", "brand", "customer", "customer_group", "territory",
"supplier", "supplier_group", "campaign", "sales_partner", "variant_of"] "supplier", "supplier_group", "campaign", "sales_partner", "variant_of"]
if len(pricing_rules) > 1: if len(pricing_rules) > 1:
for field_set in [["item_code", "variant_of", "item_group", "brand"], for field_set in [["item_code", "variant_of", "item_group", "brand"],
["customer", "customer_group", "territory"], ["supplier", "supplier_group"]]: ["customer", "customer_group", "territory"], ["supplier", "supplier_group"]]:
remaining_fields = list(set(all_fields) - set(field_set)) remaining_fields = list(set(all_fields) - set(field_set))
if if_all_rules_same(pricing_rules, remaining_fields): if if_all_rules_same(pricing_rules, remaining_fields):
pricing_rules = apply_internal_priority(pricing_rules, field_set, args) pricing_rules = apply_internal_priority(pricing_rules, field_set, args)
break break
if pricing_rules and not isinstance(pricing_rules, list): if pricing_rules and not isinstance(pricing_rules, list):
pricing_rules = list(pricing_rules) pricing_rules = list(pricing_rules)
if len(pricing_rules) > 1: if len(pricing_rules) > 1:
rate_or_discount = list(set([d.rate_or_discount for d in pricing_rules])) rate_or_discount = list(set([d.rate_or_discount for d in pricing_rules]))
if len(rate_or_discount) == 1 and rate_or_discount[0] == "Discount Percentage": if len(rate_or_discount) == 1 and rate_or_discount[0] == "Discount Percentage":
pricing_rules = filter(lambda x: x.for_price_list==args.price_list, pricing_rules) \ pricing_rules = filter(lambda x: x.for_price_list==args.price_list, pricing_rules) \
or pricing_rules or pricing_rules
if len(pricing_rules) > 1 and not args.for_shopping_cart: if len(pricing_rules) > 1 and not args.for_shopping_cart:
frappe.throw(_("Multiple Price Rules exists with same criteria, please resolve conflict by assigning priority. Price Rules: {0}") frappe.throw(_("Multiple Price Rules exists with same criteria, please resolve conflict by assigning priority. Price Rules: {0}")
.format("\n".join([d.name for d in pricing_rules])), MultiplePricingRuleConflict) .format("\n".join([d.name for d in pricing_rules])), MultiplePricingRuleConflict)
elif pricing_rules: elif pricing_rules:
return pricing_rules[0] return pricing_rules[0]
def validate_quantity_and_amount_for_suggestion(args, qty, amount, item_code, transaction_type): def validate_quantity_and_amount_for_suggestion(args, qty, amount, item_code, transaction_type):
fieldname, msg = '', '' fieldname, msg = '', ''
type_of_transaction = 'purcahse' if transaction_type == "buying" else "sale" type_of_transaction = 'purcahse' if transaction_type == "buying" else "sale"
for field, value in {'min_qty': qty, 'min_amt': amount}.items(): for field, value in {'min_qty': qty, 'min_amt': amount}.items():
if (args.get(field) and value < args.get(field) if (args.get(field) and value < args.get(field)
and (args.get(field) - cint(args.get(field) * args.threshold_percentage * 0.01)) <= value): and (args.get(field) - cint(args.get(field) * args.threshold_percentage * 0.01)) <= value):
fieldname = field fieldname = field
for field, value in {'max_qty': qty, 'max_amt': amount}.items(): for field, value in {'max_qty': qty, 'max_amt': amount}.items():
if (args.get(field) and value > args.get(field) if (args.get(field) and value > args.get(field)
and (args.get(field) + cint(args.get(field) * args.threshold_percentage * 0.01)) >= value): and (args.get(field) + cint(args.get(field) * args.threshold_percentage * 0.01)) >= value):
fieldname = field fieldname = field
if fieldname: if fieldname:
msg = _("""If you {0} {1} quantities of the item <b>{2}</b>, the scheme <b>{3}</b> msg = _("""If you {0} {1} quantities of the item <b>{2}</b>, the scheme <b>{3}</b>
will be applied on the item.""").format(type_of_transaction, args.get(fieldname), item_code, args.rule_description) will be applied on the item.""").format(type_of_transaction, args.get(fieldname), item_code, args.rule_description)
if fieldname in ['min_amt', 'max_amt']: if fieldname in ['min_amt', 'max_amt']:
msg = _("""If you {0} {1} worth item <b>{2}</b>, the scheme <b>{3}</b> will be applied on the item. msg = _("""If you {0} {1} worth item <b>{2}</b>, the scheme <b>{3}</b> will be applied on the item.
""").format(frappe.fmt_money(type_of_transaction, args.get(fieldname)), item_code, args.rule_description) """).format(frappe.fmt_money(type_of_transaction, args.get(fieldname)), item_code, args.rule_description)
frappe.msgprint(msg) frappe.msgprint(msg)
return msg return msg
def filter_pricing_rules_for_qty_amount(qty, rate, pricing_rules, args=None): def filter_pricing_rules_for_qty_amount(qty, rate, pricing_rules, args=None):
rules = [] rules = []
for rule in pricing_rules: for rule in pricing_rules:
status = False status = False
conversion_factor = 1 conversion_factor = 1
if rule.get("uom"): if rule.get("uom"):
conversion_factor = get_conversion_factor(rule.item_code, rule.uom).get("conversion_factor", 1) conversion_factor = get_conversion_factor(rule.item_code, rule.uom).get("conversion_factor", 1)
if (flt(qty) >= (flt(rule.min_qty) * conversion_factor) if (flt(qty) >= (flt(rule.min_qty) * conversion_factor)
and (flt(qty)<= (rule.max_qty * conversion_factor) if rule.max_qty else True)): and (flt(qty)<= (rule.max_qty * conversion_factor) if rule.max_qty else True)):
status = True status = True
# if user has created item price against the transaction UOM # if user has created item price against the transaction UOM
if rule.get("uom") == args.get("uom"): if rule.get("uom") == args.get("uom"):
conversion_factor = 1.0 conversion_factor = 1.0
if status and (flt(rate) >= (flt(rule.min_amt) * conversion_factor) if status and (flt(rate) >= (flt(rule.min_amt) * conversion_factor)
and (flt(rate)<= (rule.max_amt * conversion_factor) if rule.max_amt else True)): and (flt(rate)<= (rule.max_amt * conversion_factor) if rule.max_amt else True)):
status = True status = True
else: else:
status = False status = False
if status: if status:
rules.append(rule) rules.append(rule)
return rules return rules
def if_all_rules_same(pricing_rules, fields): def if_all_rules_same(pricing_rules, fields):
all_rules_same = True all_rules_same = True
val = [pricing_rules[0].get(k) for k in fields] val = [pricing_rules[0].get(k) for k in fields]
for p in pricing_rules[1:]: for p in pricing_rules[1:]:
if val != [p.get(k) for k in fields]: if val != [p.get(k) for k in fields]:
all_rules_same = False all_rules_same = False
break break
return all_rules_same return all_rules_same
def apply_internal_priority(pricing_rules, field_set, args): def apply_internal_priority(pricing_rules, field_set, args):
filtered_rules = [] filtered_rules = []
for field in field_set: for field in field_set:
if args.get(field): if args.get(field):
filtered_rules = filter(lambda x: x[field]==args[field], pricing_rules) filtered_rules = filter(lambda x: x[field]==args[field], pricing_rules)
if filtered_rules: break if filtered_rules: break
return filtered_rules or pricing_rules return filtered_rules or pricing_rules
def get_qty_and_rate_for_mixed_conditions(doc, pr_doc): def get_qty_and_rate_for_mixed_conditions(doc, pr_doc, args):
sum_qty, sum_amt = [0, 0] sum_qty, sum_amt = [0, 0]
items = get_pricing_rule_items(pr_doc) or [] items = get_pricing_rule_items(pr_doc) or []
apply_on = frappe.scrub(pr_doc.get('apply_on')) apply_on = frappe.scrub(pr_doc.get('apply_on'))
if items and doc.get("items"): if items and doc.get("items"):
for row in doc.get('items'): for row in doc.get('items'):
if row.get(apply_on) not in items: continue if row.get(apply_on) not in items: continue
if pr_doc.mixed_conditions: if pr_doc.mixed_conditions:
sum_qty += row.stock_qty amt = args.get('qty') * args.get("price_list_rate")
sum_amt += row.amount if args.get("item_code") != row.get("item_code"):
amt = row.get('qty') * row.get("price_list_rate")
if pr_doc.is_cumulative: sum_qty += row.get("stock_qty") or args.get("stock_qty")
data = get_qty_amount_data_for_cumulative(pr_doc, doc, items) sum_amt += amt
if data and data[0]: if pr_doc.is_cumulative:
sum_qty += data[0] data = get_qty_amount_data_for_cumulative(pr_doc, doc, items)
sum_amt += data[1]
return sum_qty, sum_amt if data and data[0]:
sum_qty += data[0]
sum_amt += data[1]
return sum_qty, sum_amt
def get_qty_and_rate_for_other_item(doc, pr_doc, pricing_rules): def get_qty_and_rate_for_other_item(doc, pr_doc, pricing_rules):
for d in get_pricing_rule_items(pr_doc): for d in get_pricing_rule_items(pr_doc):
for row in doc.items: for row in doc.items:
if d == row.get(frappe.scrub(pr_doc.apply_on)): if d == row.get(frappe.scrub(pr_doc.apply_on)):
pricing_rules = filter_pricing_rules_for_qty_amount(row.stock_qty, pricing_rules = filter_pricing_rules_for_qty_amount(row.get("stock_qty"),
row.amount, pricing_rules, row) row.get("amount"), pricing_rules, row)
if pricing_rules and pricing_rules[0]: if pricing_rules and pricing_rules[0]:
return pricing_rules return pricing_rules
def get_qty_amount_data_for_cumulative(pr_doc, doc, items=[]): def get_qty_amount_data_for_cumulative(pr_doc, doc, items=[]):
sum_qty, sum_amt = [0, 0] sum_qty, sum_amt = [0, 0]
doctype = doc.get('parenttype') or doc.doctype doctype = doc.get('parenttype') or doc.doctype
date_field = ('transaction_date' date_field = ('transaction_date'
if doc.get('transaction_date') else 'posting_date') if doc.get('transaction_date') else 'posting_date')
child_doctype = '{0} Item'.format(doctype) child_doctype = '{0} Item'.format(doctype)
apply_on = frappe.scrub(pr_doc.get('apply_on')) apply_on = frappe.scrub(pr_doc.get('apply_on'))
values = [pr_doc.valid_from, pr_doc.valid_upto] values = [pr_doc.valid_from, pr_doc.valid_upto]
condition = "" condition = ""
if pr_doc.warehouse: if pr_doc.warehouse:
warehouses = get_child_warehouses(pr_doc.warehouse) warehouses = get_child_warehouses(pr_doc.warehouse)
condition += """ and `tab{child_doc}`.warehouse in ({warehouses}) condition += """ and `tab{child_doc}`.warehouse in ({warehouses})
""".format(child_doc=child_doctype, warehouses = ','.join(['%s'] * len(warehouses))) """.format(child_doc=child_doctype, warehouses = ','.join(['%s'] * len(warehouses)))
values.extend(warehouses) values.extend(warehouses)
if items: if items:
condition = " and `tab{child_doc}`.{apply_on} in ({items})".format(child_doc = child_doctype, condition = " and `tab{child_doc}`.{apply_on} in ({items})".format(child_doc = child_doctype,
apply_on = apply_on, items = ','.join(['%s'] * len(items))) apply_on = apply_on, items = ','.join(['%s'] * len(items)))
values.extend(items) values.extend(items)
data_set = frappe.db.sql(""" SELECT `tab{child_doc}`.stock_qty, data_set = frappe.db.sql(""" SELECT `tab{child_doc}`.stock_qty,
`tab{child_doc}`.amount `tab{child_doc}`.amount
FROM `tab{child_doc}`, `tab{parent_doc}` FROM `tab{child_doc}`, `tab{parent_doc}`
WHERE WHERE
`tab{child_doc}`.parent = `tab{parent_doc}`.name and {date_field} `tab{child_doc}`.parent = `tab{parent_doc}`.name and {date_field}
between %s and %s and `tab{parent_doc}`.docstatus = 1 between %s and %s and `tab{parent_doc}`.docstatus = 1
{condition} group by `tab{child_doc}`.name {condition} group by `tab{child_doc}`.name
""".format(parent_doc = doctype, """.format(parent_doc = doctype,
child_doc = child_doctype, child_doc = child_doctype,
condition = condition, condition = condition,
date_field = date_field date_field = date_field
), tuple(values), as_dict=1) ), tuple(values), as_dict=1)
for data in data_set: for data in data_set:
sum_qty += data.get('stock_qty') sum_qty += data.get('stock_qty')
sum_amt += data.get('amount') sum_amt += data.get('amount')
return [sum_qty, sum_amt] return [sum_qty, sum_amt]
def validate_pricing_rules(doc): def validate_pricing_rules(doc):
validate_pricing_rule_on_transactions(doc) validate_pricing_rule_on_transactions(doc)
if not doc.pricing_rules: return for d in doc.items:
validate_pricing_rule_on_items(doc, d)
for d in doc.items: doc.calculate_taxes_and_totals()
validate_pricing_rule_on_items(doc, d)
doc.calculate_taxes_and_totals() def validate_pricing_rule_on_items(doc, item_row, do_not_validate = False):
value = 0
for pricing_rule in get_applied_pricing_rules(doc, item_row):
pr_doc = frappe.get_doc('Pricing Rule', pricing_rule)
def validate_pricing_rule_on_items(doc, item_row): if pr_doc.get('apply_on') == 'Transaction': continue
value = 0
for pr_row in get_applied_pricing_rules(doc, item_row):
pr_doc = frappe.get_doc('Pricing Rule', pr_row.pricing_rule)
if pr_doc.get('apply_on') == 'Transaction': continue if pr_doc.get('price_or_product_discount') == 'Product':
apply_pricing_rule_for_free_items(doc, pr_doc)
else:
for field in ['discount_percentage', 'discount_amount', 'rate']:
if not pr_doc.get(field): continue
if pr_doc.get('price_or_product_discount') == 'Product': value += pr_doc.get(field)
apply_pricing_rule_for_free_items(doc, pr_doc) apply_pricing_rule(doc, pr_doc, item_row, value, do_not_validate)
else:
for field in ['discount_percentage', 'discount_amount', 'rate']:
if not pr_doc.get(field): continue
value += pr_doc.get(field)
apply_pricing_rule(doc, pr_doc, pr_row, item_row, value)
def validate_pricing_rule_on_transactions(doc): def validate_pricing_rule_on_transactions(doc):
conditions = "apply_on = 'Transaction'" conditions = "apply_on = 'Transaction'"
values = {} values = {}
conditions = get_other_conditions(conditions, values, doc) conditions = get_other_conditions(conditions, values, doc)
pricing_rules = frappe.db.sql(""" Select `tabPricing Rule`.* from `tabPricing Rule` pricing_rules = frappe.db.sql(""" Select `tabPricing Rule`.* from `tabPricing Rule`
where {conditions} """.format(conditions = conditions), values, as_dict=1) where {conditions} """.format(conditions = conditions), values, as_dict=1)
if pricing_rules: if pricing_rules:
pricing_rules = filter_pricing_rules_for_qty_amount(doc.total_qty, pricing_rules = filter_pricing_rules_for_qty_amount(doc.total_qty,
doc.total, pricing_rules) doc.total, pricing_rules)
for d in pricing_rules: for d in pricing_rules:
if d.price_or_product_discount == 'Price': if d.price_or_product_discount == 'Price':
if d.apply_discount_on: if d.apply_discount_on:
doc.set('apply_discount_on', d.apply_discount_on) doc.set('apply_discount_on', d.apply_discount_on)
for field in ['additional_discount_percentage', 'discount_amount']: for field in ['additional_discount_percentage', 'discount_amount']:
if not d.get(field): continue if not d.get(field): continue
pr_field = ('discount_percentage' pr_field = ('discount_percentage'
if field == 'additional_discount_percentage' else field) if field == 'additional_discount_percentage' else field)
if d.validate_applied_rule and doc.get(field) < d.get(pr_field): if d.validate_applied_rule and doc.get(field) < d.get(pr_field):
frappe.msgprint(_("User has not applied rule on the invoice {0}") frappe.msgprint(_("User has not applied rule on the invoice {0}")
.format(doc.name)) .format(doc.name))
else: else:
doc.set(field, d.get(pr_field)) doc.set(field, d.get(pr_field))
elif d.price_or_product_discount == 'Product': elif d.price_or_product_discount == 'Product':
apply_pricing_rule_for_free_items(doc, d) apply_pricing_rule_for_free_items(doc, d)
def get_applied_pricing_rules(doc, item_row): def get_applied_pricing_rules(doc, item_row):
return [d for d in doc.pricing_rules return (item_row.get("pricing_rules").split(',')
if d.child_docname == item_row.name] if item_row.get("pricing_rules") else [])
def apply_pricing_rule_for_free_items(doc, pricing_rule): def apply_pricing_rule_for_free_items(doc, pricing_rule):
if pricing_rule.get('free_item'): if pricing_rule.get('free_item'):
items = [d.item_code for d in doc.items items = [d.item_code for d in doc.items
if d.item_code == (d.item_code if d.item_code == (d.item_code
if pricing_rule.get('same_item') else pricing_rule.get('free_item')) and d.is_free_item] if pricing_rule.get('same_item') else pricing_rule.get('free_item')) and d.is_free_item]
if not items: if not items:
doc.append('items', { doc.append('items', {
'item_code': pricing_rule.get('free_item'), 'item_code': pricing_rule.get('free_item'),
'qty': pricing_rule.get('free_qty'), 'qty': pricing_rule.get('free_qty'),
'uom': pricing_rule.get('free_item_uom'), 'uom': pricing_rule.get('free_item_uom'),
'rate': pricing_rule.get('free_item_rate'), 'rate': pricing_rule.get('free_item_rate'),
'is_free_item': 1 'is_free_item': 1
}) })
doc.set_missing_values() doc.set_missing_values()
def apply_pricing_rule(doc, pr_doc, pr_row, item_row, value): def apply_pricing_rule(doc, pr_doc, item_row, value, do_not_validate=False):
apply_on = frappe.scrub(pr_doc.get('apply_on')) apply_on, items = get_apply_on_and_items(pr_doc, item_row)
items = (get_pricing_rule_items(pr_doc)
if pr_doc.mixed_conditions else [item_row.get(apply_on)])
if pr_doc.apply_rule_on_other: rule_applied = {}
apply_on = frappe.scrub(pr_doc.apply_rule_on_other)
items = [pr_doc.get(apply_on)]
rule_applied = 1 for item in doc.get("items"):
if item_row.get(apply_on) in items: if not item.pricing_rules:
for field in ['discount_percentage', 'discount_amount', 'rate']: item.pricing_rules = item_row.pricing_rules
if not pr_doc.get(field): continue
if not pr_doc.validate_applied_rule: if item.get(apply_on) in items:
item_row.set(field, value) for field in ['discount_percentage', 'discount_amount', 'rate']:
elif item_row.get(field) < value: if not pr_doc.get(field): continue
rule_applied = 0
frappe.msgprint(_("Row {0}: user has not applied rule <b>{1}</b> on the item <b>{2}</b>")
.format(item_row.idx, pr_doc.title, item_row.item_code))
pr_row.rule_applied = rule_applied key = (item.name, item.pricing_rules)
if not pr_doc.validate_applied_rule:
rule_applied[key] = 1
item.set(field, value)
elif item.get(field) < value:
if not do_not_validate and item.idx == item_row.idx:
rule_applied[key] = 0
frappe.msgprint(_("Row {0}: user has not applied rule <b>{1}</b> on the item <b>{2}</b>")
.format(item.idx, pr_doc.title, item.item_code))
if rule_applied and doc.get("pricing_rules"):
for d in doc.get("pricing_rules"):
key = (d.child_docname, d.pricing_rule)
if key in rule_applied:
d.rule_applied = 1
def get_apply_on_and_items(pr_doc, item_row):
# for mixed or other items conditions
apply_on = frappe.scrub(pr_doc.get('apply_on'))
items = (get_pricing_rule_items(pr_doc)
if pr_doc.mixed_conditions else [item_row.get(apply_on)])
if pr_doc.apply_rule_on_other:
apply_on = frappe.scrub(pr_doc.apply_rule_on_other)
items = [pr_doc.get(apply_on)]
return apply_on, items
def get_pricing_rule_items(pr_doc): def get_pricing_rule_items(pr_doc):
apply_on = frappe.scrub(pr_doc.get('apply_on')) apply_on = frappe.scrub(pr_doc.get('apply_on'))
return [item.get(apply_on) for item in pr_doc.items] or []
pricing_rule_apply_on = apply_on_table.get(pr_doc.get('apply_on'))
return [item.get(apply_on) for item in pr_doc.get(pricing_rule_apply_on)] or []
@frappe.whitelist()
def validate_pricing_rule_for_different_cond(doc):
if isinstance(doc, string_types):
doc = json.loads(doc)
doc = frappe.get_doc(doc)
for d in doc.get("items"):
validate_pricing_rule_on_items(doc, d, True)
return doc

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){ cur_frm.cscript.cost_center = function(doc, cdt, cdn){
var d = locals[cdt][cdn]; var d = locals[cdt][cdn];
if(d.idx == 1 && d.cost_center){ if(d.cost_center){
var cl = doc.items || []; var cl = doc.items || [];
for(var i = 0; i < cl.length; i++){ for(var i = 0; i < cl.length; i++){
if(!cl[i].cost_center) cl[i].cost_center = d.cost_center; 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: if not self.on_hold:
self.release_date = '' 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): def invoice_is_blocked(self):
return self.on_hold and (not self.release_date or self.release_date > getdate(nowdate())) return self.on_hold and (not self.release_date or self.release_date > getdate(nowdate()))
@ -770,10 +765,6 @@ class PurchaseInvoice(BuyingController):
self.update_status_updater_args() self.update_status_updater_args()
if not self.is_return: if not self.is_return:
from erpnext.accounts.utils import unlink_ref_doc_from_payment_entries
if frappe.db.get_single_value('Accounts Settings', 'unlink_payment_on_cancellation_of_invoice'):
unlink_ref_doc_from_payment_entries(self)
self.update_prevdoc_status() self.update_prevdoc_status()
self.update_billing_status_for_zero_amount_refdoc("Purchase Order") self.update_billing_status_for_zero_amount_refdoc("Purchase Order")
self.update_billing_status_in_pr() self.update_billing_status_in_pr()

View File

@ -205,18 +205,11 @@ class SalesInvoice(SellingController):
def before_cancel(self): def before_cancel(self):
self.update_time_sheet(None) 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): def on_cancel(self):
self.check_close_sales_order("sales_order") super(SalesInvoice, self).on_cancel()
from erpnext.accounts.utils import unlink_ref_doc_from_payment_entries self.check_close_sales_order("sales_order")
if frappe.db.get_single_value('Accounts Settings', 'unlink_payment_on_cancellation_of_invoice'):
unlink_ref_doc_from_payment_entries(self)
if self.is_return and not self.update_billed_amount_in_sales_order: if self.is_return and not self.update_billed_amount_in_sales_order:
# NOTE status updating bypassed for is_return # NOTE status updating bypassed for is_return

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.exceptions import InvalidAccountCurrency, InvalidCurrency
from erpnext.stock.doctype.serial_no.serial_no import SerialNoWarehouseError from erpnext.stock.doctype.serial_no.serial_no import SerialNoWarehouseError
from frappe.model.naming import make_autoname 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.controllers.taxes_and_totals import get_itemised_tax_breakup_data
from erpnext.stock.doctype.item.test_item import create_item
from six import iteritems from six import iteritems
class TestSalesInvoice(unittest.TestCase): class TestSalesInvoice(unittest.TestCase):
def make(self): def make(self):
@ -1572,6 +1573,56 @@ class TestSalesInvoice(unittest.TestCase):
accounts_settings.allow_cost_center_in_entry_of_bs_account = 0 accounts_settings.allow_cost_center_in_entry_of_bs_account = 0
accounts_settings.save() 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): def create_sales_invoice(**args):
si = frappe.new_doc("Sales Invoice") si = frappe.new_doc("Sales Invoice")
@ -1669,4 +1720,4 @@ def get_outstanding_amount(against_voucher_type, against_voucher, account, party
if against_voucher_type == 'Purchase Invoice': if against_voucher_type == 'Purchase Invoice':
bal = bal * -1 bal = bal * -1
return bal return bal

View File

@ -333,6 +333,7 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
var me = this; var me = this;
this.frm = {} this.frm = {}
this.load_data(true); this.load_data(true);
this.frm.doc.offline_pos_name = '';
this.setup(); this.setup();
this.set_default_customer() this.set_default_customer()
}, },
@ -345,7 +346,6 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
if (load_doc) { if (load_doc) {
this.frm.doc = JSON.parse(localStorage.getItem('doc')); this.frm.doc = JSON.parse(localStorage.getItem('doc'));
this.frm.doc.offline_pos_name = null;
} }
$.each(this.meta, function (i, data) { $.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.list_customers_btn.toggleClass("view_customer");
me.pos_bill.show(); me.pos_bill.show();
me.list_customers_btn.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(); 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) { 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 () { create_invoice: function () {
var me = this; var me = this;
var existing_pos_list = [];
var invoice_data = {}; var invoice_data = {};
this.si_docs = this.get_doc_from_localstorage(); 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() this.update_invoice()
//to retrieve and set the default payment //to retrieve and set the default payment
invoice_data[this.frm.doc.offline_pos_name] = this.frm.doc; 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.paid_amount = this.frm.doc.net_total
this.frm.doc.outstanding_amount = 0 this.frm.doc.outstanding_amount = 0
} else { } else if(!this.frm.doc.offline_pos_name) {
this.frm.doc.offline_pos_name = $.now(); this.frm.doc.offline_pos_name = frappe.datetime.now_datetime();
this.frm.doc.posting_date = frappe.datetime.get_today(); this.frm.doc.posting_date = frappe.datetime.get_today();
this.frm.doc.posting_time = frappe.datetime.now_time(); this.frm.doc.posting_time = frappe.datetime.now_time();
this.frm.doc.pos_total_qty = this.frm.doc.qty_total; this.frm.doc.pos_total_qty = this.frm.doc.qty_total;

View File

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

View File

@ -136,7 +136,8 @@ class AccountsReceivableSummary(ReceivablePayableReport):
partywise_total = self.get_partywise_total(party_naming_by, args) 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): for party, party_dict in iteritems(partywise_total):
row = [party] row = [party]
@ -144,7 +145,10 @@ class AccountsReceivableSummary(ReceivablePayableReport):
row += [self.get_party_name(args.get("party_type"), party)] row += [self.get_party_name(args.get("party_type"), party)]
row += [partywise_advance_amount.get(party, 0)] 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 += [ row += [
party_dict.invoiced_amt, paid_amt, party_dict.credit_amt, party_dict.outstanding_amt, party_dict.invoiced_amt, paid_amt, party_dict.credit_amt, party_dict.outstanding_amt,

View File

@ -14,13 +14,13 @@ def execute(filters=None):
def get_column(): def get_column():
return [ return [
_("Delivery Note") + ":Link/Delivery Note:120", _("Date") + ":Date:100", _("Delivery Note") + ":Link/Delivery Note:120", _("Status") + "::120", _("Date") + ":Date:100",
_("Suplier") + ":Link/Customer:120", _("Customer Name") + "::120", _("Suplier") + ":Link/Customer:120", _("Customer Name") + "::120",
_("Project") + ":Link/Project:120", _("Item Code") + ":Link/Item:120", _("Project") + ":Link/Project:120", _("Item Code") + ":Link/Item:120",
_("Amount") + ":Currency:100", _("Billed Amount") + ":Currency:100", _("Pending Amount") + ":Currency:100", _("Amount") + ":Currency:100", _("Billed Amount") + ":Currency:100", _("Pending Amount") + ":Currency:100",
_("Item Name") + "::120", _("Description") + "::120", _("Company") + ":Link/Company:120", _("Item Name") + "::120", _("Description") + "::120", _("Company") + ":Link/Company:120",
] ]
def get_args(): def get_args():
return {'doctype': 'Delivery Note', 'party': 'customer', return {'doctype': 'Delivery Note', 'party': 'customer',
'date': 'posting_date', 'order': 'name', 'order_by': 'desc'} 'date': 'posting_date', 'order': 'name', 'order_by': 'desc'}

View File

@ -12,14 +12,14 @@ def get_ordered_to_be_billed_data(args):
child_tab = doctype + " Item" child_tab = doctype + " Item"
precision = get_field_precision(frappe.get_meta(child_tab).get_field("billed_amt"), precision = get_field_precision(frappe.get_meta(child_tab).get_field("billed_amt"),
currency=get_default_currency()) or 2 currency=get_default_currency()) or 2
project_field = get_project_field(doctype, party) project_field = get_project_field(doctype, party)
return frappe.db.sql(""" return frappe.db.sql("""
Select Select
`{parent_tab}`.name, `{parent_tab}`.{date_field}, `{parent_tab}`.{party}, `{parent_tab}`.{party}_name, `{parent_tab}`.name, `{parent_tab}`.status, `{parent_tab}`.{date_field}, `{parent_tab}`.{party}, `{parent_tab}`.{party}_name,
{project_field}, `{child_tab}`.item_code, `{child_tab}`.base_amount, {project_field}, `{child_tab}`.item_code, `{child_tab}`.base_amount,
(`{child_tab}`.billed_amt * ifnull(`{parent_tab}`.conversion_rate, 1)), (`{child_tab}`.billed_amt * ifnull(`{parent_tab}`.conversion_rate, 1)),
(`{child_tab}`.base_amount - (`{child_tab}`.billed_amt * ifnull(`{parent_tab}`.conversion_rate, 1))), (`{child_tab}`.base_amount - (`{child_tab}`.billed_amt * ifnull(`{parent_tab}`.conversion_rate, 1))),
`{child_tab}`.item_name, `{child_tab}`.description, `{parent_tab}`.company `{child_tab}`.item_name, `{child_tab}`.description, `{parent_tab}`.company
from from

View File

@ -14,13 +14,13 @@ def execute(filters=None):
def get_column(): def get_column():
return [ return [
_("Sales Order") + ":Link/Sales Order:120", _("Date") + ":Date:100", _("Sales Order") + ":Link/Sales Order:120", _("Status") + "::120", _("Date") + ":Date:100",
_("Suplier") + ":Link/Customer:120", _("Customer Name") + "::120", _("Suplier") + ":Link/Customer:120", _("Customer Name") + "::120",
_("Project") + ":Link/Project:120", _("Item Code") + ":Link/Item:120", _("Project") + ":Link/Project:120", _("Item Code") + ":Link/Item:120",
_("Amount") + ":Currency:100", _("Billed Amount") + ":Currency:100", _("Pending Amount") + ":Currency:100", _("Amount") + ":Currency:100", _("Billed Amount") + ":Currency:100", _("Pending Amount") + ":Currency:100",
_("Item Name") + "::120", _("Description") + "::120", _("Company") + ":Link/Company:120", _("Item Name") + "::120", _("Description") + "::120", _("Company") + ":Link/Company:120",
] ]
def get_args(): def get_args():
return {'doctype': 'Sales Order', 'party': 'customer', return {'doctype': 'Sales Order', 'party': 'customer',
'date': 'transaction_date', 'order': 'transaction_date', 'order_by': 'asc'} 'date': 'transaction_date', 'order': 'transaction_date', 'order_by': 'asc'}

View File

@ -133,6 +133,13 @@ def get_columns(filters):
"options": filters.get("based_on"), "options": filters.get("based_on"),
"width": 300 "width": 300
}, },
{
"fieldname": "currency",
"label": _("Currency"),
"fieldtype": "Link",
"options": "Currency",
"hidden": 1
},
{ {
"fieldname": "income", "fieldname": "income",
"label": _("Income"), "label": _("Income"),
@ -153,13 +160,6 @@ def get_columns(filters):
"fieldtype": "Currency", "fieldtype": "Currency",
"options": "currency", "options": "currency",
"width": 120 "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: for entry in gl_entries:
gl_entries_by_account.setdefault(entry.based_on, []).append(entry) gl_entries_by_account.setdefault(entry.based_on, []).append(entry)
return gl_entries_by_account return gl_entries_by_account

View File

@ -14,13 +14,13 @@ def execute(filters=None):
def get_column(): def get_column():
return [ return [
_("Purchase Order") + ":Link/Purchase Order:120", _("Date") + ":Date:100", _("Purchase Order") + ":Link/Purchase Order:120", _("Status") + "::120", _("Date") + ":Date:100",
_("Suplier") + ":Link/Supplier:120", _("Suplier Name") + "::120", _("Suplier") + ":Link/Supplier:120", _("Suplier Name") + "::120",
_("Project") + ":Link/Project:120", _("Item Code") + ":Link/Item:120", _("Project") + ":Link/Project:120", _("Item Code") + ":Link/Item:120",
_("Amount") + ":Currency:100", _("Billed Amount") + ":Currency:100", _("Amount to Bill") + ":Currency:100", _("Amount") + ":Currency:100", _("Billed Amount") + ":Currency:100", _("Amount to Bill") + ":Currency:100",
_("Item Name") + "::120", _("Description") + "::120", _("Company") + ":Link/Company:120", _("Item Name") + "::120", _("Description") + "::120", _("Company") + ":Link/Company:120",
] ]
def get_args(): def get_args():
return {'doctype': 'Purchase Order', 'party': 'supplier', return {'doctype': 'Purchase Order', 'party': 'supplier',
'date': 'transaction_date', 'order': 'transaction_date', 'order_by': 'asc'} 'date': 'transaction_date', 'order': 'transaction_date', 'order_by': 'asc'}

View File

@ -14,7 +14,7 @@ def execute(filters=None):
def get_column(): def get_column():
return [ return [
_("Purchase Receipt") + ":Link/Purchase Receipt:120", _("Date") + ":Date:100", _("Purchase Receipt") + ":Link/Purchase Receipt:120", _("Status") + "::120", _("Date") + ":Date:100",
_("Supplier") + ":Link/Supplier:120", _("Supplier Name") + "::120", _("Supplier") + ":Link/Supplier:120", _("Supplier Name") + "::120",
_("Project") + ":Link/Project:120", _("Item Code") + ":Link/Item:120", _("Project") + ":Link/Project:120", _("Item Code") + ":Link/Item:120",
_("Amount") + ":Currency:100", _("Billed Amount") + ":Currency:100", _("Amount to Bill") + ":Currency:100", _("Amount") + ":Currency:100", _("Billed Amount") + ":Currency:100", _("Amount to Bill") + ":Currency:100",

View File

@ -25,9 +25,6 @@ frappe.ui.form.on("Purchase Order", {
frm.set_indicator_formatter('item_code', frm.set_indicator_formatter('item_code',
function(doc) { return (doc.qty<=doc.received_qty) ? "green" : "orange" }) function(doc) { return (doc.qty<=doc.received_qty) ? "green" : "orange" })
frm.set_indicator_formatter('pricing_rule',
function(doc) { return (doc.rule_applied) ? "green" : "red" })
frm.set_query("blanket_order", "items", function() { frm.set_query("blanket_order", "items", function() {
return { return {
filters: { filters: {

View File

@ -465,6 +465,33 @@ class TestPurchaseOrder(unittest.TestCase):
self.assertEquals(se_items, supplied_items) self.assertEquals(se_items, supplied_items)
update_backflush_based_on("BOM") update_backflush_based_on("BOM")
def test_advance_payment_entry_unlink_against_purchase_order(self):
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
frappe.db.set_value("Accounts Settings", "Accounts Settings",
"unlink_advance_payment_on_cancelation_of_order", 1)
po_doc = create_purchase_order()
pe = get_payment_entry("Purchase Order", po_doc.name, bank_account="_Test Bank - _TC")
pe.reference_no = "1"
pe.reference_date = nowdate()
pe.paid_from_account_currency = po_doc.currency
pe.paid_to_account_currency = po_doc.currency
pe.source_exchange_rate = 1
pe.target_exchange_rate = 1
pe.paid_amount = po_doc.grand_total
pe.save(ignore_permissions=True)
pe.submit()
po_doc = frappe.get_doc('Purchase Order', po_doc.name)
po_doc.cancel()
pe_doc = frappe.get_doc('Payment Entry', pe.name)
pe_doc.cancel()
frappe.db.set_value("Accounts Settings", "Accounts Settings",
"unlink_advance_payment_on_cancelation_of_order", 0)
def make_subcontracted_item(item_code): def make_subcontracted_item(item_code):
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom

View File

@ -81,9 +81,9 @@ def get_data():
"description": "Sales pipeline, leads, opportunities and customers." "description": "Sales pipeline, leads, opportunities and customers."
}, },
{ {
"module_name": "Help Desk", "module_name": "Support",
"category": "Modules", "category": "Modules",
"label": _("Help Desk"), "label": _("Support"),
"color": "#1abc9c", "color": "#1abc9c",
"icon": "fa fa-check-square-o", "icon": "fa fa-check-square-o",
"type": "module", "type": "module",

View File

@ -1,82 +0,0 @@
from __future__ import unicode_literals
from frappe import _
def get_data():
return [
{
"label": _("Issues"),
"items": [
{
"type": "doctype",
"name": "Issue",
"description": _("Support queries from customers."),
"onboard": 1,
},
{
"type": "doctype",
"name": "Communication",
"description": _("Communication log."),
"onboard": 1,
},
]
},
{
"label": _("Warranty"),
"items": [
{
"type": "doctype",
"name": "Warranty Claim",
"description": _("Warranty Claim against Serial No."),
},
{
"type": "doctype",
"name": "Serial No",
"description": _("Single unit of an Item."),
},
]
},
{
"label": _("Service Level Agreement"),
"items": [
{
"type": "doctype",
"name": "Employee Group",
"description": _("Support Team."),
},
{
"type": "doctype",
"name": "Service Level",
"description": _("Service Level."),
},
{
"type": "doctype",
"name": "Service Level Agreement",
"description": _("Service Level Agreement."),
}
]
},
{
"label": _("Reports"),
"icon": "fa fa-list",
"items": [
{
"type": "page",
"name": "support-analytics",
"label": _("Support Analytics"),
"icon": "fa fa-bar-chart"
},
{
"type": "report",
"name": "Minutes to First Response for Issues",
"doctype": "Issue",
"is_query_report": True
},
{
"type": "report",
"name": "Support Hours",
"doctype": "Issue",
"is_query_report": True
},
]
},
]

View File

@ -287,6 +287,11 @@ def get_data():
"name": "Employee Advance", "name": "Employee Advance",
"dependencies": ["Employee"] "dependencies": ["Employee"]
}, },
{
"type": "doctype",
"name": "Expense Claim",
"dependencies": ["Employee"]
},
{ {
"type": "doctype", "type": "doctype",
"name": "Loan Type", "name": "Loan Type",
@ -296,6 +301,10 @@ def get_data():
"name": "Loan Application", "name": "Loan Application",
"dependencies": ["Employee"] "dependencies": ["Employee"]
}, },
{
"type": "doctype",
"name": "Loan"
}
] ]
}, },
{ {

View File

@ -27,6 +27,13 @@ def get_data():
"onboard": 1, "onboard": 1,
"dependencies": ["Item", "Customer"], "dependencies": ["Item", "Customer"],
}, },
{
"type": "doctype",
"name": "Sales Invoice",
"description": _("Invoices for Costumers."),
"onboard": 1,
"dependencies": ["Item", "Customer"],
},
{ {
"type": "doctype", "type": "doctype",
"name": "Sales Partner", "name": "Sales Partner",

View File

@ -10,11 +10,13 @@ def get_data():
"type": "doctype", "type": "doctype",
"name": "Issue", "name": "Issue",
"description": _("Support queries from customers."), "description": _("Support queries from customers."),
"onboard": 1,
}, },
{ {
"type": "doctype", "type": "doctype",
"name": "Communication", "name": "Communication",
"description": _("Communication log."), "description": _("Communication log."),
"onboard": 1,
}, },
] ]
}, },
@ -33,6 +35,26 @@ def get_data():
}, },
] ]
}, },
{
"label": _("Service Level Agreement"),
"items": [
{
"type": "doctype",
"name": "Employee Group",
"description": _("Support Team."),
},
{
"type": "doctype",
"name": "Service Level",
"description": _("Service Level."),
},
{
"type": "doctype",
"name": "Service Level Agreement",
"description": _("Service Level Agreement."),
}
]
},
{ {
"label": _("Reports"), "label": _("Reports"),
"icon": "fa fa-list", "icon": "fa fa-list",
@ -57,24 +79,4 @@ def get_data():
}, },
] ]
}, },
{ ]
"label": _("Service Level Agreement"),
"items": [
{
"type": "doctype",
"name": "Employee Group",
"description": _("Support Team."),
},
{
"type": "doctype",
"name": "Service Level",
"description": _("Service Level."),
},
{
"type": "doctype",
"name": "Service Level Agreement",
"description": _("Service Level Agreement."),
}
]
},
]

View File

@ -119,6 +119,12 @@ class AccountsController(TransactionBase):
self.validate_non_invoice_documents_schedule() self.validate_non_invoice_documents_schedule()
def before_print(self): 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', if self.doctype in ['Purchase Order', 'Sales Order', 'Sales Invoice', 'Purchase Invoice',
'Supplier Quotation', 'Purchase Receipt', 'Delivery Note', 'Quotation']: 'Supplier Quotation', 'Purchase Receipt', 'Delivery Note', 'Quotation']:
if self.get("group_same_items"): if self.get("group_same_items"):
@ -276,7 +282,7 @@ class AccountsController(TransactionBase):
if self.doctype in ["Purchase Invoice", "Sales Invoice"] and item.meta.get_field('is_fixed_asset'): if self.doctype in ["Purchase Invoice", "Sales Invoice"] and item.meta.get_field('is_fixed_asset'):
item.set('is_fixed_asset', ret.get('is_fixed_asset', 0)) item.set('is_fixed_asset', ret.get('is_fixed_asset', 0))
if ret.get("pricing_rules"): if ret.get("pricing_rules") and not ret.get("validate_applied_rule", 0):
# if user changed the discount percentage then set user's discount percentage ? # if user changed the discount percentage then set user's discount percentage ?
item.set("pricing_rules", ret.get("pricing_rules")) item.set("pricing_rules", ret.get("pricing_rules"))
item.set("discount_percentage", ret.get("discount_percentage")) item.set("discount_percentage", ret.get("discount_percentage"))
@ -546,6 +552,19 @@ class AccountsController(TransactionBase):
from erpnext.accounts.utils import reconcile_against_document from erpnext.accounts.utils import reconcile_against_document
reconcile_against_document(lst) reconcile_against_document(lst)
def on_cancel(self):
from erpnext.accounts.utils import unlink_ref_doc_from_payment_entries
if self.doctype in ["Sales Invoice", "Purchase Invoice"]:
if self.is_return: return
if frappe.db.get_single_value('Accounts Settings', 'unlink_payment_on_cancellation_of_invoice'):
unlink_ref_doc_from_payment_entries(self)
elif self.doctype in ["Sales Order", "Purchase Order"]:
if frappe.db.get_single_value('Accounts Settings', 'unlink_advance_payment_on_cancelation_of_order'):
unlink_ref_doc_from_payment_entries(self)
def validate_multiple_billing(self, ref_dt, item_ref_dn, based_on, parentfield): def validate_multiple_billing(self, ref_dt, item_ref_dn, based_on, parentfield):
from erpnext.controllers.status_updater import get_tolerance_for from erpnext.controllers.status_updater import get_tolerance_for
item_tolerance = {} item_tolerance = {}

View File

@ -531,6 +531,8 @@ class BuyingController(StockController):
update_last_purchase_rate(self, is_submit = 1) update_last_purchase_rate(self, is_submit = 1)
def on_cancel(self): def on_cancel(self):
super(BuyingController, self).on_cancel()
if self.get('is_return'): if self.get('is_return'):
return return

View File

@ -153,7 +153,6 @@ standard_portal_menu_items = [
{"title": _("Issues"), "route": "/issues", "reference_doctype": "Issue", "role":"Customer"}, {"title": _("Issues"), "route": "/issues", "reference_doctype": "Issue", "role":"Customer"},
{"title": _("Addresses"), "route": "/addresses", "reference_doctype": "Address"}, {"title": _("Addresses"), "route": "/addresses", "reference_doctype": "Address"},
{"title": _("Timesheets"), "route": "/timesheets", "reference_doctype": "Timesheet", "role":"Customer"}, {"title": _("Timesheets"), "route": "/timesheets", "reference_doctype": "Timesheet", "role":"Customer"},
{"title": _("Timesheets"), "route": "/timesheets", "reference_doctype": "Timesheet", "role":"Customer"},
{"title": _("Lab Test"), "route": "/lab-test", "reference_doctype": "Lab Test", "role":"Patient"}, {"title": _("Lab Test"), "route": "/lab-test", "reference_doctype": "Lab Test", "role":"Patient"},
{"title": _("Prescription"), "route": "/prescription", "reference_doctype": "Patient Encounter", "role":"Patient"}, {"title": _("Prescription"), "route": "/prescription", "reference_doctype": "Patient Encounter", "role":"Patient"},
{"title": _("Patient Appointment"), "route": "/patient-appointments", "reference_doctype": "Patient Appointment", "role":"Patient"}, {"title": _("Patient Appointment"), "route": "/patient-appointments", "reference_doctype": "Patient Appointment", "role":"Patient"},
@ -246,8 +245,7 @@ scheduler_events = {
"erpnext.erpnext_integrations.doctype.amazon_mws_settings.amazon_mws_settings.schedule_get_order_details", "erpnext.erpnext_integrations.doctype.amazon_mws_settings.amazon_mws_settings.schedule_get_order_details",
"erpnext.accounts.doctype.gl_entry.gl_entry.rename_gle_sle_docs", "erpnext.accounts.doctype.gl_entry.gl_entry.rename_gle_sle_docs",
"erpnext.projects.doctype.project.project.hourly_reminder", "erpnext.projects.doctype.project.project.hourly_reminder",
"erpnext.projects.doctype.project.project.collect_project_status", "erpnext.projects.doctype.project.project.collect_project_status"
"erpnext.support.doctype.issue.issue.update_support_timer",
], ],
"daily": [ "daily": [
"erpnext.stock.reorder_item.reorder_item", "erpnext.stock.reorder_item.reorder_item",
@ -338,4 +336,4 @@ user_privacy_documents = [
'match_field': 'contact_email', 'match_field': 'contact_email',
'personal_fields': ['contact_mobile', 'contact_display', 'customer_name'], 'personal_fields': ['contact_mobile', 'contact_display', 'customer_name'],
} }
] ]

View File

@ -400,6 +400,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)) 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): def get_leaves_for_period(employee, leave_type, from_date, to_date, status, docname=None):
leave_applications = frappe.db.sql(""" leave_applications = frappe.db.sql("""
select name, employee, leave_type, from_date, to_date, total_leave_days select name, employee, leave_type, from_date, to_date, total_leave_days

View File

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

View File

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

View File

@ -14,11 +14,11 @@ def execute(filters=None):
columns, earning_types, ded_types = get_columns(salary_slips) columns, earning_types, ded_types = get_columns(salary_slips)
ss_earning_map = get_ss_earning_map(salary_slips) ss_earning_map = get_ss_earning_map(salary_slips)
ss_ded_map = get_ss_ded_map(salary_slips) ss_ded_map = get_ss_ded_map(salary_slips)
doj_map = get_employee_doj_map()
data = [] data = []
for ss in salary_slips: for ss in salary_slips:
row = [ss.name, ss.employee, ss.employee_name, ss.branch, ss.department, ss.designation, row = [ss.name, ss.employee, ss.employee_name, doj_map.get(ss.employee), ss.branch, ss.department, ss.designation,
ss.company, ss.start_date, ss.end_date, ss.leave_without_pay, ss.payment_days] ss.company, ss.start_date, ss.end_date, ss.leave_without_pay, ss.payment_days]
if not ss.branch == None:columns[3] = columns[3].replace('-1','120') if not ss.branch == None:columns[3] = columns[3].replace('-1','120')
@ -44,17 +44,17 @@ def execute(filters=None):
def get_columns(salary_slips): def get_columns(salary_slips):
""" """
columns = [ columns = [
_("Salary Slip ID") + ":Link/Salary Slip:150",_("Employee") + ":Link/Employee:120", _("Employee Name") + "::140", _("Branch") + ":Link/Branch:120", _("Salary Slip ID") + ":Link/Salary Slip:150",_("Employee") + ":Link/Employee:120", _("Employee Name") + "::140",
_("Department") + ":Link/Department:120", _("Designation") + ":Link/Designation:120", _("Date of Joining") + "::80", _("Branch") + ":Link/Branch:120", _("Department") + ":Link/Department:120",
_("Company") + ":Link/Company:120", _("Start Date") + "::80", _("End Date") + "::80", _("Leave Without Pay") + ":Float:130", _("Designation") + ":Link/Designation:120", _("Company") + ":Link/Company:120", _("Start Date") + "::80",
_("Payment Days") + ":Float:120" _("End Date") + "::80", _("Leave Without Pay") + ":Float:130", _("Payment Days") + ":Float:120"
] ]
""" """
columns = [ columns = [
_("Salary Slip ID") + ":Link/Salary Slip:150",_("Employee") + ":Link/Employee:120", _("Employee Name") + "::140", _("Branch") + ":Link/Branch:-1", _("Salary Slip ID") + ":Link/Salary Slip:150",_("Employee") + ":Link/Employee:120", _("Employee Name") + "::140",
_("Department") + ":Link/Department:-1", _("Designation") + ":Link/Designation:-1", _("Date of Joining") + "::80", _("Branch") + ":Link/Branch:-1", _("Department") + ":Link/Department:-1",
_("Company") + ":Link/Company:120", _("Start Date") + "::80", _("End Date") + "::80", _("Leave Without Pay") + ":Float:-1", _("Designation") + ":Link/Designation:-1", _("Company") + ":Link/Company:120", _("Start Date") + "::80",
_("Payment Days") + ":Float:120" _("End Date") + "::80", _("Leave Without Pay") + ":Float:-1", _("Payment Days") + ":Float:120"
] ]
salary_components = {_("Earning"): [], _("Deduction"): []} salary_components = {_("Earning"): [], _("Deduction"): []}
@ -93,6 +93,16 @@ def get_conditions(filters):
return conditions, filters return conditions, filters
def get_employee_doj_map():
return frappe._dict(frappe.db.sql("""
SELECT
employee,
date_of_joining
FROM `tabEmployee`
WHERE
`status`='Active'
"""))
def get_ss_earning_map(salary_slips): def get_ss_earning_map(salary_slips):
ss_earnings = frappe.db.sql("""select parent, salary_component, amount ss_earnings = frappe.db.sql("""select parent, salary_component, amount
from `tabSalary Detail` where parent in (%s)""" % from `tabSalary Detail` where parent in (%s)""" %
@ -115,4 +125,4 @@ def get_ss_ded_map(salary_slips):
ss_ded_map.setdefault(d.parent, frappe._dict()).setdefault(d.salary_component, []) ss_ded_map.setdefault(d.parent, frappe._dict()).setdefault(d.salary_component, [])
ss_ded_map[d.parent][d.salary_component] = flt(d.amount) ss_ded_map[d.parent][d.salary_component] = flt(d.amount)
return ss_ded_map return ss_ded_map

View File

@ -586,10 +586,12 @@ execute:frappe.delete_doc('DocType', 'Notification Control')
erpnext.patches.v12_0.set_gst_category erpnext.patches.v12_0.set_gst_category
erpnext.patches.v11_0.remove_barcodes_field_from_copy_fields_to_variants erpnext.patches.v11_0.remove_barcodes_field_from_copy_fields_to_variants
erpnext.patches.v12_0.set_task_status erpnext.patches.v12_0.set_task_status
erpnext.patches.v11_0.make_italian_localization_fields # 01-03-2019 erpnext.patches.v11_0.make_italian_localization_fields # 26-03-2019
erpnext.patches.v12_0.add_item_name_in_work_orders erpnext.patches.v12_0.add_item_name_in_work_orders
erpnext.patches.v12_0.update_pricing_rule_fields erpnext.patches.v12_0.update_pricing_rule_fields
erpnext.patches.v11_1.make_job_card_time_logs erpnext.patches.v11_1.make_job_card_time_logs
erpnext.patches.v12_0.rename_pricing_rule_child_doctypes erpnext.patches.v12_0.rename_pricing_rule_child_doctypes
erpnext.patches.v12_0.move_target_distribution_from_parent_to_child erpnext.patches.v12_0.move_target_distribution_from_parent_to_child
erpnext.patches.v12_0.stock_entry_enhancements erpnext.patches.v12_0.stock_entry_enhancements
erpnext.patches.v10_0.item_barcode_childtable_migrate # 16-02-2019
erpnext.patches.v12_0.move_item_tax_to_item_tax_template

View File

@ -6,7 +6,6 @@ from erpnext.regional.italy.setup import make_custom_fields, setup_report
from erpnext.regional.italy import state_codes from erpnext.regional.italy import state_codes
import frappe import frappe
def execute(): def execute():
company = frappe.get_all('Company', filters = {'country': 'Italy'}) company = frappe.get_all('Company', filters = {'country': 'Italy'})
if not company: if not company:
@ -27,4 +26,12 @@ def execute():
frappe.db.sql(""" frappe.db.sql("""
UPDATE tabAddress set {condition} country_code = UPPER(ifnull((select code UPDATE tabAddress set {condition} country_code = UPPER(ifnull((select code
from `tabCountry` where name = `tabAddress`.country), '')) from `tabCountry` where name = `tabAddress`.country), ''))
where country_code is null and state_code is null
""".format(condition=condition)) """.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

@ -3,6 +3,8 @@ import json
from six import iteritems from six import iteritems
def execute(): def execute():
if "tax_type" not in frappe.db.get_table_columns("Item Tax"):
return
old_item_taxes = {} old_item_taxes = {}
item_tax_templates = {} item_tax_templates = {}
rename_template_to_untitled = [] rename_template_to_untitled = []
@ -40,7 +42,7 @@ def execute():
item.set("taxes", []) item.set("taxes", [])
item.append("taxes", {"item_tax_template": item_tax_template_name, "tax_category": ""}) item.append("taxes", {"item_tax_template": item_tax_template_name, "tax_category": ""})
item.save() item.save()
doctypes = [ doctypes = [
'Quotation', 'Sales Order', 'Delivery Note', 'Sales Invoice', 'Quotation', 'Sales Order', 'Delivery Note', 'Sales Invoice',
'Supplier Quotation', 'Purchase Order', 'Purchase Receipt', 'Purchase Invoice' 'Supplier Quotation', 'Purchase Order', 'Purchase Receipt', 'Purchase Invoice'

View File

@ -165,6 +165,13 @@ class Task(NestedSet):
self.update_nsm_model() self.update_nsm_model()
def update_status(self):
if self.status not in ('Cancelled', 'Completed') 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() @frappe.whitelist()
def check_if_child_exists(name): def check_if_child_exists(name):
child_tasks = frappe.get_all("Task", filters={"parent_task": name}) child_tasks = frappe.get_all("Task", filters={"parent_task": name})
@ -196,10 +203,9 @@ def set_multiple_status(names, status):
task.save() task.save()
def set_tasks_as_overdue(): def set_tasks_as_overdue():
frappe.db.sql("""update tabTask set `status`='Overdue' tasks = frappe.get_all("Task", filters={'status':['not in',['Cancelled', 'Completed']]})
where exp_end_date is not null for task in tasks:
and exp_end_date < CURDATE() frappe.get_doc("Task", task.name).update_status()
and `status` not in ('Completed', 'Cancelled')""")
@frappe.whitelist() @frappe.whitelist()
def get_children(doctype, parent, task=None, project=None, is_root=False): def get_children(doctype, parent, task=None, project=None, is_root=False):

View File

@ -10,8 +10,8 @@ frappe.ui.form.on("Timesheet", {
filters:{ filters:{
'status': 'Active' 'status': 'Active'
} }
} };
} };
frm.fields_dict['time_logs'].grid.get_field('task').get_query = function(frm, cdt, cdn) { frm.fields_dict['time_logs'].grid.get_field('task').get_query = function(frm, cdt, cdn) {
var child = locals[cdt][cdn]; var child = locals[cdt][cdn];
@ -20,22 +20,26 @@ frappe.ui.form.on("Timesheet", {
'project': child.project, 'project': child.project,
'status': ["!=", "Cancelled"] 'status': ["!=", "Cancelled"]
} }
} };
} };
frm.fields_dict['time_logs'].grid.get_field('project').get_query = function() { frm.fields_dict['time_logs'].grid.get_field('project').get_query = function() {
return{ return{
filters: { filters: {
'company': frm.doc.company 'company': frm.doc.company
} }
} };
} };
}, },
onload: function(frm){ onload: function(frm){
if (frm.doc.__islocal && frm.doc.time_logs) { if (frm.doc.__islocal && frm.doc.time_logs) {
calculate_time_and_amount(frm); calculate_time_and_amount(frm);
} }
if (frm.is_new()) {
set_employee_and_company(frm);
}
}, },
refresh: function(frm) { refresh: function(frm) {
@ -58,7 +62,7 @@ frappe.ui.form.on("Timesheet", {
if ((row.from_time <= frappe.datetime.now_datetime()) && !row.completed) { if ((row.from_time <= frappe.datetime.now_datetime()) && !row.completed) {
button = 'Resume Timer'; button = 'Resume Timer';
} }
}) });
frm.add_custom_button(__(button), function() { frm.add_custom_button(__(button), function() {
var flag = true; var flag = true;
@ -77,7 +81,7 @@ frappe.ui.form.on("Timesheet", {
erpnext.timesheet.timer(frm, row, timestamp); erpnext.timesheet.timer(frm, row, timestamp);
flag = false; flag = false;
} }
}) });
// If no activities found to start a timer, create new // If no activities found to start a timer, create new
if (flag) { if (flag) {
erpnext.timesheet.timer(frm); 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') frappe.db.get_value('Company', { 'company_name' : frm.doc.company }, 'standard_working_hours')
.then(({ message }) => { .then(({ message }) => {
(frappe.working_hours = message.standard_working_hours || 0); (frappe.working_hours = message.standard_working_hours || 0);
}); });
}, },
make_invoice: function(frm) { make_invoice: function(frm) {
@ -125,8 +129,8 @@ frappe.ui.form.on("Timesheet", {
frappe.set_route("Form", r.message.doctype, r.message.name); frappe.set_route("Form", r.message.doctype, r.message.name);
} }
} }
}) });
}) });
dialog.show(); dialog.show();
}, },
@ -136,7 +140,7 @@ frappe.ui.form.on("Timesheet", {
frm: frm frm: frm
}); });
}, },
}) });
frappe.ui.form.on("Timesheet Detail", { frappe.ui.form.on("Timesheet Detail", {
time_logs_remove: function(frm) { time_logs_remove: function(frm) {
@ -171,22 +175,22 @@ frappe.ui.form.on("Timesheet Detail", {
.find('[data-fieldname="timer"]') .find('[data-fieldname="timer"]')
.append(frappe.render_template("timesheet")); .append(frappe.render_template("timesheet"));
frm.trigger("control_timer"); frm.trigger("control_timer");
}) });
}, },
hours: function(frm, cdt, cdn) { hours: function(frm, cdt, cdn) {
calculate_end_time(frm, cdt, cdn) calculate_end_time(frm, cdt, cdn);
}, },
billing_hours: function(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) { 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) { 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) { billable: function(frm, cdt, cdn) {
@ -212,7 +216,7 @@ frappe.ui.form.on("Timesheet Detail", {
calculate_billing_costing_amount(frm, cdt, cdn); calculate_billing_costing_amount(frm, cdt, cdn);
} }
} }
}) });
} }
}); });
@ -240,23 +244,23 @@ var calculate_end_time = function(frm, cdt, cdn) {
frm._setting_hours = true; frm._setting_hours = true;
frappe.model.set_value(cdt, cdn, "to_time", frappe.model.set_value(cdt, cdn, "to_time",
d.format(frappe.defaultDatetimeFormat)).then(() => { d.format(frappe.defaultDatetimeFormat)).then(() => {
frm._setting_hours = false; frm._setting_hours = false;
}); });
} }
} }
} };
var update_billing_hours = function(frm, cdt, cdn){ var update_billing_hours = function(frm, cdt, cdn){
var child = locals[cdt][cdn]; var child = locals[cdt][cdn];
if(!child.billable) frappe.model.set_value(cdt, cdn, 'billing_hours', 0.0); if(!child.billable) frappe.model.set_value(cdt, cdn, 'billing_hours', 0.0);
} };
var update_time_rates = function(frm, cdt, cdn){ var update_time_rates = function(frm, cdt, cdn){
var child = locals[cdt][cdn]; var child = locals[cdt][cdn];
if(!child.billable){ if(!child.billable){
frappe.model.set_value(cdt, cdn, 'billing_rate', 0.0); frappe.model.set_value(cdt, cdn, 'billing_rate', 0.0);
} }
} };
var calculate_billing_costing_amount = function(frm, cdt, cdn){ var calculate_billing_costing_amount = function(frm, cdt, cdn){
var child = locals[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, 'billing_amount', billing_amount);
frappe.model.set_value(cdt, cdn, 'costing_amount', costing_amount); frappe.model.set_value(cdt, cdn, 'costing_amount', costing_amount);
calculate_time_and_amount(frm); calculate_time_and_amount(frm);
} };
var calculate_time_and_amount = function(frm) { var calculate_time_and_amount = function(frm) {
var tl = frm.doc.time_logs || []; 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_hours", total_working_hr);
frm.set_value("total_billable_amount", total_billable_amount); frm.set_value("total_billable_amount", total_billable_amount);
frm.set_value("total_costing_amount", total_costing_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);
}
});
};

1
erpnext/public/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
node_modules

View File

@ -158,6 +158,12 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
}; };
}); });
} }
if(frappe.meta.get_docfield(this.frm.doc.doctype, "pricing_rules")) {
this.frm.set_indicator_formatter('pricing_rule', function(doc) {
return (doc.rule_applied) ? "green" : "red";
});
}
}, },
onload: function() { onload: function() {
var me = this; var me = this;
@ -422,6 +428,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
method: "erpnext.stock.get_item_details.get_item_details", method: "erpnext.stock.get_item_details.get_item_details",
child: item, child: item,
args: { args: {
doc: me.frm.doc,
args: { args: {
item_code: item.item_code, item_code: item.item_code,
barcode: item.barcode, barcode: item.barcode,
@ -456,7 +463,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
cost_center: item.cost_center, cost_center: item.cost_center,
tax_category: me.frm.doc.tax_category, tax_category: me.frm.doc.tax_category,
item_tax_template: item.item_tax_template, item_tax_template: item.item_tax_template,
child_docname: item.name child_docname: item.name,
} }
}, },
@ -482,7 +489,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
} }
}, },
() => me.conversion_factor(doc, cdt, cdn, true), () => me.conversion_factor(doc, cdt, cdn, true),
() => me.update_free_items(item) () => me.validate_pricing_rule(item)
]); ]);
} }
} }
@ -1116,7 +1123,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
callback: function(r) { callback: function(r) {
if (!r.exc && r.message) { if (!r.exc && r.message) {
r.message.forEach(row_item => { r.message.forEach(row_item => {
me.update_free_items(row_item); me.validate_pricing_rule(row_item);
}); });
me._set_values_for_item_list(r.message); me._set_values_for_item_list(r.message);
me.calculate_taxes_and_totals(); me.calculate_taxes_and_totals();
@ -1139,14 +1146,12 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
return this.frm.call({ return this.frm.call({
method: "erpnext.accounts.doctype.pricing_rule.pricing_rule.apply_pricing_rule", method: "erpnext.accounts.doctype.pricing_rule.pricing_rule.apply_pricing_rule",
args: { args: args }, args: { args: args, doc: me.frm.doc },
callback: function(r) { callback: function(r) {
if (!r.exc && r.message) { if (!r.exc && r.message) {
me._set_values_for_item_list(r.message); me._set_values_for_item_list(r.message);
if(item) me.set_gross_profit(item); if(item) me.set_gross_profit(item);
if(calculate_taxes_and_totals) me.calculate_taxes_and_totals();
if(me.frm.doc.apply_discount_on) me.frm.trigger("apply_discount_on") if(me.frm.doc.apply_discount_on) me.frm.trigger("apply_discount_on")
me.update_free_items(item);
} }
} }
}); });
@ -1200,7 +1205,6 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
"warehouse": d.warehouse, "warehouse": d.warehouse,
"serial_no": d.serial_no, "serial_no": d.serial_no,
"price_list_rate": d.price_list_rate, "price_list_rate": d.price_list_rate,
"discount_percentage": d.discount_percentage || 0.0,
"conversion_factor": d.conversion_factor || 1.0 "conversion_factor": d.conversion_factor || 1.0
}); });
@ -1241,6 +1245,8 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
// if pricing rule set as blank from an existing value, apply price_list // if pricing rule set as blank from an existing value, apply price_list
if(!me.frm.doc.ignore_pricing_rule && existing_pricing_rule && !d.pricing_rules) { if(!me.frm.doc.ignore_pricing_rule && existing_pricing_rule && !d.pricing_rules) {
me.apply_price_list(frappe.get_doc(d.doctype, d.name)); me.apply_price_list(frappe.get_doc(d.doctype, d.name));
} else {
me.validate_pricing_rule(frappe.get_doc(d.doctype, d.name));
} }
} }
@ -1288,41 +1294,29 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
}); });
}, },
update_free_items: function(item) { validate_pricing_rule: function(item) {
var me = this; let me = this;
const fields = ["discount_percentage", "discount_amount", "pricing_rules"];
if (item.pricing_rules) { if (item.pricing_rules) {
frappe.call({ frappe.call({
method: "erpnext.accounts.doctype.pricing_rule.pricing_rule.get_free_items", method: "erpnext.accounts.doctype.pricing_rule.utils.validate_pricing_rule_for_different_cond",
args: { args: {
pricing_rules: item.pricing_rules, doc: me.frm.doc
item_row: item
}, },
callback: function(r) { callback: function(r) {
let items = []; if (r.message) {
let child = ''; r.message.items.forEach(d => {
me.frm.doc.items.forEach(row => {
me.frm.doc.items.map(d => { if(d.name == row.name) {
items[d.item_code] = d; fields.forEach(f => {
}); row[f] = d[f];
});
if(r.message && r.message.length) { }
r.message.forEach(d => {
// If free item is already exists
if(d.item_code in items &&
d.is_free_item && items[d.item_code].is_free_item) {
child = items[d.item_code];
} else {
child = frappe.model.add_child(me.frm.doc, item.doctype, "items");
}
$.each(d, function(k, v) {
child[k] = v;
}); });
me.frm.script_manager.trigger("price_list_rate", child.doctype, child.name);
}); });
me.trigger_price_list_rate();
} }
} }
}); });
@ -1337,9 +1331,29 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
me.frm.doc.items = items; me.frm.doc.items = items;
refresh_field('items'); refresh_field('items');
} else if(item.applied_on_items && item.apply_on) {
const applied_on_items = item.applied_on_items.split(',');
me.frm.doc.items.forEach(row => {
if(applied_on_items.includes(row[item.apply_on])) {
fields.forEach(f => {
row[f] = 0;
});
}
});
me.trigger_price_list_rate();
} }
}, },
trigger_price_list_rate: function() {
var me = this;
this.frm.doc.items.forEach(child_row => {
me.frm.script_manager.trigger("price_list_rate",
child_row.doctype, child_row.name);
})
},
validate_company_and_party: function() { validate_company_and_party: function() {
var me = this; var me = this;
var valid = true; var valid = true;
@ -1379,7 +1393,9 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
}, },
callback: function(r) { callback: function(r) {
if(!r.exc) { if(!r.exc) {
me.frm.set_value("taxes", r.message); for (let tax of r.message) {
me.frm.add_child("taxes", tax);
}
me.calculate_taxes_and_totals(); me.calculate_taxes_and_totals();
} }
} }

View File

@ -31,7 +31,7 @@
<base-image :src="image" :alt="title" /> <base-image :src="image" :alt="title" />
</div> </div>
</div> </div>
<div class="col-md-8"> <div class="col-md-8" style='padding-left: 30px;'>
<h2>{{ title }}</h2> <h2>{{ title }}</h2>
<div class="text-muted"> <div class="text-muted">
<slot name="detail-header-item"></slot> <slot name="detail-header-item"></slot>

View File

@ -1 +0,0 @@
/Users/shivammishra/Projects/ERPNext/yet-another-bench/apps/erpnext/node_modules

View File

@ -5,6 +5,7 @@ from __future__ import unicode_literals
import frappe import frappe
import unittest import unittest
from frappe.utils import getdate
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
from erpnext.stock.doctype.item.test_item import make_item from erpnext.stock.doctype.item.test_item import make_item
@ -12,10 +13,27 @@ import json
class TestGSTR3BReport(unittest.TestCase): class TestGSTR3BReport(unittest.TestCase):
def test_gstr_3b_report(self): def test_gstr_3b_report(self):
month_number_mapping = {
1: "January",
2: "February",
3: "March",
4: "April",
5: "May",
6: "June",
7: "July",
8: "August",
9: "September",
10: "October",
11: "November",
12: "December"
}
frappe.set_user("Administrator") frappe.set_user("Administrator")
frappe.db.sql("delete from `tabSales Invoice` where company='_Test Company GST'") frappe.db.sql("delete from `tabSales Invoice` where company='_Test Company GST'")
frappe.db.sql("delete from `tabPurchase Invoice` where company='_Test Company GST'") frappe.db.sql("delete from `tabPurchase Invoice` where company='_Test Company GST'")
frappe.db.sql("delete from `tabGSTR 3B Report` where company='_Test Company GST'")
make_company() make_company()
make_item("Milk", properties = {"is_nil_exempt": 1, "standard_rate": 0.000000}) make_item("Milk", properties = {"is_nil_exempt": 1, "standard_rate": 0.000000})
@ -33,8 +51,8 @@ class TestGSTR3BReport(unittest.TestCase):
"doctype": "GSTR 3B Report", "doctype": "GSTR 3B Report",
"company": "_Test Company GST", "company": "_Test Company GST",
"company_address": "_Test Address-Billing", "company_address": "_Test Address-Billing",
"year": "2019", "year": getdate().year,
"month": "March" "month": month_number_mapping.get(getdate().month)
}).insert() }).insert()
output = json.loads(report.json_output) output = json.loads(report.json_output)
@ -55,7 +73,6 @@ def make_sales_invoice():
income_account = 'Sales - _GST', income_account = 'Sales - _GST',
expense_account = 'Cost of Goods Sold - _GST', expense_account = 'Cost of Goods Sold - _GST',
cost_center = 'Main - _GST', cost_center = 'Main - _GST',
posting_date = '2019-03-10',
do_not_save=1 do_not_save=1
) )
@ -77,7 +94,6 @@ def make_sales_invoice():
income_account = 'Sales - _GST', income_account = 'Sales - _GST',
expense_account = 'Cost of Goods Sold - _GST', expense_account = 'Cost of Goods Sold - _GST',
cost_center = 'Main - _GST', cost_center = 'Main - _GST',
posting_date = '2019-03-10',
do_not_save=1 do_not_save=1
) )
@ -99,7 +115,6 @@ def make_sales_invoice():
income_account = 'Sales - _GST', income_account = 'Sales - _GST',
expense_account = 'Cost of Goods Sold - _GST', expense_account = 'Cost of Goods Sold - _GST',
cost_center = 'Main - _GST', cost_center = 'Main - _GST',
posting_date = '2019-03-10',
do_not_save=1 do_not_save=1
) )
@ -122,7 +137,6 @@ def make_sales_invoice():
income_account = 'Sales - _GST', income_account = 'Sales - _GST',
expense_account = 'Cost of Goods Sold - _GST', expense_account = 'Cost of Goods Sold - _GST',
cost_center = 'Main - _GST', cost_center = 'Main - _GST',
posting_date = '2019-03-10',
do_not_save=1 do_not_save=1
) )
si3.submit() si3.submit()
@ -135,7 +149,6 @@ def create_purchase_invoices():
currency = 'INR', currency = 'INR',
warehouse = 'Finished Goods - _GST', warehouse = 'Finished Goods - _GST',
cost_center = 'Main - _GST', cost_center = 'Main - _GST',
posting_date = '2019-03-10',
do_not_save=1, do_not_save=1,
) )
@ -157,7 +170,6 @@ def create_purchase_invoices():
currency = 'INR', currency = 'INR',
warehouse = 'Finished Goods - _GST', warehouse = 'Finished Goods - _GST',
cost_center = 'Main - _GST', cost_center = 'Main - _GST',
posting_date = '2019-03-10',
item = "Milk", item = "Milk",
do_not_save=1 do_not_save=1
) )

View File

@ -265,6 +265,7 @@ def make_custom_fields(update=True):
'Purchase Order Item': [hsn_sac_field, nil_rated_exempt, is_non_gst], 'Purchase Order Item': [hsn_sac_field, nil_rated_exempt, is_non_gst],
'Purchase Receipt Item': [hsn_sac_field, nil_rated_exempt, is_non_gst], 'Purchase Receipt Item': [hsn_sac_field, nil_rated_exempt, is_non_gst],
'Purchase Invoice Item': [hsn_sac_field, nil_rated_exempt, is_non_gst], 'Purchase Invoice Item': [hsn_sac_field, nil_rated_exempt, is_non_gst],
'Material Request Item': [hsn_sac_field, nil_rated_exempt, is_non_gst],
'Employee': [ 'Employee': [
dict(fieldname='ifsc_code', label='IFSC Code', dict(fieldname='ifsc_code', label='IFSC Code',
fieldtype='Data', insert_after='bank_ac_no', print_hide=1, fieldtype='Data', insert_after='bank_ac_no', print_hide=1,

View File

@ -95,13 +95,12 @@
<Cognome>{{ doc.customer_data.last_name }}</Cognome> <Cognome>{{ doc.customer_data.last_name }}</Cognome>
</Anagrafica> </Anagrafica>
{%- else %} {%- else %}
{%- if doc.customer_data.is_public_administration %}
<CodiceFiscale>{{ doc.customer_data.fiscal_code }}</CodiceFiscale>
{%- else %}
<IdFiscaleIVA> <IdFiscaleIVA>
<IdPaese>{{ doc.customer_address_data.country_code }}</IdPaese> <IdPaese>{{ doc.customer_address_data.country_code }}</IdPaese>
<IdCodice>{{ doc.tax_id | replace("IT","") }}</IdCodice> <IdCodice>{{ doc.tax_id | replace("IT","") }}</IdCodice>
</IdFiscaleIVA> </IdFiscaleIVA>
{%- if doc.customer_data.fiscal_code %}
<CodiceFiscale>{{ doc.customer_data.fiscal_code }}</CodiceFiscale>
{%- endif %} {%- endif %}
<Anagrafica> <Anagrafica>
<Denominazione>{{ doc.customer_name }}</Denominazione> <Denominazione>{{ doc.customer_name }}</Denominazione>
@ -128,22 +127,42 @@
<ImportoBollo>{{ format_float(doc.stamp_duty) }}</ImportoBollo> <ImportoBollo>{{ format_float(doc.stamp_duty) }}</ImportoBollo>
</DatiBollo> </DatiBollo>
{%- endif %} {%- 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> <Causale>VENDITA</Causale>
</DatiGeneraliDocumento> </DatiGeneraliDocumento>
{%- if doc.po_no %} {%- for po_no, po_date in doc.customer_po_data.items() %}
<DatiOrdineAcquisto> <DatiOrdineAcquisto>
<IdDocumento>{{ doc.po_no }}</IdDocumento> <IdDocumento>{{ po_no }}</IdDocumento>
{%- if doc.po_date %} <Data>{{ po_date }}</Data>
<Data>{{ doc.po_date }}</Data> </DatiOrdineAcquisto>
{%- endif %} {%- endfor %}
</DatiOrdineAcquisto>
{%- endif %}
{%- if doc.is_return and doc.return_against_unamended %} {%- if doc.is_return and doc.return_against_unamended %}
<DatiFattureCollegate> <DatiFattureCollegate>
<IdDocumento>{{ doc.return_against_unamended }}</IdDocumento> <IdDocumento>{{ doc.return_against_unamended }}</IdDocumento>
</DatiFattureCollegate> </DatiFattureCollegate>
{%- endif %} {%- 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 %} {%- if doc.shipping_address_data %}
<DatiTrasporto> <DatiTrasporto>
<IndirizzoResa> <IndirizzoResa>
@ -165,7 +184,11 @@
<UnitaMisura>{{ item.stock_uom }}</UnitaMisura> <UnitaMisura>{{ item.stock_uom }}</UnitaMisura>
<PrezzoUnitario>{{ format_float(item.price_list_rate or item.rate) }}</PrezzoUnitario> <PrezzoUnitario>{{ format_float(item.price_list_rate or item.rate) }}</PrezzoUnitario>
{{ render_discount_or_margin(item) }} {{ render_discount_or_margin(item) }}
<PrezzoTotale>{{ format_float(item.amount) }}</PrezzoTotale> {%- if (item.discount_amount or item.rate_with_margin) %}
<PrezzoTotale>{{ format_float(item.net_amount) }}</PrezzoTotale>
{%- else %}
<PrezzoTotale>{{ format_float(item.amount) }}</PrezzoTotale>
{%- endif %}
<AliquotaIVA>{{ format_float(item.tax_rate) }}</AliquotaIVA> <AliquotaIVA>{{ format_float(item.tax_rate) }}</AliquotaIVA>
{%- if item.tax_exemption_reason %} {%- if item.tax_exemption_reason %}
<Natura>{{ item.tax_exemption_reason.split("-")[0] }}</Natura> <Natura>{{ item.tax_exemption_reason.split("-")[0] }}</Natura>
@ -199,7 +222,9 @@
<ModalitaPagamento>{{ payment_term.mode_of_payment_code.split("-")[0] }}</ModalitaPagamento> <ModalitaPagamento>{{ payment_term.mode_of_payment_code.split("-")[0] }}</ModalitaPagamento>
<DataScadenzaPagamento>{{ payment_term.due_date }}</DataScadenzaPagamento> <DataScadenzaPagamento>{{ payment_term.due_date }}</DataScadenzaPagamento>
<ImportoPagamento>{{ format_float(payment_term.payment_amount) }}</ImportoPagamento> <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 %} {%- if payment_term.bank_account_iban %}
<IBAN>{{ payment_term.bank_account_iban }}</IBAN> <IBAN>{{ payment_term.bank_account_iban }}</IBAN>
<ABI>{{ payment_term.bank_account_iban[5:10] }}</ABI> <ABI>{{ payment_term.bank_account_iban[5:10] }}</ABI>

View File

@ -3,15 +3,26 @@ erpnext.setup_e_invoice_button = (doctype) => {
refresh: (frm) => { refresh: (frm) => {
if(frm.doc.docstatus == 1) { if(frm.doc.docstatus == 1) {
frm.add_custom_button('Generate E-Invoice', () => { frm.add_custom_button('Generate E-Invoice', () => {
var w = window.open( frm.call({
frappe.urllib.get_full_url( method: "erpnext.regional.italy.utils.generate_single_invoice",
"/api/method/erpnext.regional.italy.utils.generate_single_invoice?" args: {
+ "docname=" + frm.doc.name docname: frm.doc.name
) },
) callback: function(r) {
if (!w) { frm.reload_doc();
frappe.msgprint(__("Please enable pop-ups")); return; 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") 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 = { custom_fields = {
'Company': [ 'Company': [
dict(fieldname='sb_e_invoicing', label='E-Invoicing', dict(fieldname='sb_e_invoicing', label='E-Invoicing',
@ -128,7 +144,7 @@ def make_custom_fields(update=True):
'Purchase Invoice Item': invoice_item_fields, 'Purchase Invoice Item': invoice_item_fields,
'Sales Order Item': invoice_item_fields, 'Sales Order Item': invoice_item_fields,
'Delivery Note 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, 'Quotation Item': invoice_item_fields,
'Purchase Order Item': invoice_item_fields, 'Purchase Order Item': invoice_item_fields,
'Purchase Receipt Item': invoice_item_fields, 'Purchase Receipt Item': invoice_item_fields,

View File

@ -5,6 +5,7 @@ from frappe.utils import flt, cstr
from erpnext.controllers.taxes_and_totals import get_itemised_tax from erpnext.controllers.taxes_and_totals import get_itemised_tax
from frappe import _ from frappe import _
from frappe.core.doctype.file.file import remove_file from frappe.core.doctype.file.file import remove_file
from six import string_types
from frappe.desk.form.load import get_attachments from frappe.desk.form.load import get_attachments
from erpnext.regional.italy import state_codes 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: if item.tax_rate == 0.0 and item.tax_amount == 0.0:
item.tax_exemption_reason = tax_data["0.0"]["tax_exemption_reason"] 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 return invoice
def get_conditions(filters): def get_conditions(filters):
@ -134,6 +143,7 @@ def get_invoice_summary(items, taxes):
idx=len(items)+1, idx=len(items)+1,
item_code=reference_row.description, item_code=reference_row.description,
item_name=reference_row.description, item_name=reference_row.description,
description=reference_row.description,
rate=reference_row.tax_amount, rate=reference_row.tax_amount,
qty=1.0, qty=1.0,
amount=reference_row.tax_amount, amount=reference_row.tax_amount,
@ -142,7 +152,7 @@ def get_invoice_summary(items, taxes):
tax_amount=(reference_row.tax_amount * tax.rate) / 100, tax_amount=(reference_row.tax_amount * tax.rate) / 100,
net_amount=reference_row.tax_amount, net_amount=reference_row.tax_amount,
taxable_amount=reference_row.tax_amount, taxable_amount=reference_row.tax_amount,
item_tax_rate="{}", item_tax_rate={tax.account_head: tax.rate},
charges=True charges=True
) )
) )
@ -150,10 +160,16 @@ def get_invoice_summary(items, taxes):
#Check item tax rates if tax rate is zero. #Check item tax rates if tax rate is zero.
if tax.rate == 0: if tax.rate == 0:
for item in items: for item in items:
item_tax_rate = json.loads(item.item_tax_rate) item_tax_rate = item.item_tax_rate
if tax.account_head in 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]) 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]["tax_amount"] += item.tax_amount
summary_data[key]["taxable_amount"] += item.net_amount summary_data[key]["taxable_amount"] += item.net_amount
if key == "0.0": if key == "0.0":
@ -198,19 +214,25 @@ def sales_invoice_validate(doc):
else: else:
doc.company_fiscal_regime = company_fiscal_regime 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: 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")) frappe.throw(_("Please set either the Tax ID or Fiscal Code on Company '%s'" % doc.company), title=_("E-Invoicing Information Missing"))
#Validate customer details #Validate customer details
customer_type, is_public_administration = frappe.db.get_value("Customer", doc.customer, ["customer_type", "is_public_administration"]) customer = frappe.get_doc("Customer", doc.customer)
if customer_type == _("Individual"):
if customer.customer_type == _("Individual"):
doc.customer_fiscal_code = customer.fiscal_code
if not doc.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")) frappe.throw(_("Please set Fiscal Code for the customer '%s'" % doc.customer), title=_("E-Invoicing Information Missing"))
else: else:
if is_public_administration: if customer.is_public_administration:
doc.customer_fiscal_code = customer.fiscal_code
if not doc.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")) frappe.throw(_("Please set Fiscal Code for the public administration '%s'" % doc.customer), title=_("E-Invoicing Information Missing"))
else: else:
doc.tax_id = customer.tax_id
if not doc.tax_id: if not doc.tax_id:
frappe.throw(_("Please set Tax ID for the customer '%s'" % doc.customer), title=_("E-Invoicing Information Missing")) frappe.throw(_("Please set Tax ID for the customer '%s'" % doc.customer), title=_("E-Invoicing Information Missing"))
@ -276,13 +298,18 @@ def prepare_and_attach_invoice(doc, replace=False):
def generate_single_invoice(docname): def generate_single_invoice(docname):
doc = frappe.get_doc("Sales Invoice", docname) doc = frappe.get_doc("Sales Invoice", docname)
e_invoice = prepare_and_attach_invoice(doc, True) e_invoice = prepare_and_attach_invoice(doc, True)
return e_invoice.file_name
@frappe.whitelist()
def download_e_invoice_file(file_name):
content = None 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() 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.filecontent = content
frappe.local.response.type = "download" frappe.local.response.type = "download"

View File

@ -4,7 +4,7 @@
frappe.query_reports["GSTR-1"] = { frappe.query_reports["GSTR-1"] = {
"filters": [ "filters": [
{ {
"fieldname":"company", "fieldname": "company",
"label": __("Company"), "label": __("Company"),
"fieldtype": "Link", "fieldtype": "Link",
"options": "Company", "options": "Company",
@ -12,22 +12,22 @@ frappe.query_reports["GSTR-1"] = {
"default": frappe.defaults.get_user_default("Company") "default": frappe.defaults.get_user_default("Company")
}, },
{ {
"fieldname":"company_address", "fieldname": "company_address",
"label": __("Address"), "label": __("Address"),
"fieldtype": "Link", "fieldtype": "Link",
"options": "Address", "options": "Address",
"get_query": function() { "get_query": function () {
var company = frappe.query_report.get_filter_value('company'); var company = frappe.query_report.get_filter_value('company');
if (company) { if (company) {
return { return {
"query": 'frappe.contacts.doctype.address.address.address_query', "query": 'frappe.contacts.doctype.address.address.address_query',
"filters": { link_doctype: 'Company', link_name: company} "filters": { link_doctype: 'Company', link_name: company }
}; };
} }
} }
}, },
{ {
"fieldname":"from_date", "fieldname": "from_date",
"label": __("From Date"), "label": __("From Date"),
"fieldtype": "Date", "fieldtype": "Date",
"reqd": 1, "reqd": 1,
@ -35,19 +35,34 @@ frappe.query_reports["GSTR-1"] = {
"width": "80" "width": "80"
}, },
{ {
"fieldname":"to_date", "fieldname": "to_date",
"label": __("To Date"), "label": __("To Date"),
"fieldtype": "Date", "fieldtype": "Date",
"reqd": 1, "reqd": 1,
"default": frappe.datetime.get_today() "default": frappe.datetime.get_today()
}, },
{ {
"fieldname":"type_of_business", "fieldname": "type_of_business",
"label": __("Type of Business"), "label": __("Type of Business"),
"fieldtype": "Select", "fieldtype": "Select",
"reqd": 1, "reqd": 1,
"options": ["B2B", "B2C Large", "B2C Small","CDNR", "EXPORT"], "options": ["B2B", "B2C Large", "B2C Small", "CDNR", "EXPORT"],
"default": "B2B" "default": "B2B"
} }
] ],
onload: function (report) {
report.page.add_inner_button(__("Download as Json"), function () {
var filters = report.get_values();
const args = {
cmd: 'erpnext.regional.report.gstr_1.gstr_1.get_json',
data: report.data,
report_name: report.report_name,
filters: filters
};
open_url_post(frappe.request.url, args);
});
}
} }

View File

@ -4,9 +4,10 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe, json import frappe, json
from frappe import _ from frappe import _
from frappe.utils import flt, formatdate from frappe.utils import flt, formatdate, now_datetime, getdate
from datetime import date from datetime import date
from six import iteritems from six import iteritems
from erpnext.regional.doctype.gstr_3b_report.gstr_3b_report import get_period
def execute(filters=None): def execute(filters=None):
return Gstr1Report(filters).run() return Gstr1Report(filters).run()
@ -38,7 +39,7 @@ class Gstr1Report(object):
shipping_bill_date, shipping_bill_date,
reason_for_issuing_document reason_for_issuing_document
""" """
self.customer_type = "Company" if self.filters.get("type_of_business") == "B2B" else "Individual" # self.customer_type = "Company" if self.filters.get("type_of_business") == "B2B" else "Individual"
def run(self): def run(self):
self.get_columns() self.get_columns()
@ -113,9 +114,14 @@ class Gstr1Report(object):
if self.filters.get(opts[0]): if self.filters.get(opts[0]):
conditions += opts[1] conditions += opts[1]
customers = frappe.get_all("Customer", filters={"customer_type": self.customer_type}) # customers = frappe.get_all("Customer", filters={"customer_type": self.customer_type})
if self.filters.get("type_of_business") == "B2B": if self.filters.get("type_of_business") == "B2B":
customers = frappe.get_all("Customer",
filters={
"gst_category": ["in", ["Registered Regular", "Deemed Export", "SEZ"]]
})
conditions += """ and ifnull(gst_category, '') != 'Overseas' and is_return != 1 conditions += """ and ifnull(gst_category, '') != 'Overseas' and is_return != 1
and customer in ({0})""".format(", ".join([frappe.db.escape(c.name) for c in customers])) and customer in ({0})""".format(", ".join([frappe.db.escape(c.name) for c in customers]))
@ -124,6 +130,11 @@ class Gstr1Report(object):
if not b2c_limit: if not b2c_limit:
frappe.throw(_("Please set B2C Limit in GST Settings.")) frappe.throw(_("Please set B2C Limit in GST Settings."))
customers = frappe.get_all("Customer",
filters={
"gst_category": ["in", ["Unregistered"]]
})
if self.filters.get("type_of_business") == "B2C Large": if self.filters.get("type_of_business") == "B2C Large":
conditions += """ and SUBSTR(place_of_supply, 1, 2) != SUBSTR(company_gstin, 1, 2) conditions += """ and SUBSTR(place_of_supply, 1, 2) != SUBSTR(company_gstin, 1, 2)
and grand_total > {0} and is_return != 1 and customer in ({1})""".\ and grand_total > {0} and is_return != 1 and customer in ({1})""".\
@ -494,3 +505,158 @@ class Gstr1Report(object):
} }
] ]
self.columns = self.invoice_columns + self.tax_columns + self.other_columns self.columns = self.invoice_columns + self.tax_columns + self.other_columns
@frappe.whitelist()
def get_json():
data = frappe._dict(frappe.local.form_dict)
del data["cmd"]
if "csrf_token" in data:
del data["csrf_token"]
filters = json.loads(data["filters"])
report_data = json.loads(data["data"])
report_name = data["report_name"]
gstin = get_company_gstin_number(filters["company"])
fp = "%02d%s" % (getdate(filters["to_date"]).month, getdate(filters["to_date"]).year)
gst_json = {"gstin": "", "version": "GST2.2.9",
"hash": "hash", "gstin": gstin, "fp": fp}
res = {}
if filters["type_of_business"] == "B2B":
for item in report_data:
res.setdefault(item["customer_gstin"], {}).setdefault(item["invoice_number"],[]).append(item)
out = get_b2b_json(res, gstin)
gst_json["b2b"] = out
elif filters["type_of_business"] == "B2C Large":
for item in report_data:
res.setdefault(item["place_of_supply"], []).append(item)
out = get_b2cl_json(res, gstin)
gst_json["b2cl"] = out
elif filters["type_of_business"] == "EXPORT":
for item in report_data:
res.setdefault(item["export_type"], []).append(item)
out = get_export_json(res)
gst_json["exp"] = out
download_json_file(report_name, filters["type_of_business"], gst_json)
def get_b2b_json(res, gstin):
inv_type, out = {"Registered Regular": "R", "Deemed Export": "DE", "URD": "URD", "SEZ": "SEZ"}, []
for gst_in in res:
b2b_item, inv = {"ctin": gst_in, "inv": []}, []
if not gst_in: continue
for number, invoice in iteritems(res[gst_in]):
inv_item = get_basic_invoice_detail(invoice[0])
inv_item["pos"] = "%02d" % int(invoice[0]["place_of_supply"].split('-')[0])
inv_item["rchrg"] = invoice[0]["reverse_charge"]
inv_item["inv_typ"] = inv_type.get(invoice[0].get("gst_category", ""),"")
if inv_item["pos"]=="00": continue
inv_item["itms"] = []
for item in invoice:
inv_item["itms"].append(get_rate_and_tax_details(item, gstin))
inv.append(inv_item)
if not inv: continue
b2b_item["inv"] = inv
out.append(b2b_item)
return out
def get_b2cl_json(res, gstin):
out = []
for pos in res:
b2cl_item, inv = {"pos": "%02d" % int(pos.split('-')[0]), "inv": []}, []
for row in res[pos]:
inv_item = get_basic_invoice_detail(row)
if row.get("sale_from_bonded_wh"):
inv_item["inv_typ"] = "CBW"
inv_item["itms"] = [get_rate_and_tax_details(row, gstin)]
inv.append(inv_item)
b2cl_item["inv"] = inv
out.append(b2cl_item)
return out
def get_export_json(res):
out = []
for exp_type in res:
exp_item, inv = {"exp_typ": exp_type, "inv": []}, []
for row in res[exp_type]:
inv_item = get_basic_invoice_detail(row)
inv_item["itms"] = [{
"txval": flt(row["taxable_value"], 2),
"rt": row["rate"] or 0,
"iamt": 0,
"csamt": 0
}]
inv.append(inv_item)
exp_item["inv"] = inv
out.append(exp_item)
return out
def get_basic_invoice_detail(row):
return {
"inum": row["invoice_number"],
"idt": getdate(row["posting_date"]).strftime('%d-%m-%Y'),
"val": flt(row["invoice_value"], 2)
}
def get_rate_and_tax_details(row, gstin):
itm_det = {"txval": flt(row["taxable_value"], 2),
"rt": row["rate"],
"csamt": (flt(row.get("cess_amount"), 2) or 0)
}
# calculate rate
num = 1 if not row["rate"] else "%d%02d" % (row["rate"], 1)
rate = row.get("rate") or 0
# calculate tax amount added
tax = flt((row["taxable_value"]*rate)/100.0, 2)
frappe.errprint([tax, tax/2])
if row.get("customer_gstin") and gstin[0:2] == row["customer_gstin"][0:2]:
itm_det.update({"camt": flt(tax/2.0, 2), "samt": flt(tax/2.0, 2)})
else:
itm_det.update({"iamt": tax})
return {"num": int(num), "itm_det": itm_det}
def get_company_gstin_number(company):
filters = [
["is_your_company_address", "=", 1],
["Dynamic Link", "link_doctype", "=", "Company"],
["Dynamic Link", "link_name", "=", company],
["Dynamic Link", "parenttype", "=", "Address"],
]
gstin = frappe.get_all("Address", filters=filters, fields=["gstin"])
if gstin:
return gstin[0]["gstin"]
else:
frappe.throw(_("No GST No. found for the Company."))
def download_json_file(filename, report_type, data):
''' download json content in a file '''
frappe.response['filename'] = frappe.scrub("{0} {1}".format(filename, report_type)) + '.json'
frappe.response['filecontent'] = json.dumps(data)
frappe.response['content_type'] = 'application/json'
frappe.response['type'] = 'download'

View File

@ -97,6 +97,8 @@ class Quotation(SellingController):
self.update_lead() self.update_lead()
def on_cancel(self): def on_cancel(self):
super(Quotation, self).on_cancel()
#update enquiry status #update enquiry status
self.set_status(update=True) self.set_status(update=True)
self.update_opportunity() self.update_opportunity()

View File

@ -183,6 +183,8 @@ class SalesOrder(SellingController):
self.update_blanket_order() self.update_blanket_order()
def on_cancel(self): def on_cancel(self):
super(SalesOrder, self).on_cancel()
# Cannot cancel closed SO # Cannot cancel closed SO
if self.status == 'Closed': if self.status == 'Closed':
frappe.throw(_("Closed order cannot be cancelled. Unclose to cancel.")) frappe.throw(_("Closed order cannot be cancelled. Unclose to cancel."))

View File

@ -2,7 +2,7 @@
# License: GNU General Public License v3. See license.txt # License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe.utils import flt, add_days from frappe.utils import flt, add_days, nowdate
import frappe.permissions import frappe.permissions
import unittest import unittest
from erpnext.selling.doctype.sales_order.sales_order \ from erpnext.selling.doctype.sales_order.sales_order \
@ -13,7 +13,6 @@ from erpnext.controllers.accounts_controller import update_child_qty_rate
import json import json
from erpnext.selling.doctype.sales_order.sales_order import make_raw_material_request from erpnext.selling.doctype.sales_order.sales_order import make_raw_material_request
class TestSalesOrder(unittest.TestCase): class TestSalesOrder(unittest.TestCase):
def tearDown(self): def tearDown(self):
frappe.set_user("Administrator") frappe.set_user("Administrator")
@ -703,6 +702,28 @@ class TestSalesOrder(unittest.TestCase):
se.cancel() se.cancel()
self.assertFalse(frappe.db.exists("Serial No", {"sales_order": so.name})) self.assertFalse(frappe.db.exists("Serial No", {"sales_order": so.name}))
def test_advance_payment_entry_unlink_against_sales_order(self):
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
frappe.db.set_value("Accounts Settings", "Accounts Settings",
"unlink_advance_payment_on_cancelation_of_order", 0)
so = make_sales_order()
pe = get_payment_entry("Sales Order", so.name, bank_account="_Test Bank - _TC")
pe.reference_no = "1"
pe.reference_date = nowdate()
pe.paid_from_account_currency = so.currency
pe.paid_to_account_currency = so.currency
pe.source_exchange_rate = 1
pe.target_exchange_rate = 1
pe.paid_amount = so.grand_total
pe.save(ignore_permissions=True)
pe.submit()
so_doc = frappe.get_doc('Sales Order', so.name)
self.assertRaises(frappe.LinkExistsError, so_doc.cancel)
def test_request_for_raw_materials(self): def test_request_for_raw_materials(self):
from erpnext.stock.doctype.item.test_item import make_item from erpnext.stock.doctype.item.test_item import make_item
item = make_item("_Test Finished Item", {"is_stock_item": 1, item = make_item("_Test Finished Item", {"is_stock_item": 1,

View File

@ -233,9 +233,21 @@ erpnext.pos.PointOfSale = class PointOfSale {
} else { } else {
this.update_item_in_frm(item, field, value) this.update_item_in_frm(item, field, value)
.then(() => { .then(() => {
// update cart frappe.dom.unfreeze();
this.update_cart_data(item); frappe.run_serially([
this.set_form_action(); () => {
let items = this.frm.doc.items.map(item => item.name);
if (items && items.length > 0 && items.includes(item.name)) {
this.frm.doc.items.forEach(item_row => {
// update cart
this.on_qty_change(item_row);
});
} else {
this.on_qty_change(item);
}
},
() => this.post_qty_change(item)
]);
}); });
} }
return; return;
@ -251,7 +263,28 @@ erpnext.pos.PointOfSale = class PointOfSale {
frappe.flags.hide_serial_batch_dialog = true; frappe.flags.hide_serial_batch_dialog = true;
frappe.run_serially([ frappe.run_serially([
() => this.frm.script_manager.trigger('item_code', item.doctype, item.name), () => {
this.frm.script_manager.trigger('item_code', item.doctype, item.name)
.then(() => {
this.frm.script_manager.trigger('qty', item.doctype, item.name)
.then(() => {
frappe.run_serially([
() => {
let items = this.frm.doc.items.map(i => i.name);
if (items && items.length > 0 && items.includes(item.name)) {
this.frm.doc.items.forEach(item_row => {
// update cart
this.on_qty_change(item_row);
});
} else {
this.on_qty_change(item);
}
},
() => this.post_qty_change(item)
]);
});
});
},
() => { () => {
const show_dialog = item.has_serial_no || item.has_batch_no; const show_dialog = item.has_serial_no || item.has_batch_no;
@ -261,14 +294,25 @@ erpnext.pos.PointOfSale = class PointOfSale {
(item.has_serial_no) || (item.actual_batch_qty != item.actual_qty)) ) { (item.has_serial_no) || (item.actual_batch_qty != item.actual_qty)) ) {
// check has serial no/batch no and update cart // check has serial no/batch no and update cart
this.select_batch_and_serial_no(item); this.select_batch_and_serial_no(item);
} else {
// update cart
this.update_cart_data(item);
} }
} }
]); ]);
} }
on_qty_change(item) {
frappe.run_serially([
() => this.update_cart_data(item),
]);
}
post_qty_change(item) {
this.cart.update_taxes_and_totals();
this.cart.update_grand_total();
this.cart.update_qty_total();
this.cart.scroll_to_item(item.item_code);
this.set_form_action();
}
select_batch_and_serial_no(row) { select_batch_and_serial_no(row) {
frappe.dom.unfreeze(); frappe.dom.unfreeze();
@ -283,7 +327,8 @@ erpnext.pos.PointOfSale = class PointOfSale {
frappe.model.clear_doc(item.doctype, item.name); frappe.model.clear_doc(item.doctype, item.name);
} }
}, },
() => this.update_cart_data(item) () => this.update_cart_data(item),
() => this.post_qty_change(item)
]); ]);
}); });
}) })
@ -300,9 +345,6 @@ erpnext.pos.PointOfSale = class PointOfSale {
update_cart_data(item) { update_cart_data(item) {
this.cart.add_item(item); this.cart.add_item(item);
this.cart.update_taxes_and_totals();
this.cart.update_grand_total();
this.cart.update_qty_total();
frappe.dom.unfreeze(); frappe.dom.unfreeze();
} }
@ -446,16 +488,15 @@ erpnext.pos.PointOfSale = class PointOfSale {
} }
setup_company() { setup_company() {
this.company = frappe.sys_defaults.company;
return new Promise(resolve => { return new Promise(resolve => {
if(!this.company) { if(!frappe.sys_defaults.company) {
frappe.prompt({fieldname:"company", options: "Company", fieldtype:"Link", frappe.prompt({fieldname:"company", options: "Company", fieldtype:"Link",
label: __("Select Company"), reqd: 1}, (data) => { label: __("Select Company"), reqd: 1}, (data) => {
this.company = data.company; this.company = data.company;
resolve(this.company); resolve(this.company);
}, __("Select Company")); }, __("Select Company"));
} else { } else {
resolve(this.company); resolve();
} }
}) })
} }
@ -509,7 +550,9 @@ erpnext.pos.PointOfSale = class PointOfSale {
} }
set_pos_profile_data() { set_pos_profile_data() {
this.frm.doc.company = this.company; if (this.company) {
this.frm.doc.company = this.company;
}
return new Promise(resolve => { return new Promise(resolve => {
return this.frm.call({ return this.frm.call({
@ -953,7 +996,6 @@ class POSCart {
$item.appendTo(this.$cart_items); $item.appendTo(this.$cart_items);
} }
this.highlight_item(item.item_code); this.highlight_item(item.item_code);
this.scroll_to_item(item.item_code);
} }
update_item(item) { update_item(item) {
@ -1206,7 +1248,10 @@ class POSItems {
clearTimeout(this.last_search); clearTimeout(this.last_search);
this.last_search = setTimeout(() => { this.last_search = setTimeout(() => {
const search_term = e.target.value; const search_term = e.target.value;
this.filter_items({ search_term }); const item_group = this.item_group_field ?
this.item_group_field.get_value() : '';
this.filter_items({ search_term:search_term, item_group: item_group});
}, 300); }, 300);
}); });

View File

@ -29,7 +29,7 @@ def get_items(start, page_length, price_list, item_group, search_value="", pos_p
batch_no = data.get("batch_no") if data.get("batch_no") else "" batch_no = data.get("batch_no") if data.get("batch_no") else ""
barcode = data.get("barcode") if data.get("barcode") else "" barcode = data.get("barcode") if data.get("barcode") else ""
item_code, condition = get_conditions(item_code, serial_no, batch_no, barcode) condition = get_conditions(item_code, serial_no, batch_no, barcode)
if pos_profile: if pos_profile:
condition += get_item_group_condition(pos_profile) condition += get_item_group_condition(pos_profile)
@ -86,7 +86,6 @@ def get_items(start, page_length, price_list, item_group, search_value="", pos_p
and {condition} limit {start}, {page_length}""".format and {condition} limit {start}, {page_length}""".format
(start=start,page_length=page_length,lft=lft, rgt=rgt, condition=condition), (start=start,page_length=page_length,lft=lft, rgt=rgt, condition=condition),
{ {
'item_code': item_code,
'price_list': price_list, 'price_list': price_list,
'warehouse': warehouse 'warehouse': warehouse
} , as_dict=1) } , as_dict=1)
@ -133,12 +132,10 @@ def search_serial_or_batch_or_barcode_number(search_value):
def get_conditions(item_code, serial_no, batch_no, barcode): def get_conditions(item_code, serial_no, batch_no, barcode):
if serial_no or batch_no or barcode: if serial_no or batch_no or barcode:
return frappe.db.escape(item_code), "i.name = %(item_code)s" return "i.name = {0}".format(frappe.db.escape(item_code))
condition = """(i.name like %(item_code)s return """(i.name like {item_code}
or i.item_name like %(item_code)s)""" or i.item_name like {item_code})""".format(item_code = frappe.db.escape('%' + item_code + '%'))
return frappe.db.escape('%' + item_code + '%'), condition
def get_item_group_condition(pos_profile): def get_item_group_condition(pos_profile):
cond = "and 1=1" cond = "and 1=1"

View File

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

View File

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

View File

@ -177,7 +177,7 @@ class DeliveryNote(SellingController):
frappe.msgprint(_("Note: Item {0} entered multiple times").format(d.item_code)) frappe.msgprint(_("Note: Item {0} entered multiple times").format(d.item_code))
else: else:
chk_dupl_itm.append(f) chk_dupl_itm.append(f)
#Customer Provided parts will have zero valuation rate #Customer Provided parts will have zero valuation rate
if frappe.db.get_value('Item', d.item_code, 'is_customer_provided_item'): if frappe.db.get_value('Item', d.item_code, 'is_customer_provided_item'):
d.allow_zero_valuation_rate = 1 d.allow_zero_valuation_rate = 1
@ -223,6 +223,8 @@ class DeliveryNote(SellingController):
self.make_gl_entries() self.make_gl_entries()
def on_cancel(self): def on_cancel(self):
super(DeliveryNote, self).on_cancel()
self.check_close_sales_order("against_sales_order") self.check_close_sales_order("against_sales_order")
self.check_next_docstatus() self.check_next_docstatus()

View File

@ -118,7 +118,6 @@ class Item(WebsiteGenerator):
self.validate_has_variants() self.validate_has_variants()
self.validate_stock_exists_for_template_item() self.validate_stock_exists_for_template_item()
self.validate_asset_exists_for_serialized_asset()
self.validate_attributes() self.validate_attributes()
self.validate_variant_attributes() self.validate_variant_attributes()
self.validate_website_image() self.validate_website_image()
@ -128,6 +127,7 @@ class Item(WebsiteGenerator):
self.validate_uom_conversion_factor() self.validate_uom_conversion_factor()
self.validate_item_defaults() self.validate_item_defaults()
self.validate_customer_provided_part() self.validate_customer_provided_part()
self.validate_stock_for_has_batch_and_has_serial()
if not self.get("__islocal"): if not self.get("__islocal"):
self.old_item_group = frappe.db.get_value(self.doctype, self.name, "item_group") self.old_item_group = frappe.db.get_value(self.doctype, self.name, "item_group")
@ -759,12 +759,6 @@ class Item(WebsiteGenerator):
frappe.throw( frappe.throw(
_('Cannot change Attributes after stock transaction. Make a new Item and transfer stock to the new Item')) _('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): def asset_exists(self):
if not hasattr(self, '_asset_created'): if not hasattr(self, '_asset_created'):
self._asset_created = frappe.db.get_all("Asset", self._asset_created = frappe.db.get_all("Asset",
@ -791,6 +785,9 @@ class Item(WebsiteGenerator):
d.conversion_factor = value d.conversion_factor = value
def validate_attributes(self): def validate_attributes(self):
if not self.variant_based_on:
self.variant_based_on = 'Item Attribute'
if (self.has_variants or self.variant_of) and self.variant_based_on == 'Item Attribute': if (self.has_variants or self.variant_of) and self.variant_based_on == 'Item Attribute':
attributes = [] attributes = []
if not self.attributes: if not self.attributes:
@ -813,7 +810,7 @@ class Item(WebsiteGenerator):
variant = get_variant(self.variant_of, args, self.name) variant = get_variant(self.variant_of, args, self.name)
if variant: if variant:
frappe.throw(_("Item variant {0} exists with same attributes") frappe.throw(_("Item variant {0} exists with same attributes")
.format(variant), ItemVariantExistsError) .format(variant), ItemVariantExistsError)
validate_item_variant_attributes(self, args) validate_item_variant_attributes(self, args)
@ -821,6 +818,11 @@ class Item(WebsiteGenerator):
for d in self.attributes: for d in self.attributes:
d.variant_of = self.variant_of d.variant_of = self.variant_of
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): def get_timeline_data(doctype, name):
'''returns timeline data based on stock ledger entry''' '''returns timeline data based on stock ledger entry'''

View File

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

View File

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

View File

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

View File

@ -1,31 +1,32 @@
{ {
"add_total_row": 1, "add_total_row": 1,
"apply_user_permissions": 1, "creation": "2018-01-09 18:38:23.540100",
"creation": "2018-01-09 18:38:23.540100", "disable_prepared_report": 0,
"disabled": 0, "disabled": 0,
"docstatus": 0, "docstatus": 0,
"doctype": "Report", "doctype": "Report",
"idx": 0, "idx": 0,
"is_standard": "Yes", "is_standard": "Yes",
"modified": "2018-01-15 15:04:15.340151", "modified": "2019-04-01 22:10:09.829361",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Ordered Items To Be Delivered", "name": "Ordered Items To Be Delivered",
"owner": "Administrator", "owner": "Administrator",
"query": "select \n `tabSales Order`.`name` as \"Sales Order:Link/Sales Order:120\",\n `tabSales Order`.`customer` as \"Customer:Link/Customer:120\",\n `tabSales Order`.`customer_name` as \"Customer Name::150\",\n `tabSales Order`.`transaction_date` as \"Date:Date\",\n `tabSales Order`.`project` as \"Project:Link/Project:120\",\n `tabSales Order Item`.item_code as \"Item:Link/Item:120\",\n `tabSales Order Item`.qty as \"Qty:Float:140\",\n `tabSales Order Item`.delivered_qty as \"Delivered Qty:Float:140\",\n (`tabSales Order Item`.qty - ifnull(`tabSales Order Item`.delivered_qty, 0)) as \"Qty to Deliver:Float:140\",\n `tabSales Order Item`.base_rate as \"Rate:Float:140\",\n `tabSales Order Item`.base_amount as \"Amount:Float:140\",\n ((`tabSales Order Item`.qty - ifnull(`tabSales Order Item`.delivered_qty, 0))*`tabSales Order Item`.base_rate) as \"Amount to Deliver:Float:140\",\n `tabBin`.actual_qty as \"Available Qty:Float:120\",\n `tabBin`.projected_qty as \"Projected Qty:Float:120\",\n `tabSales Order Item`.`delivery_date` as \"Item Delivery Date:Date:120\",\n DATEDIFF(CURDATE(),`tabSales Order Item`.`delivery_date`) as \"Delay Days:Int:120\",\n `tabSales Order Item`.item_name as \"Item Name::150\",\n `tabSales Order Item`.description as \"Description::200\",\n `tabSales Order Item`.item_group as \"Item Group:Link/Item Group:120\",\n `tabSales Order Item`.warehouse as \"Warehouse:Link/Warehouse:200\"\nfrom\n `tabSales Order` JOIN `tabSales Order Item` \n LEFT JOIN `tabBin` ON (`tabBin`.item_code = `tabSales Order Item`.item_code\n and `tabBin`.warehouse = `tabSales Order Item`.warehouse)\nwhere\n `tabSales Order Item`.`parent` = `tabSales Order`.`name`\n and `tabSales Order`.docstatus = 1\n and `tabSales Order`.status not in (\"Stopped\", \"Closed\")\n and ifnull(`tabSales Order Item`.delivered_qty,0) < ifnull(`tabSales Order Item`.qty,0)\norder by `tabSales Order`.transaction_date asc", "prepared_report": 0,
"ref_doctype": "Delivery Note", "query": "select \n `tabSales Order`.`name` as \"Sales Order:Link/Sales Order:120\",\n `tabSales Order`.`status` as \"Status:Data:120\",\n `tabSales Order`.`customer` as \"Customer:Link/Customer:120\",\n `tabSales Order`.`customer_name` as \"Customer Name::150\",\n `tabSales Order`.`transaction_date` as \"Date:Date\",\n `tabSales Order`.`project` as \"Project:Link/Project:120\",\n `tabSales Order Item`.item_code as \"Item:Link/Item:120\",\n `tabSales Order Item`.qty as \"Qty:Float:140\",\n `tabSales Order Item`.delivered_qty as \"Delivered Qty:Float:140\",\n (`tabSales Order Item`.qty - ifnull(`tabSales Order Item`.delivered_qty, 0)) as \"Qty to Deliver:Float:140\",\n `tabSales Order Item`.base_rate as \"Rate:Float:140\",\n `tabSales Order Item`.base_amount as \"Amount:Float:140\",\n ((`tabSales Order Item`.qty - ifnull(`tabSales Order Item`.delivered_qty, 0))*`tabSales Order Item`.base_rate) as \"Amount to Deliver:Float:140\",\n `tabBin`.actual_qty as \"Available Qty:Float:120\",\n `tabBin`.projected_qty as \"Projected Qty:Float:120\",\n `tabSales Order Item`.`delivery_date` as \"Item Delivery Date:Date:120\",\n DATEDIFF(CURDATE(),`tabSales Order Item`.`delivery_date`) as \"Delay Days:Int:120\",\n `tabSales Order Item`.item_name as \"Item Name::150\",\n `tabSales Order Item`.description as \"Description::200\",\n `tabSales Order Item`.item_group as \"Item Group:Link/Item Group:120\",\n `tabSales Order Item`.warehouse as \"Warehouse:Link/Warehouse:200\"\nfrom\n `tabSales Order` JOIN `tabSales Order Item` \n LEFT JOIN `tabBin` ON (`tabBin`.item_code = `tabSales Order Item`.item_code\n and `tabBin`.warehouse = `tabSales Order Item`.warehouse)\nwhere\n `tabSales Order Item`.`parent` = `tabSales Order`.`name`\n and `tabSales Order`.docstatus = 1\n and `tabSales Order`.status not in (\"Stopped\", \"Closed\")\n and ifnull(`tabSales Order Item`.delivered_qty,0) < ifnull(`tabSales Order Item`.qty,0)\norder by `tabSales Order`.transaction_date asc",
"report_name": "Ordered Items To Be Delivered", "ref_doctype": "Delivery Note",
"report_type": "Query Report", "report_name": "Ordered Items To Be Delivered",
"report_type": "Query Report",
"roles": [ "roles": [
{ {
"role": "Stock User" "role": "Stock User"
}, },
{ {
"role": "Stock Manager" "role": "Stock Manager"
}, },
{ {
"role": "Sales User" "role": "Sales User"
}, },
{ {
"role": "Accounts User" "role": "Accounts User"
} }

View File

@ -1,31 +1,32 @@
{ {
"add_total_row": 1, "add_total_row": 1,
"apply_user_permissions": 1, "creation": "2013-02-22 18:01:55",
"creation": "2013-02-22 18:01:55", "disable_prepared_report": 0,
"disabled": 0, "disabled": 0,
"docstatus": 0, "docstatus": 0,
"doctype": "Report", "doctype": "Report",
"idx": 3, "idx": 3,
"is_standard": "Yes", "is_standard": "Yes",
"modified": "2017-02-24 20:04:20.699195", "modified": "2019-04-01 22:12:05.573343",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Purchase Order Items To Be Received", "name": "Purchase Order Items To Be Received",
"owner": "Administrator", "owner": "Administrator",
"query": "select \n `tabPurchase Order`.`name` as \"Purchase Order:Link/Purchase Order:120\",\n\t`tabPurchase Order`.`transaction_date` as \"Date:Date:100\",\n\t`tabPurchase Order Item`.`schedule_date` as \"Reqd by Date:Date:110\",\n\t`tabPurchase Order`.`supplier` as \"Supplier:Link/Supplier:120\",\n\t`tabPurchase Order`.`supplier_name` as \"Supplier Name::150\",\n\t`tabPurchase Order Item`.`project` as \"Project\",\n\t`tabPurchase Order Item`.item_code as \"Item Code:Link/Item:120\",\n\t`tabPurchase Order Item`.qty as \"Qty:Float:100\",\n\t`tabPurchase Order Item`.received_qty as \"Received Qty:Float:100\", \n\t(`tabPurchase Order Item`.qty - ifnull(`tabPurchase Order Item`.received_qty, 0)) as \"Qty to Receive:Float:100\",\n `tabPurchase Order Item`.warehouse as \"Warehouse:Link/Warehouse:150\",\n\t`tabPurchase Order Item`.item_name as \"Item Name::150\",\n\t`tabPurchase Order Item`.description as \"Description::200\",\n `tabPurchase Order Item`.brand as \"Brand::100\",\n\t`tabPurchase Order`.`company` as \"Company:Link/Company:\"\nfrom\n\t`tabPurchase Order`, `tabPurchase Order Item`\nwhere\n\t`tabPurchase Order Item`.`parent` = `tabPurchase Order`.`name`\n\tand `tabPurchase Order`.docstatus = 1\n\tand `tabPurchase Order`.status not in (\"Stopped\", \"Closed\")\n\tand ifnull(`tabPurchase Order Item`.received_qty, 0) < ifnull(`tabPurchase Order Item`.qty, 0)\norder by `tabPurchase Order`.transaction_date asc", "prepared_report": 0,
"ref_doctype": "Purchase Receipt", "query": "select \n `tabPurchase Order`.`name` as \"Purchase Order:Link/Purchase Order:120\",\n `tabPurchase Order`.`status` as \"Status:Data:120\",\n\t`tabPurchase Order`.`transaction_date` as \"Date:Date:100\",\n\t`tabPurchase Order Item`.`schedule_date` as \"Reqd by Date:Date:110\",\n\t`tabPurchase Order`.`supplier` as \"Supplier:Link/Supplier:120\",\n\t`tabPurchase Order`.`supplier_name` as \"Supplier Name::150\",\n\t`tabPurchase Order Item`.`project` as \"Project\",\n\t`tabPurchase Order Item`.item_code as \"Item Code:Link/Item:120\",\n\t`tabPurchase Order Item`.qty as \"Qty:Float:100\",\n\t`tabPurchase Order Item`.received_qty as \"Received Qty:Float:100\", \n\t(`tabPurchase Order Item`.qty - ifnull(`tabPurchase Order Item`.received_qty, 0)) as \"Qty to Receive:Float:100\",\n `tabPurchase Order Item`.warehouse as \"Warehouse:Link/Warehouse:150\",\n\t`tabPurchase Order Item`.item_name as \"Item Name::150\",\n\t`tabPurchase Order Item`.description as \"Description::200\",\n `tabPurchase Order Item`.brand as \"Brand::100\",\n\t`tabPurchase Order`.`company` as \"Company:Link/Company:\"\nfrom\n\t`tabPurchase Order`, `tabPurchase Order Item`\nwhere\n\t`tabPurchase Order Item`.`parent` = `tabPurchase Order`.`name`\n\tand `tabPurchase Order`.docstatus = 1\n\tand `tabPurchase Order`.status not in (\"Stopped\", \"Closed\")\n\tand ifnull(`tabPurchase Order Item`.received_qty, 0) < ifnull(`tabPurchase Order Item`.qty, 0)\norder by `tabPurchase Order`.transaction_date asc",
"report_name": "Purchase Order Items To Be Received", "ref_doctype": "Purchase Receipt",
"report_type": "Query Report", "report_name": "Purchase Order Items To Be Received",
"report_type": "Query Report",
"roles": [ "roles": [
{ {
"role": "Stock Manager" "role": "Stock Manager"
}, },
{ {
"role": "Stock User" "role": "Stock User"
}, },
{ {
"role": "Purchase User" "role": "Purchase User"
}, },
{ {
"role": "Accounts User" "role": "Accounts User"
} }

View File

@ -225,16 +225,15 @@ def get_item_details(items, sle, filters):
cf_join = "left join `tabUOM Conversion Detail` ucd on ucd.parent=item.name and ucd.uom='%s'" \ cf_join = "left join `tabUOM Conversion Detail` ucd on ucd.parent=item.name and ucd.uom='%s'" \
% frappe.db.escape(filters.get("include_uom")) % frappe.db.escape(filters.get("include_uom"))
item_codes = ', '.join(['"' + frappe.db.escape(i, percent=False) + '"' for i in items])
res = frappe.db.sql(""" res = frappe.db.sql("""
select select
item.name, item.item_name, item.description, item.item_group, item.brand, item.stock_uom {cf_field} item.name, item.item_name, item.description, item.item_group, item.brand, item.stock_uom %s
from from
`tabItem` item `tabItem` item
{cf_join} %s
where where
item.name in ({item_codes}) and ifnull(item.disabled, 0) = 0 item.name in (%s) and ifnull(item.disabled, 0) = 0
""".format(cf_field=cf_field, cf_join=cf_join, item_codes=item_codes), as_dict=1) """ % (cf_field, cf_join, ','.join(['%s'] *len(items))), items, as_dict=1)
for item in res: for item in res:
item_details.setdefault(item.name, item) item_details.setdefault(item.name, item)

View File

@ -113,7 +113,6 @@ def get_item_details(items, sl_entries, include_uom):
cf_join = "left join `tabUOM Conversion Detail` ucd on ucd.parent=item.name and ucd.uom='%s'" \ cf_join = "left join `tabUOM Conversion Detail` ucd on ucd.parent=item.name and ucd.uom='%s'" \
% frappe.db.escape(include_uom) % frappe.db.escape(include_uom)
item_codes = ', '.join([frappe.db.escape(i, percent=False) for i in items])
res = frappe.db.sql(""" res = frappe.db.sql("""
select select
item.name, item.item_name, item.description, item.item_group, item.brand, item.stock_uom {cf_field} item.name, item.item_name, item.description, item.item_group, item.brand, item.stock_uom {cf_field}
@ -122,7 +121,7 @@ def get_item_details(items, sl_entries, include_uom):
{cf_join} {cf_join}
where where
item.name in ({item_codes}) item.name in ({item_codes})
""".format(cf_field=cf_field, cf_join=cf_join, item_codes=item_codes), as_dict=1) """.format(cf_field=cf_field, cf_join=cf_join, item_codes=','.join(['%s'] *len(items))), items, as_dict=1)
for item in res: for item in res:
item_details.setdefault(item.name, item) item_details.setdefault(item.name, item)

View File

@ -1,7 +1,9 @@
frappe.ui.form.on("Issue", { frappe.ui.form.on("Issue", {
onload: function(frm) { onload: function(frm) {
frm.email_field = "raised_by"; frm.email_field = "raised_by";
set_time_to_resolve_and_response(frm); if (frm.doc.service_level_agreement) {
set_time_to_resolve_and_response(frm);
}
}, },
refresh: function (frm) { refresh: function (frm) {
@ -84,15 +86,16 @@ function set_time_to_resolve_and_response(frm) {
const email_account = frm.fields_dict['email_account'].$wrapper; const email_account = frm.fields_dict['email_account'].$wrapper;
const time_to_respond = $(get_time_left_element(__('Time To Respond'), frm.doc.response_by)); const time_to_respond = $(get_time_left_element(__('Time To Respond'), frm.doc.response_by));
const time_to_resolve = $(get_time_left_element(__('Time To Resolve'), frm.doc.resolve_by)); const time_to_resolve = $(get_time_left_element(__('Time To Resolve'), frm.doc.resolution_by));
time_to_respond.insertAfter(customer); time_to_respond.insertAfter(customer);
time_to_resolve.insertAfter(email_account); time_to_resolve.insertAfter(email_account);
} }
function get_time_left_element(label, timestamp) { function get_time_left_element(label, timestamp) {
$('.'+ frappe.scrub(label) +'').remove();
return ` return `
<div class="frappe-control input-max-width" data-field_name="${label.replace(/ /g, "_").toLowerCase()}"> <div class="frappe-control input-max-width `+ frappe.scrub(label) +`" data-field_name="`+ frappe.scrub(label) +`">
<div class="form-group"> <div class="form-group">
<div class="clearfix"> <div class="clearfix">
<label class="control-label" style="padding-right: 0px;"> <label class="control-label" style="padding-right: 0px;">
@ -109,5 +112,5 @@ function get_time_left_element(label, timestamp) {
function get_time_left(timestamp) { function get_time_left(timestamp) {
const diff = moment(timestamp).diff(moment()); const diff = moment(timestamp).diff(moment());
return diff >= 44500 ? moment.duration().humanize() : 0; return diff >= 44500 ? moment.duration(diff).humanize() : 0;
} }

View File

@ -576,7 +576,7 @@
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Agreement Staus", "label": "Agreement Status",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"options": "Ongoing\nFulfilled\nFailed", "options": "Ongoing\nFulfilled\nFailed",
@ -1262,17 +1262,15 @@
} }
], ],
"has_web_view": 0, "has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0, "hide_toolbar": 0,
"icon": "fa fa-ticket", "icon": "fa fa-ticket",
"idx": 7, "idx": 7,
"image_view": 0,
"in_create": 0, "in_create": 0,
"is_submittable": 0, "is_submittable": 0,
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2019-03-17 22:42:16.274957", "modified": "2019-04-04 10:55:40.222692",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Support", "module": "Support",
"name": "Issue", "name": "Issue",
@ -1300,7 +1298,6 @@
], ],
"quick_entry": 1, "quick_entry": 1,
"read_only": 0, "read_only": 0,
"read_only_onload": 0,
"search_fields": "status,customer,subject,raised_by", "search_fields": "status,customer,subject,raised_by",
"show_name_in_global_search": 0, "show_name_in_global_search": 0,
"sort_order": "ASC", "sort_order": "ASC",

View File

@ -65,10 +65,20 @@ class Issue(Document):
self.first_responded_on = now() self.first_responded_on = now()
if self.status=="Closed" and status !="Closed": if self.status=="Closed" and status !="Closed":
self.resolution_date = now() self.resolution_date = now()
self.update_agreement_status()
if self.status=="Open" and status !="Open": if self.status=="Open" and status !="Open":
# if no date, it should be set as None and not a blank string "", as per mysql strict config # if no date, it should be set as None and not a blank string "", as per mysql strict config
self.resolution_date = None self.resolution_date = None
def update_agreement_status(self):
current_time = frappe.flags.current_time or now_datetime()
if self.service_level_agreement:
if (round(time_diff_in_hours(self.response_by, current_time), 2) < 0
or round(time_diff_in_hours(self.resolution_by, current_time), 2) < 0):
self.agreement_status = "Failed"
else:
self.agreement_status = "Fulfilled"
def create_communication(self): def create_communication(self):
communication = frappe.new_doc("Communication") communication = frappe.new_doc("Communication")
communication.update({ communication.update({
@ -128,8 +138,8 @@ class Issue(Document):
start_date_time = get_datetime(self.creation) start_date_time = get_datetime(self.creation)
self.response_by, self.time_to_respond = get_expected_time_for('response', service_level, start_date_time) self.response_by = get_expected_time_for('response', service_level, start_date_time)
self.resolution_by, self.time_to_resolve = get_expected_time_for('resolution', service_level, start_date_time) self.resolution_by = get_expected_time_for('resolution', service_level, start_date_time)
def get_expected_time_for(parameter, service_level, start_date_time): def get_expected_time_for(parameter, service_level, start_date_time):
current_date_time = start_date_time current_date_time = start_date_time
@ -194,7 +204,7 @@ def get_expected_time_for(parameter, service_level, start_date_time):
else: else:
current_date_time = expected_time current_date_time = expected_time
return current_date_time, round(time_diff_in_hours(current_date_time, start_date_time), 2) return current_date_time
def get_list_context(context=None): def get_list_context(context=None):
return { return {
@ -265,18 +275,6 @@ def update_issue(contact, method):
"""Called when Contact is deleted""" """Called when Contact is deleted"""
frappe.db.sql("""UPDATE `tabIssue` set contact='' where contact=%s""", contact.name) frappe.db.sql("""UPDATE `tabIssue` set contact='' where contact=%s""", contact.name)
def update_support_timer():
issues = frappe.get_list("Issue", filters={"status": "Open"}, order_by="creation DESC")
for issue in issues:
issue = frappe.get_doc("Issue", issue.name)
if round(time_diff_in_hours(issue.response_by, now_datetime()), 2) < 0 or round(time_diff_in_hours(issue.resolution_by, now_datetime()), 2) < 0:
issue.agreement_status = "Failed"
else:
issue.agreement_status = "Fulfilled"
issue.save()
def get_holidays(holiday_list_name): def get_holidays(holiday_list_name):
holiday_list = frappe.get_cached_doc("Holiday List", holiday_list_name) holiday_list = frappe.get_cached_doc("Holiday List", holiday_list_name)
holidays = [holiday.holiday_date for holiday in holiday_list.holidays] holidays = [holiday.holiday_date for holiday in holiday_list.holidays]

View File

@ -35,6 +35,24 @@ class TestIssue(unittest.TestCase):
self.assertEquals(issue.response_by, datetime.datetime(2019, 3, 4, 18, 0)) self.assertEquals(issue.response_by, datetime.datetime(2019, 3, 4, 18, 0))
self.assertEquals(issue.resolution_by, datetime.datetime(2019, 3, 6, 12, 0)) self.assertEquals(issue.resolution_by, datetime.datetime(2019, 3, 6, 12, 0))
frappe.flags.current_time = datetime.datetime(2019, 3, 3, 12, 0)
issue.status = 'Closed'
issue.save()
self.assertEqual(issue.agreement_status, 'Fulfilled')
issue.status = 'Open'
issue.save()
frappe.flags.current_time = datetime.datetime(2019, 3, 5, 12, 0)
issue.status = 'Closed'
issue.save()
self.assertEqual(issue.agreement_status, 'Failed')
def make_issue(creation, customer=None): def make_issue(creation, customer=None):

View File

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