Merge pull request #12885 from netchampfaris/setup-progress-deadlock-fix
[deadlock fix] Save if not set
diff --git a/erpnext/accounts/doctype/sales_invoice/pos.py b/erpnext/accounts/doctype/sales_invoice/pos.py
index 63db16c..20eb14b 100644
--- a/erpnext/accounts/doctype/sales_invoice/pos.py
+++ b/erpnext/accounts/doctype/sales_invoice/pos.py
@@ -2,23 +2,30 @@
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
-import frappe, json
-from frappe import _
-from frappe.utils import nowdate
-from erpnext.setup.utils import get_exchange_rate
-from frappe.core.doctype.communication.email import make
-from erpnext.stock.get_item_details import get_pos_profile
+
+import json
+
+import frappe
from erpnext.accounts.party import get_party_account_currency
from erpnext.controllers.accounts_controller import get_taxes_and_charges
+from erpnext.setup.utils import get_exchange_rate
+from erpnext.stock.get_item_details import get_pos_profile
+from frappe import _
+from frappe.core.doctype.communication.email import make
+from frappe.utils import nowdate
+
@frappe.whitelist()
def get_pos_data():
doc = frappe.new_doc('Sales Invoice')
- doc.is_pos = 1;
+ doc.is_pos = 1
pos_profile = get_pos_profile(doc.company) or {}
if not pos_profile:
frappe.throw(_("POS Profile is required to use Point-of-Sale"))
- if not doc.company: doc.company = pos_profile.get('company')
+
+ if not doc.company:
+ doc.company = pos_profile.get('company')
+
doc.update_stock = pos_profile.get('update_stock')
if pos_profile.get('name'):
@@ -30,18 +37,20 @@
update_multi_mode_option(doc, pos_profile)
default_print_format = pos_profile.get('print_format') or "Point of Sale"
print_template = frappe.db.get_value('Print Format', default_print_format, 'html')
+ items_list = get_items_list(pos_profile)
customers = get_customers_list(pos_profile)
return {
'doc': doc,
'default_customer': pos_profile.get('customer'),
- 'items': get_items_list(pos_profile),
+ 'items': items_list,
'item_groups': get_item_groups(pos_profile),
'customers': customers,
'address': get_customers_address(customers),
'contacts': get_contacts(customers),
'serial_no_data': get_serial_no_data(pos_profile, doc.company),
'batch_no_data': get_batch_no_data(),
+ 'barcode_data': get_barcode_data(items_list),
'tax_data': get_item_tax_data(),
'price_list_data': get_price_list_data(doc.selling_price_list),
'bin_data': get_bin_data(pos_profile),
@@ -51,20 +60,23 @@
'meta': get_meta()
}
+
def get_meta():
doctype_meta = {
'customer': frappe.get_meta('Customer'),
'invoice': frappe.get_meta('Sales Invoice')
}
- for row in frappe.get_all('DocField', fields = ['fieldname', 'options'],
- filters = {'parent': 'Sales Invoice', 'fieldtype': 'Table'}):
+ for row in frappe.get_all('DocField', fields=['fieldname', 'options'],
+ filters={'parent': 'Sales Invoice', 'fieldtype': 'Table'}):
doctype_meta[row.fieldname] = frappe.get_meta(row.options)
return doctype_meta
+
def get_company_data(company):
- return frappe.get_all('Company', fields = ["*"], filters= {'name': company})[0]
+ return frappe.get_all('Company', fields=["*"], filters={'name': company})[0]
+
def update_pos_profile_data(doc, pos_profile, company_data):
doc.campaign = pos_profile.get('campaign')
@@ -96,12 +108,14 @@
doc.terms = frappe.db.get_value('Terms and Conditions', pos_profile.get('tc_name'), 'terms') or doc.terms or ''
doc.offline_pos_name = ''
+
def get_root(table):
root = frappe.db.sql(""" select name from `tab%(table)s` having
- min(lft)"""%{'table': table}, as_dict=1)
+ min(lft)""" % {'table': table}, as_dict=1)
return root[0].name
+
def update_multi_mode_option(doc, pos_profile):
from frappe.model import default_fields
@@ -123,15 +137,18 @@
doc.append('payments', payment_mode)
+
def get_mode_of_payment(doc):
- return frappe.db.sql(""" select mpa.default_account, mpa.parent, mp.type as type from `tabMode of Payment Account` mpa,
- `tabMode of Payment` mp where mpa.parent = mp.name and mpa.company = %(company)s""", {'company': doc.company}, as_dict=1)
+ return frappe.db.sql(""" select mpa.default_account, mpa.parent, mp.type as type from `tabMode of Payment Account` mpa, \
+ `tabMode of Payment` mp where mpa.parent = mp.name and mpa.company = %(company)s""", {'company': doc.company}, as_dict=1)
+
def update_tax_table(doc):
taxes = get_taxes_and_charges('Sales Taxes and Charges Template', doc.taxes_and_charges)
for tax in taxes:
doc.append('taxes', tax)
+
def get_items_list(pos_profile):
cond = "1=1"
item_groups = []
@@ -139,19 +156,20 @@
# Get items based on the item groups defined in the POS profile
for d in pos_profile.get('item_groups'):
item_groups.extend([d.name for d in get_child_nodes('Item Group', d.item_group)])
- cond = "item_group in (%s)"%(', '.join(['%s']*len(item_groups)))
+ cond = "item_group in (%s)" % (', '.join(['%s'] * len(item_groups)))
- return frappe.db.sql("""
+ return frappe.db.sql("""
select
name, item_code, item_name, description, item_group, expense_account, has_batch_no,
- has_serial_no, expense_account, selling_cost_center, stock_uom, image,
- default_warehouse, is_stock_item, barcode, brand
+ has_serial_no, expense_account, selling_cost_center, stock_uom, image,
+ default_warehouse, is_stock_item, brand
from
tabItem
where
disabled = 0 and has_variants = 0 and is_sales_item = 1 and {cond}
""".format(cond=cond), tuple(item_groups), as_dict=1)
+
def get_item_groups(pos_profile):
item_group_dict = {}
item_groups = frappe.db.sql("""Select name,
@@ -161,6 +179,7 @@
item_group_dict[data.name] = [data.lft, data.rgt]
return item_group_dict
+
def get_customers_list(pos_profile={}):
cond = "1=1"
customer_groups = []
@@ -168,12 +187,13 @@
# Get customers based on the customer groups defined in the POS profile
for d in pos_profile.get('customer_groups'):
customer_groups.extend([d.name for d in get_child_nodes('Customer Group', d.customer_group)])
- cond = "customer_group in (%s)"%(', '.join(['%s']*len(customer_groups)))
+ cond = "customer_group in (%s)" % (', '.join(['%s'] * len(customer_groups)))
return frappe.db.sql(""" select name, customer_name, customer_group,
territory, customer_pos_id from tabCustomer where disabled = 0
and {cond}""".format(cond=cond), tuple(customer_groups), as_dict=1) or {}
+
def get_customers_address(customers):
customer_address = {}
if isinstance(customers, basestring):
@@ -185,33 +205,37 @@
(select parent from `tabDynamic Link` where link_doctype = 'Customer' and link_name = %s
and parenttype = 'Address')""", data.name, as_dict=1)
address_data = {}
- if address: address_data = address[0]
+ if address:
+ address_data = address[0]
address_data.update({'full_name': data.customer_name, 'customer_pos_id': data.customer_pos_id})
customer_address[data.name] = address_data
return customer_address
+
def get_contacts(customers):
customer_contact = {}
if isinstance(customers, basestring):
customers = [frappe._dict({'name': customers})]
for data in customers:
- contact = frappe.db.sql(""" select email_id, phone, mobile_no from `tabContact`
+ contact = frappe.db.sql(""" select email_id, phone, mobile_no from `tabContact`
where is_primary_contact =1 and name in
(select parent from `tabDynamic Link` where link_doctype = 'Customer' and link_name = %s
and parenttype = 'Contact')""", data.name, as_dict=1)
- if contact:
+ if contact:
customer_contact[data.name] = contact[0]
return customer_contact
+
def get_child_nodes(group_type, root):
lft, rgt = frappe.db.get_value(group_type, root, ["lft", "rgt"])
return frappe.db.sql(""" Select name, lft, rgt from `tab{tab}` where
lft >= {lft} and rgt <= {rgt} order by lft""".format(tab=group_type, lft=lft, rgt=rgt), as_dict=1)
+
def get_serial_no_data(pos_profile, company):
# get itemwise serial no data
# example {'Nokia Lumia 1020': {'SN0001': 'Pune'}}
@@ -232,6 +256,7 @@
return itemwise_serial_no
+
def get_batch_no_data():
# get itemwise batch no data
# exmaple: {'LED-GRE': [Batch001, Batch002]}
@@ -248,6 +273,26 @@
return itemwise_batch
+
+def get_barcode_data(items_list):
+ # get itemwise batch no data
+ # exmaple: {'LED-GRE': [Batch001, Batch002]}
+ # where LED-GRE is item code, SN0001 is serial no and Pune is warehouse
+
+ itemwise_barcode = {}
+ for item in items_list:
+ barcodes = frappe.db.sql("""
+ select barcode from `tabItem Barcode` where parent = '{0}'
+ """.format(item.item_code), as_dict=1)
+
+ for barcode in barcodes:
+ if item.item_code not in itemwise_barcode:
+ itemwise_barcode.setdefault(item.item_code, [])
+ itemwise_barcode[item.item_code].append(barcode)
+
+ return itemwise_barcode
+
+
def get_item_tax_data():
# get default tax of an item
# example: {'Consulting Services': {'Excise 12 - TS': '12.000'}}
@@ -262,17 +307,19 @@
return itemwise_tax
+
def get_price_list_data(selling_price_list):
itemwise_price_list = {}
price_lists = frappe.db.sql("""Select ifnull(price_list_rate, 0) as price_list_rate,
item_code from `tabItem Price` ip where price_list = %(price_list)s""",
- {'price_list': selling_price_list}, as_dict=1)
+ {'price_list': selling_price_list}, as_dict=1)
for item in price_lists:
itemwise_price_list[item.item_code] = item.price_list_rate
return itemwise_price_list
+
def get_bin_data(pos_profile):
itemwise_bin_data = {}
cond = "1=1"
@@ -289,6 +336,7 @@
return itemwise_bin_data
+
def get_pricing_rule_data(doc):
pricing_rules = ""
if doc.ignore_pricing_rule == 0:
@@ -297,9 +345,10 @@
and ifnull(company, '') in (%(company)s, '') and disable = 0 and %(date)s
between ifnull(valid_from, '2000-01-01') and ifnull(valid_upto, '2500-12-31')
order by priority desc, name desc""",
- {'company': doc.company, 'price_list': doc.selling_price_list, 'date': nowdate()}, as_dict=1)
+ {'company': doc.company, 'price_list': doc.selling_price_list, 'date': nowdate()}, as_dict=1)
return pricing_rules
+
@frappe.whitelist()
def make_invoice(doc_list={}, email_queue_list={}, customers_list={}):
if isinstance(doc_list, basestring):
@@ -338,14 +387,15 @@
'synced_contacts': get_contacts(customers)
}
+
def validate_records(doc):
validate_item(doc)
+
def get_customer_id(doc, customer=None):
cust_id = None
if doc.get('customer_pos_id'):
- cust_id = frappe.db.get_value('Customer',
- {'customer_pos_id': doc.get('customer_pos_id')}, 'name')
+ cust_id = frappe.db.get_value('Customer',{'customer_pos_id': doc.get('customer_pos_id')}, 'name')
if not cust_id:
customer = customer or doc.get('customer')
@@ -356,6 +406,7 @@
return cust_id
+
def make_customer_and_address(customers):
customers_list = []
for customer, data in customers.items():
@@ -372,6 +423,7 @@
frappe.db.commit()
return customers_list
+
def add_customer(data):
customer_doc = frappe.new_doc('Customer')
customer_doc.customer_name = data.get('full_name') or data.get('customer')
@@ -380,28 +432,29 @@
customer_doc.customer_group = get_customer_group(data)
customer_doc.territory = get_territory(data)
customer_doc.flags.ignore_mandatory = True
- customer_doc.save(ignore_permissions = True)
+ customer_doc.save(ignore_permissions=True)
frappe.db.commit()
return customer_doc.name
+
def get_territory(data):
if data.get('territory'):
return data.get('territory')
- return frappe.db.get_single_value('Selling Settings',
- 'territory') or _('All Territories')
+ return frappe.db.get_single_value('Selling Settings','territory') or _('All Territories')
+
def get_customer_group(data):
if data.get('customer_group'):
return data.get('customer_group')
- return frappe.db.get_single_value('Selling Settings',
- 'customer_group') or frappe.db.get_value('Customer Group', {'is_group': 0}, 'name')
+ return frappe.db.get_single_value('Selling Settings', 'customer_group') or frappe.db.get_value('Customer Group', {'is_group': 0}, 'name')
-def make_contact(args,customer):
+
+def make_contact(args, customer):
if args.get('email_id') or args.get('phone'):
name = frappe.db.get_value('Dynamic Link',
- {'link_doctype': 'Customer', 'link_name': customer, 'parenttype': 'Contact'}, 'parent')
+ {'link_doctype': 'Customer', 'link_name': customer, 'parenttype': 'Contact'}, 'parent')
args = {
'first_name': args.get('full_name'),
@@ -416,16 +469,18 @@
doc.update(args)
doc.is_primary_contact = 1
if not name:
- doc.append('links',{
+ doc.append('links', {
'link_doctype': 'Customer',
'link_name': customer
})
doc.flags.ignore_mandatory = True
doc.save(ignore_permissions=True)
+
def make_address(args, customer):
- if not args.get('address_line1'): return
-
+ if not args.get('address_line1'):
+ return
+
name = args.get('name')
if not name:
@@ -437,7 +492,7 @@
else:
address = frappe.new_doc('Address')
address.country = frappe.db.get_value('Company', args.get('company'), 'country')
- address.append('links',{
+ address.append('links', {
'link_doctype': 'Customer',
'link_name': customer
})
@@ -446,7 +501,8 @@
address.is_shipping_address = 1
address.update(args)
address.flags.ignore_mandatory = True
- address.save(ignore_permissions = True)
+ address.save(ignore_permissions=True)
+
def make_email_queue(email_queue):
name_list = []
@@ -455,15 +511,16 @@
data = json.loads(data)
sender = frappe.session.user
print_format = "POS Invoice"
- attachments = [frappe.attach_print('Sales Invoice', name, print_format= print_format)]
+ attachments = [frappe.attach_print('Sales Invoice', name, print_format=print_format)]
- make(subject = data.get('subject'), content = data.get('content'), recipients = data.get('recipients'),
- sender=sender,attachments = attachments, send_email=True,
- doctype='Sales Invoice', name=name)
+ make(subject=data.get('subject'), content=data.get('content'), recipients=data.get('recipients'),
+ sender=sender, attachments=attachments, send_email=True,
+ doctype='Sales Invoice', name=name)
name_list.append(key)
return name_list
+
def validate_item(doc):
for item in doc.get('items'):
if not frappe.db.exists('Item', item.get('item_code')):
@@ -486,13 +543,15 @@
frappe.db.commit()
name_list.append(name)
except Exception as e:
- if frappe.message_log: frappe.message_log.pop()
+ if frappe.message_log:
+ frappe.message_log.pop()
frappe.db.rollback()
frappe.log_error(frappe.get_traceback())
name_list = save_invoice(doc, name, name_list)
return name_list
+
def save_invoice(doc, name, name_list):
try:
if not frappe.db.exists('Sales Invoice', {'offline_pos_name': name}):
diff --git a/erpnext/accounts/page/pos/pos.js b/erpnext/accounts/page/pos/pos.js
index ab32d9f..ccb5553 100644
--- a/erpnext/accounts/page/pos/pos.js
+++ b/erpnext/accounts/page/pos/pos.js
@@ -301,6 +301,7 @@
this.customers = r.message.customers;
this.serial_no_data = r.message.serial_no_data;
this.batch_no_data = r.message.batch_no_data;
+ this.barcode_data = r.message.barcode_data;
this.tax_data = r.message.tax_data;
this.contacts = r.message.contacts;
this.address = r.message.address || {};
@@ -415,7 +416,7 @@
});
this.serach_item.make_input();
-
+
this.serach_item.$input.on("keypress", function (event) {
clearTimeout(me.last_search_timeout);
@@ -423,7 +424,7 @@
if((me.serach_item.$input.val() != "") || (event.which == 13)) {
me.items = me.get_items();
me.make_item_list();
- }
+ }
}, 400);
});
@@ -1110,9 +1111,9 @@
search_status = false;
me.item_serial_no[item.item_code] = [me.serach_item.$input.val(), me.serial_no_data[item.item_code][me.serach_item.$input.val()]]
return true
- } else if (item.barcode == me.serach_item.$input.val()) {
+ } else if (in_list(me.barcode_data[item.item_code], me.serach_item.$input.val())) {
search_status = false;
- return item.barcode == me.serach_item.$input.val();
+ return true;
} else if (reg.test(item.item_code.toLowerCase()) || (item.description && reg.test(item.description.toLowerCase())) ||
reg.test(item.item_name.toLowerCase()) || reg.test(item.item_group.toLowerCase())) {
return true
@@ -1526,8 +1527,8 @@
me.print_document(html)
})
}
-
- if (this.frm.doc.docstatus == 1) {
+
+ if (this.frm.doc.docstatus == 1) {
this.page.add_menu_item(__("Email"), function () {
me.email_prompt()
})
diff --git a/erpnext/accounts/report/balance_sheet/balance_sheet.js b/erpnext/accounts/report/balance_sheet/balance_sheet.js
index 760fa64..f22f3a1 100644
--- a/erpnext/accounts/report/balance_sheet/balance_sheet.js
+++ b/erpnext/accounts/report/balance_sheet/balance_sheet.js
@@ -11,5 +11,3 @@
"default": 1
});
});
-
-
diff --git a/erpnext/accounts/report/cash_flow/cash_flow.js b/erpnext/accounts/report/cash_flow/cash_flow.js
index 455664f..391f57b 100644
--- a/erpnext/accounts/report/cash_flow/cash_flow.js
+++ b/erpnext/accounts/report/cash_flow/cash_flow.js
@@ -5,6 +5,11 @@
frappe.query_reports["Cash Flow"] = $.extend({},
erpnext.financial_statements);
+ // The last item in the array is the definition for Presentation Currency
+ // filter. It won't be used in cash flow for now so we pop it. Please take
+ // of this if you are working here.
+ frappe.query_reports["Cash Flow"]["filters"].pop();
+
frappe.query_reports["Cash Flow"]["filters"].push({
"fieldname": "accumulated_values",
"label": __("Accumulated Values"),
diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py
index b0c49df..5d2a35b 100644
--- a/erpnext/accounts/report/financial_statements.py
+++ b/erpnext/accounts/report/financial_statements.py
@@ -2,12 +2,14 @@
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
-import frappe
+
import re
-from frappe import _
-from frappe.utils import (flt, getdate, get_first_day, get_last_day, date_diff,
- add_months, add_days, formatdate, cint)
+
+import frappe
+from erpnext.accounts.report.utils import get_currency, convert_to_presentation_currency
from erpnext.accounts.utils import get_fiscal_year
+from frappe import _
+from frappe.utils import (flt, getdate, get_first_day, add_months, add_days, formatdate)
def get_period_list(from_fiscal_year, to_fiscal_year, periodicity, accumulated_values=False,
@@ -84,6 +86,7 @@
return period_list
+
def get_fiscal_year_data(from_fiscal_year, to_fiscal_year):
fiscal_year = frappe.db.sql("""select min(year_start_date) as year_start_date,
max(year_end_date) as year_end_date from `tabFiscal Year` where
@@ -92,16 +95,19 @@
return fiscal_year[0] if fiscal_year else {}
+
def validate_fiscal_year(fiscal_year, from_fiscal_year, to_fiscal_year):
if not fiscal_year.get('year_start_date') and not fiscal_year.get('year_end_date'):
frappe.throw(_("End Year cannot be before Start Year"))
+
def get_months(start_date, end_date):
diff = (12 * end_date.year + end_date.month) - (12 * start_date.year + start_date.month)
return diff + 1
+
def get_label(periodicity, from_date, to_date):
- if periodicity=="Yearly":
+ if periodicity == "Yearly":
if formatdate(from_date, "YYYY") == formatdate(to_date, "YYYY"):
label = formatdate(from_date, "YYYY")
else:
@@ -111,28 +117,34 @@
return label
-def get_data(company, root_type, balance_must_be, period_list, filters=None,
+
+def get_data(
+ company, root_type, balance_must_be, period_list, filters=None,
accumulated_values=1, only_current_fiscal_year=True, ignore_closing_entries=False,
ignore_accumulated_values_for_fy=False):
+
accounts = get_accounts(company, root_type)
if not accounts:
return None
accounts, accounts_by_name, parent_children_map = filter_accounts(accounts)
- company_currency = frappe.db.get_value("Company", company, "default_currency")
+ company_currency = get_appropriate_currency(company, filters)
gl_entries_by_account = {}
for root in frappe.db.sql("""select lft, rgt from tabAccount
where root_type=%s and ifnull(parent_account, '') = ''""", root_type, as_dict=1):
- set_gl_entries_by_account(company,
+ set_gl_entries_by_account(
+ company,
period_list[0]["year_start_date"] if only_current_fiscal_year else None,
period_list[-1]["to_date"],
root.lft, root.rgt, filters,
- gl_entries_by_account, ignore_closing_entries=ignore_closing_entries)
+ gl_entries_by_account, ignore_closing_entries=ignore_closing_entries
+ )
- calculate_values(accounts_by_name, gl_entries_by_account, period_list, accumulated_values, ignore_accumulated_values_for_fy)
+ calculate_values(
+ accounts_by_name, gl_entries_by_account, period_list, accumulated_values, ignore_accumulated_values_for_fy)
accumulate_values_into_parents(accounts, accounts_by_name, period_list, accumulated_values)
out = prepare_data(accounts, balance_must_be, period_list, company_currency)
out = filter_out_zero_value_rows(out, parent_children_map)
@@ -143,7 +155,15 @@
return out
-def calculate_values(accounts_by_name, gl_entries_by_account, period_list, accumulated_values, ignore_accumulated_values_for_fy):
+def get_appropriate_currency(company, filters=None):
+ if filters and filters.get("presentation_currency"):
+ return filters["presentation_currency"]
+ else:
+ return frappe.db.get_value("Company", company, "default_currency")
+
+
+def calculate_values(
+ accounts_by_name, gl_entries_by_account, period_list, accumulated_values, ignore_accumulated_values_for_fy):
for entries in gl_entries_by_account.values():
for entry in entries:
d = accounts_by_name.get(entry.account)
@@ -164,6 +184,7 @@
if entry.posting_date < period_list[0].year_start_date:
d["opening_balance"] = d.get("opening_balance", 0.0) + flt(entry.debit) - flt(entry.credit)
+
def accumulate_values_into_parents(accounts, accounts_by_name, period_list, accumulated_values):
"""accumulate children's values in parent accounts"""
for d in reversed(accounts):
@@ -175,6 +196,7 @@
accounts_by_name[d.parent_account]["opening_balance"] = \
accounts_by_name[d.parent_account].get("opening_balance", 0.0) + d.get("opening_balance", 0.0)
+
def prepare_data(accounts, balance_must_be, period_list, company_currency):
data = []
year_start_date = period_list[0]["year_start_date"].strftime("%Y-%m-%d")
@@ -192,10 +214,10 @@
"year_start_date": year_start_date,
"year_end_date": year_end_date,
"currency": company_currency,
- "opening_balance": d.get("opening_balance", 0.0) * (1 if balance_must_be=="Debit" else -1)
+ "opening_balance": d.get("opening_balance", 0.0) * (1 if balance_must_be == "Debit" else -1)
})
for period in period_list:
- if d.get(period.key) and balance_must_be=="Credit":
+ if d.get(period.key) and balance_must_be == "Credit":
# change sign based on Debit or Credit, since calculation is done using (debit - credit)
d[period.key] *= -1
@@ -212,6 +234,7 @@
return data
+
def filter_out_zero_value_rows(data, parent_children_map, show_zero_values=False):
data_with_value = []
for d in data:
@@ -228,6 +251,7 @@
return data_with_value
+
def add_total_row(out, root_type, balance_must_be, period_list, company_currency):
total_row = {
"account_name": "'" + _("Total {0} ({1})").format(_(root_type), _(balance_must_be)) + "'",
@@ -246,16 +270,19 @@
total_row["total"] += flt(row["total"])
row["total"] = ""
- if total_row.has_key("total"):
+ if "total" in total_row:
out.append(total_row)
# blank row after Total
out.append({})
+
def get_accounts(company, root_type):
- return frappe.db.sql("""select name, parent_account, lft, rgt, root_type, report_type, account_name from `tabAccount`
+ return frappe.db.sql(
+ """select name, parent_account, lft, rgt, root_type, report_type, account_name from `tabAccount`
where company=%s and root_type=%s order by lft""", (company, root_type), as_dict=True)
+
def filter_accounts(accounts, depth=10):
parent_children_map = {}
accounts_by_name = {}
@@ -280,6 +307,7 @@
return filtered_accounts, accounts_by_name, parent_children_map
+
def sort_root_accounts(roots):
"""Sort root types as Asset, Liability, Equity, Income, Expense"""
@@ -299,13 +327,14 @@
roots.sort(compare_roots)
-def set_gl_entries_by_account(company, from_date, to_date, root_lft, root_rgt, filters, gl_entries_by_account,
- ignore_closing_entries=False):
+
+def set_gl_entries_by_account(
+ company, from_date, to_date, root_lft, root_rgt, filters, gl_entries_by_account, ignore_closing_entries=False):
"""Returns a dict like { "account": [gl entries], ... }"""
additional_conditions = get_additional_conditions(from_date, ignore_closing_entries, filters)
- gl_entries = frappe.db.sql("""select posting_date, account, debit, credit, is_opening, fiscal_year from `tabGL Entry`
+ gl_entries = frappe.db.sql("""select posting_date, account, debit, credit, is_opening, fiscal_year, debit_in_account_currency, credit_in_account_currency, account_currency from `tabGL Entry`
where company=%(company)s
{additional_conditions}
and posting_date <= %(to_date)s
@@ -321,11 +350,15 @@
},
as_dict=True)
+ if filters and filters.get('presentation_currency'):
+ convert_to_presentation_currency(gl_entries, get_currency(filters))
+
for entry in gl_entries:
gl_entries_by_account.setdefault(entry.account, []).append(entry)
return gl_entries_by_account
+
def get_additional_conditions(from_date, ignore_closing_entries, filters):
additional_conditions = []
@@ -337,15 +370,17 @@
if filters:
if filters.get("project"):
- additional_conditions.append("project = '%s'"%(frappe.db.escape(filters.get("project"))))
+ additional_conditions.append("project = '%s'" % (frappe.db.escape(filters.get("project"))))
if filters.get("cost_center"):
additional_conditions.append(get_cost_center_cond(filters.get("cost_center")))
return " and {}".format(" and ".join(additional_conditions)) if additional_conditions else ""
+
def get_cost_center_cond(cost_center):
lft, rgt = frappe.db.get_value("Cost Center", cost_center, ["lft", "rgt"])
- return (""" cost_center in (select name from `tabCost Center` where lft >=%s and rgt <=%s)"""%(lft, rgt))
+ return """ cost_center in (select name from `tabCost Center` where lft >=%s and rgt <=%s)""" % (lft, rgt)
+
def get_columns(periodicity, period_list, accumulated_values=1, company=None):
columns = [{
diff --git a/erpnext/accounts/report/general_ledger/general_ledger.html b/erpnext/accounts/report/general_ledger/general_ledger.html
index 9e1b884..5cfdc9f 100644
--- a/erpnext/accounts/report/general_ledger/general_ledger.html
+++ b/erpnext/accounts/report/general_ledger/general_ledger.html
@@ -44,44 +44,24 @@
<br>{%= __("Supplier Invoice No") %}: {%= data[i].bill_no %}
{% } %}
</td>
- {% if(filters.print_in_account_currency) { %}
<td style="text-align: right">
- {%= format_currency(data[i].debit_in_account_currency, data[i].account_currency) %}
- </td>
+ {%= format_currency(data[i].debit, filters.presentation_currency) %}</td>
<td style="text-align: right">
- {%= format_currency(data[i].credit_in_account_currency, data[i].account_currency) %}
- </td>
- {% } else { %}
- <td style="text-align: right">
- {%= format_currency(data[i].debit) %}</td>
- <td style="text-align: right">
- {%= format_currency(data[i].credit) %}</td>
- {% } %}
+ {%= format_currency(data[i].credit, filters.presentation_currency) %}</td>
{% } else { %}
<td></td>
<td></td>
<td><b>{%= frappe.format(data[i].account, {fieldtype: "Link"}) || " " %}</b></td>
- {% if(filters.print_in_account_currency) { %}
- <td style="text-align: right">
- {%= data[i].account && format_currency(data[i].debit_in_account_currency, data[i].account_currency) %}</td>
- <td style="text-align: right">
- {%= data[i].account && format_currency(data[i].credit_in_account_currency, data[i].account_currency) %}</td>
- {% } else { %}
- <td style="text-align: right">
- {%= data[i].account && format_currency(data[i].debit) %}
- </td>
- <td style="text-align: right">
- {%= data[i].account && format_currency(data[i].credit) %}
- </td>
- {% } %}
- {% } %}
- {% if(filters.print_in_account_currency) { %}
<td style="text-align: right">
- {%= format_currency(data[i].balance_in_account_currency, data[i].account_currency) %}
+ {%= data[i].account && format_currency(data[i].debit, filters.presentation_currency) %}
</td>
- {% } else { %}
- <td style="text-align: right">{%= format_currency(data[i].balance) %}</td>
- {% } %}
+ <td style="text-align: right">
+ {%= data[i].account && format_currency(data[i].credit, filters.presentation_currency) %}
+ </td>
+ {% } %}
+ <td style="text-align: right">
+ {%= format_currency(data[i].balance, filters.presentation_currency) %}
+ </td>
</tr>
{% } %}
</tbody>
diff --git a/erpnext/accounts/report/general_ledger/general_ledger.js b/erpnext/accounts/report/general_ledger/general_ledger.js
index adefadd..d6a2aec 100644
--- a/erpnext/accounts/report/general_ledger/general_ledger.js
+++ b/erpnext/accounts/report/general_ledger/general_ledger.js
@@ -1,4 +1,4 @@
-// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
+// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
frappe.query_reports["General Ledger"] = {
@@ -107,9 +107,10 @@
"fieldtype": "Check",
},
{
- "fieldname":"print_in_account_currency",
- "label": __("Print in Account Currency"),
- "fieldtype": "Check",
+ "fieldname": "presentation_currency",
+ "label": __("Currency"),
+ "fieldtype": "Select",
+ "options": erpnext.get_presentation_currency_list()
}
]
}
diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py
index 8e4259c..70ab67f 100644
--- a/erpnext/accounts/report/general_ledger/general_ledger.py
+++ b/erpnext/accounts/report/general_ledger/general_ledger.py
@@ -3,10 +3,13 @@
from __future__ import unicode_literals
import frappe
+from erpnext import get_company_currency, get_default_company
+from erpnext.accounts.report.utils import get_currency, convert_to_presentation_currency
from frappe.utils import getdate, cstr, flt, fmt_money
from frappe import _, _dict
from erpnext.accounts.utils import get_account_currency
+
def execute(filters=None):
account_details = {}
@@ -29,6 +32,7 @@
return columns, res
+
def validate_filters(filters, account_details):
if not filters.get('company'):
frappe.throw(_('{0} is mandatory').format(_('Company')))
@@ -37,7 +41,7 @@
frappe.throw(_("Account {0} does not exists").format(filters.account))
if filters.get("account") and filters.get("group_by_account") \
- and account_details[filters.account].is_group == 0:
+ and account_details[filters.account].is_group == 0:
frappe.throw(_("Can not filter based on Account, if grouped by Account"))
if filters.get("voucher_no") and filters.get("group_by_voucher"):
@@ -56,6 +60,7 @@
elif not frappe.db.exists(party_type, party):
frappe.throw(_("Invalid {0}: {1}").format(party_type, party))
+
def set_account_currency(filters):
if not (filters.get("account") or filters.get("party")):
return filters
@@ -66,8 +71,13 @@
if filters.get("account"):
account_currency = get_account_currency(filters.account)
elif filters.get("party"):
- gle_currency = frappe.db.get_value("GL Entry", {"party_type": filters.party_type,
- "party": filters.party, "company": filters.company}, "account_currency")
+ gle_currency = frappe.db.get_value(
+ "GL Entry", {
+ "party_type": filters.party_type, "party": filters.party, "company": filters.company
+ },
+ "account_currency"
+ )
+
if gle_currency:
account_currency = gle_currency
else:
@@ -90,29 +100,39 @@
return result
+
def get_gl_entries(filters):
+ currency_map = get_currency(filters)
select_fields = """, sum(debit_in_account_currency) as debit_in_account_currency,
sum(credit_in_account_currency) as credit_in_account_currency""" \
- if filters.get("show_in_account_currency") else ""
+
group_by_condition = "group by voucher_type, voucher_no, account, cost_center" \
if filters.get("group_by_voucher") else "group by name"
- gl_entries = frappe.db.sql("""
+ gl_entries = frappe.db.sql(
+ """
select
posting_date, account, party_type, party,
sum(debit) as debit, sum(credit) as credit,
voucher_type, voucher_no, cost_center, project,
- against_voucher_type, against_voucher,
+ against_voucher_type, against_voucher, account_currency,
remarks, against, is_opening {select_fields}
from `tabGL Entry`
where company=%(company)s {conditions}
{group_by_condition}
- order by posting_date, account"""\
- .format(select_fields=select_fields, conditions=get_conditions(filters),
- group_by_condition=group_by_condition), filters, as_dict=1)
+ order by posting_date, account
+ """.format(
+ select_fields=select_fields, conditions=get_conditions(filters),
+ group_by_condition=group_by_condition
+ ),
+ filters, as_dict=1)
- return gl_entries
+ if filters.get('presentation_currency'):
+ return convert_to_presentation_currency(gl_entries, currency_map)
+ else:
+ return gl_entries
+
def get_conditions(filters):
conditions = []
@@ -132,16 +152,20 @@
if not (filters.get("account") or filters.get("party") or filters.get("group_by_account")):
conditions.append("posting_date >=%(from_date)s")
+ conditions.append("posting_date <=%(to_date)s")
if filters.get("project"):
conditions.append("project=%(project)s")
from frappe.desk.reportview import build_match_conditions
match_conditions = build_match_conditions("GL Entry")
- if match_conditions: conditions.append(match_conditions)
+
+ if match_conditions:
+ conditions.append(match_conditions)
return "and {}".format(" and ".join(conditions)) if conditions else ""
+
def get_data_with_opening_closing(filters, account_details, gl_entries):
data = []
gle_map = initialize_gle_map(gl_entries)
@@ -178,14 +202,15 @@
return data
+
def get_totals_dict():
def _get_debit_credit_dict(label):
return _dict(
- account = "'{0}'".format(label),
- debit = 0.0,
- credit = 0.0,
- debit_in_account_currency = 0.0,
- credit_in_account_currency = 0.0
+ account="'{0}'".format(label),
+ debit=0.0,
+ credit=0.0,
+ debit_in_account_currency=0.0,
+ credit_in_account_currency=0.0
)
return _dict(
opening = _get_debit_credit_dict(_('Opening')),
@@ -193,12 +218,14 @@
closing = _get_debit_credit_dict(_('Closing (Opening + Total)'))
)
+
def initialize_gle_map(gl_entries):
gle_map = frappe._dict()
for gle in gl_entries:
- gle_map.setdefault(gle.account, _dict(totals = get_totals_dict(), entries = []))
+ gle_map.setdefault(gle.account, _dict(totals=get_totals_dict(), entries=[]))
return gle_map
+
def get_accountwise_gle(filters, gl_entries, gle_map):
totals = get_totals_dict()
entries = []
@@ -210,13 +237,12 @@
data[key].debit_in_account_currency += flt(gle.debit_in_account_currency)
data[key].credit_in_account_currency += flt(gle.credit_in_account_currency)
-
from_date, to_date = getdate(filters.from_date), getdate(filters.to_date)
for gle in gl_entries:
if gle.posting_date < from_date or cstr(gle.is_opening) == "Yes":
update_value_in_dict(gle_map[gle.account].totals, 'opening', gle)
update_value_in_dict(totals, 'opening', gle)
-
+
update_value_in_dict(gle_map[gle.account].totals, 'closing', gle)
update_value_in_dict(totals, 'closing', gle)
@@ -233,6 +259,7 @@
return totals, entries
+
def get_result_as_list(data, filters):
balance, balance_in_account_currency = 0, 0
inv_details = get_supplier_invoice_details()
@@ -272,6 +299,15 @@
return balance
def get_columns(filters):
+ if filters.get("presentation_currency"):
+ currency = filters["presentation_currency"]
+ else:
+ if filters.get("company"):
+ currency = get_company_currency(filters["company"])
+ else:
+ company = get_default_company()
+ currency = get_company_currency(company)
+
columns = [
{
"label": _("Posting Date"),
@@ -287,47 +323,25 @@
"width": 180
},
{
- "label": _("Debit"),
+ "label": _("Debit ({0})".format(currency)),
"fieldname": "debit",
"fieldtype": "Float",
"width": 100
},
{
- "label": _("Credit"),
+ "label": _("Credit ({0})".format(currency)),
"fieldname": "credit",
"fieldtype": "Float",
"width": 100
},
{
- "label": _("Balance (Dr - Cr)"),
+ "label": _("Balance ({0})".format(currency)),
"fieldname": "balance",
"fieldtype": "Float",
"width": 130
}
]
- if filters.get("show_in_account_currency"):
- columns.extend([
- {
- "label": _("Debit") + " (" + filters.account_currency + ")",
- "fieldname": "debit_in_account_currency",
- "fieldtype": "Float",
- "width": 100
- },
- {
- "label": _("Credit") + " (" + filters.account_currency + ")",
- "fieldname": "credit_in_account_currency",
- "fieldtype": "Float",
- "width": 100
- },
- {
- "label": _("Balance") + " (" + filters.account_currency + ")",
- "fieldname": "balance_in_account_currency",
- "fieldtype": "Data",
- "width": 100
- }
- ])
-
columns.extend([
{
"label": _("Voucher Type"),
diff --git a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js
index bcac2df..a02c592 100644
--- a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js
+++ b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js
@@ -1,6 +1,7 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
+
frappe.require("assets/erpnext/js/financial_statements.js", function() {
frappe.query_reports["Profit and Loss Statement"] = $.extend({},
erpnext.financial_statements);
@@ -24,4 +25,4 @@
"fieldtype": "Check"
}
);
-});
\ No newline at end of file
+});
diff --git a/erpnext/accounts/report/utils.py b/erpnext/accounts/report/utils.py
new file mode 100644
index 0000000..4490398
--- /dev/null
+++ b/erpnext/accounts/report/utils.py
@@ -0,0 +1,128 @@
+import frappe
+from erpnext import get_company_currency, get_default_company
+from erpnext.setup.utils import get_exchange_rate
+from frappe.utils import cint
+
+__exchange_rates = {}
+P_OR_L_ACCOUNTS = list(
+ sum(frappe.get_list('Account', fields=['name'], or_filters=[{'root_type': 'Income'}, {'root_type': 'Expense'}], as_list=True), ())
+)
+
+
+def get_currency(filters):
+ """
+ Returns a dictionary containing currency information. The keys of the dict are
+ - company: The company for which we are fetching currency information. if no
+ company is specified, it will fallback to the default company.
+ - company currency: The functional currency of the said company.
+ - presentation currency: The presentation currency to use. Only currencies that
+ have been used for transactions will be allowed.
+ - report date: The report date.
+ :param filters: Report filters
+ :type filters: dict
+
+ :return: str - Currency
+ """
+ company = get_appropriate_company(filters)
+ company_currency = get_company_currency(company)
+ presentation_currency = filters['presentation_currency'] if filters.get('presentation_currency') else company_currency
+ report_date = filters.get('to_date') or filters.get('to_fiscal_year')
+
+ currency_map = dict(company=company, company_currency=company_currency, presentation_currency=presentation_currency, report_date=report_date)
+
+ return currency_map
+
+
+def convert(value, from_, to, date):
+ """
+ convert `value` from `from_` to `to` on `date`
+ :param value: Amount to be converted
+ :param from_: Currency of `value`
+ :param to: Currency to convert to
+ :param date: exchange rate as at this date
+ :return: Result of converting `value`
+ """
+ rate = get_rate_as_at(date, from_, to)
+ converted_value = value / (rate or 1)
+ return converted_value
+
+
+def get_rate_as_at(date, from_currency, to_currency):
+ """
+ Gets exchange rate as at `date` for `from_currency` - `to_currency` exchange rate.
+ This calls `get_exchange_rate` so that we can get the correct exchange rate as per
+ the user's Accounts Settings.
+ It is made efficient by memoising results to `__exchange_rates`
+ :param date: exchange rate as at this date
+ :param from_currency: Base currency
+ :param to_currency: Quote currency
+ :return: Retrieved exchange rate
+ """
+ rate = __exchange_rates.get('{0}-{1}@{2}'.format(from_currency, to_currency, date))
+ if not rate:
+ rate = get_exchange_rate(from_currency, to_currency, date) or 1
+ __exchange_rates['{0}-{1}@{2}'.format(from_currency, to_currency, date)] = rate
+
+ return rate
+
+
+def is_p_or_l_account(account_name):
+ """
+ Check if the given `account name` is an `Account` with `root_type` of either 'Income'
+ or 'Expense'.
+ :param account_name:
+ :return: Boolean
+ """
+ return account_name in P_OR_L_ACCOUNTS
+
+
+def convert_to_presentation_currency(gl_entries, currency_info):
+ """
+ Take a list of GL Entries and change the 'debit' and 'credit' values to currencies
+ in `currency_info`.
+ :param gl_entries:
+ :param currency_info:
+ :return:
+ """
+ converted_gl_list = []
+ presentation_currency = currency_info['presentation_currency']
+ company_currency = currency_info['company_currency']
+
+ for entry in gl_entries:
+ account = entry['account']
+ debit = cint(entry['debit'])
+ credit = cint(entry['credit'])
+ debit_in_account_currency = cint(entry['debit_in_account_currency'])
+ credit_in_account_currency = cint(entry['credit_in_account_currency'])
+ account_currency = entry['account_currency']
+
+ if account_currency != presentation_currency or (account_currency == presentation_currency and not is_p_or_l_account(account)):
+ value = debit or credit
+
+ date = currency_info['report_date'] if not is_p_or_l_account(account) else entry['posting_date']
+
+ converted_value = convert(value, presentation_currency, company_currency, date)
+
+ if entry.get('debit'):
+ entry['debit'] = converted_value
+ else:
+ entry['credit'] = converted_value
+
+ elif account_currency == presentation_currency:
+ if entry.get('debit'):
+ entry['debit'] = debit_in_account_currency
+ else:
+ entry['credit'] = credit_in_account_currency
+
+ converted_gl_list.append(entry)
+
+ return converted_gl_list
+
+
+def get_appropriate_company(filters):
+ if filters.get('company'):
+ company = filters['company']
+ else:
+ company = get_default_company()
+
+ return company
diff --git a/erpnext/config/desktop.py b/erpnext/config/desktop.py
index 7556083..3c18844 100644
--- a/erpnext/config/desktop.py
+++ b/erpnext/config/desktop.py
@@ -360,6 +360,14 @@
"hidden": 1
},
{
+ "module_name": "Hotels",
+ "color": "#EA81E8",
+ "icon": "fa fa-bed",
+ "type": "module",
+ "label": _("Hotels"),
+ "hidden": 1
+ },
+ {
"module_name": "Agriculture",
"color": "#8BC34A",
"icon": "octicon octicon-globe",
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index b3672cb..b7017c1 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -12,7 +12,7 @@
from erpnext.accounts.party import get_party_account_currency, validate_party_frozen_disabled
from erpnext.exceptions import InvalidCurrency
-force_item_fields = ("item_group", "barcode", "brand", "stock_uom")
+force_item_fields = ("item_group", "brand", "stock_uom")
class AccountsController(TransactionBase):
def __init__(self, *args, **kwargs):
diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py
index 4b7b43c..b141455 100644
--- a/erpnext/controllers/buying_controller.py
+++ b/erpnext/controllers/buying_controller.py
@@ -218,7 +218,7 @@
if not exists:
rm = self.append(raw_material_table, {})
- required_qty = flt(flt(bom_item.qty_consumed_per_unit) * flt(item.qty) *
+ required_qty = flt(flt(bom_item.qty_consumed_per_unit) * (flt(item.qty) + getattr(item, 'rejected_qty', 0)) *
flt(item.conversion_factor), rm.precision("required_qty"))
rm.reference_name = item.name
rm.bom_detail_no = bom_item.name
diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py
index fd7bdc7..9a93633 100644
--- a/erpnext/controllers/queries.py
+++ b/erpnext/controllers/queries.py
@@ -165,8 +165,8 @@
and (tabItem.`{key}` LIKE %(txt)s
or tabItem.item_group LIKE %(txt)s
or tabItem.item_name LIKE %(txt)s
- or tabItem.barcode LIKE %(txt)s
or tabItem.description LIKE %(txt)s)
+ or tabItem.item_code IN (select parent from `tabItem Barcode` where barcode LIKE %(txt)s)
{fcond} {mcond}
order by
if(locate(%(_txt)s, name), locate(%(_txt)s, name), 99999),
@@ -270,13 +270,14 @@
}
if args.get('warehouse'):
- batch_nos = frappe.db.sql("""select sle.batch_no, round(sum(sle.actual_qty),2), sle.stock_uom, batch.expiry_date
+ batch_nos = frappe.db.sql("""select sle.batch_no, round(sum(sle.actual_qty),2), sle.stock_uom, concat('MFG-',batch.manufacturing_date), concat('EXP-',batch.expiry_date)
from `tabStock Ledger Entry` sle
INNER JOIN `tabBatch` batch on sle.batch_no = batch.name
where
sle.item_code = %(item_code)s
and sle.warehouse = %(warehouse)s
- and sle.batch_no like %(txt)s
+ and (sle.batch_no like %(txt)s
+ or batch.manufacturing_date like %(txt)s)
and batch.docstatus < 2
{0}
{match_conditions}
@@ -287,9 +288,10 @@
if batch_nos:
return batch_nos
else:
- return frappe.db.sql("""select name, expiry_date from `tabBatch` batch
+ return frappe.db.sql("""select name, concat('MFG-', manufacturing_date), concat('EXP-',expiry_date) from `tabBatch` batch
where item = %(item_code)s
- and name like %(txt)s
+ and (name like %(txt)s
+ or manufacturing_date like %(txt)s)
and docstatus < 2
{0}
{match_conditions}
diff --git a/erpnext/demo/user/hr.py b/erpnext/demo/user/hr.py
index e59c3ee..d61aa4e 100644
--- a/erpnext/demo/user/hr.py
+++ b/erpnext/demo/user/hr.py
@@ -172,7 +172,6 @@
"from_date": frappe.flags.current_date,
"to_date": to_date,
"leave_type": allocated_leave.leave_type,
- "status": "Approved"
})
try:
leave_application.insert()
@@ -191,8 +190,9 @@
"employee": employee.name,
"attendance_date": attendance_date
})
+
leave = frappe.db.sql("""select name from `tabLeave Application`
- where employee = %s and %s between from_date and to_date and workflow_state = 'Approved'
+ where employee = %s and %s between from_date and to_date
and docstatus = 1""", (employee.name, attendance_date))
if leave:
diff --git a/erpnext/docs/assets/img/hotels/hotel-room.png b/erpnext/docs/assets/img/hotels/hotel-room.png
new file mode 100644
index 0000000..7aad227
--- /dev/null
+++ b/erpnext/docs/assets/img/hotels/hotel-room.png
Binary files differ
diff --git a/erpnext/docs/user/manual/en/hospitality/hotel-room.md b/erpnext/docs/user/manual/en/hospitality/hotel-room.md
new file mode 100644
index 0000000..b788d1b
--- /dev/null
+++ b/erpnext/docs/user/manual/en/hospitality/hotel-room.md
@@ -0,0 +1,5 @@
+# Hotel Room
+
+Hotel Room is a master to create hotel rooms for reservation
+
+<img class="screenshot" alt="Hotel Room" src="/docs/assets/img/hotels/hotel-room.png">
diff --git a/erpnext/docs/user/manual/en/hospitality/index.md b/erpnext/docs/user/manual/en/hospitality/index.md
index efd7377..dc6c743 100644
--- a/erpnext/docs/user/manual/en/hospitality/index.md
+++ b/erpnext/docs/user/manual/en/hospitality/index.md
@@ -6,4 +6,8 @@
The Restaurant module in ERPNext will help you manage a chain of restaurants. You can create Restaurants, Menus, Tables, Reservations and a manage Order Entry and Billing.
+### Manage Hotels
+
+The Hotels module in ERPNext will help you manage creating Hotel Rooms, create Hotel Room Reservation. It will also help in creating Invoice from hotel room reservation
+
{index}
\ No newline at end of file
diff --git a/erpnext/docs/user/manual/en/hospitality/index.txt b/erpnext/docs/user/manual/en/hospitality/index.txt
index cbe6da0..0c909d8 100644
--- a/erpnext/docs/user/manual/en/hospitality/index.txt
+++ b/erpnext/docs/user/manual/en/hospitality/index.txt
@@ -1,4 +1,5 @@
restaurant
restaurant-menu
reservations
-order-entry
\ No newline at end of file
+order-entry
+hotel-room
diff --git a/erpnext/domains/hospitality.py b/erpnext/domains/hospitality.py
index bc55d9c..09b98c2 100644
--- a/erpnext/domains/hospitality.py
+++ b/erpnext/domains/hospitality.py
@@ -1,6 +1,7 @@
data = {
'desktop_icons': [
'Restaurant',
+ 'Hotels',
'Accounts',
'Buying',
'Stock',
@@ -9,7 +10,9 @@
'ToDo'
],
'restricted_roles': [
- 'Restaurant Manager'
+ 'Restaurant Manager',
+ 'Hotel Manager',
+ 'Hotel Reservation User'
],
'custom_fields': {
'Sales Invoice': [
diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py
index 0a96820..90d4d0e 100755
--- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py
+++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py
@@ -103,7 +103,7 @@
# Check if He/She on Leave
leave_record = frappe.db.sql("""select half_day from `tabLeave Application`
- where employee = %s and %s between from_date and to_date and status = 'Approved'
+ where employee = %s and %s between from_date and to_date
and docstatus = 1""", (employee, date), as_dict=True)
if leave_record:
if leave_record[0].half_day:
diff --git a/erpnext/hotels/__init__.py b/erpnext/hotels/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/hotels/__init__.py
diff --git a/erpnext/hotels/doctype/__init__.py b/erpnext/hotels/doctype/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/hotels/doctype/__init__.py
diff --git a/erpnext/hotels/doctype/hotel_room/__init__.py b/erpnext/hotels/doctype/hotel_room/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/hotels/doctype/hotel_room/__init__.py
diff --git a/erpnext/hotels/doctype/hotel_room/hotel_room.js b/erpnext/hotels/doctype/hotel_room/hotel_room.js
new file mode 100644
index 0000000..76f22d5
--- /dev/null
+++ b/erpnext/hotels/doctype/hotel_room/hotel_room.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Hotel Room', {
+ refresh: function(frm) {
+
+ }
+});
diff --git a/erpnext/hotels/doctype/hotel_room/hotel_room.json b/erpnext/hotels/doctype/hotel_room/hotel_room.json
new file mode 100644
index 0000000..2567c07
--- /dev/null
+++ b/erpnext/hotels/doctype/hotel_room/hotel_room.json
@@ -0,0 +1,175 @@
+{
+ "allow_copy": 0,
+ "allow_guest_to_view": 0,
+ "allow_import": 1,
+ "allow_rename": 1,
+ "autoname": "prompt",
+ "beta": 1,
+ "creation": "2017-12-08 12:33:56.320420",
+ "custom": 0,
+ "docstatus": 0,
+ "doctype": "DocType",
+ "document_type": "Setup",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "fields": [
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "hotel_room_type",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "Hotel Room Type",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Hotel Room Type",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "capacity",
+ "fieldtype": "Int",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "Capacity",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "extra_bed_capacity",
+ "fieldtype": "Int",
+ "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": "Extra Bed Capacity",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ }
+ ],
+ "has_web_view": 0,
+ "hide_heading": 0,
+ "hide_toolbar": 0,
+ "idx": 0,
+ "image_view": 0,
+ "in_create": 0,
+ "is_submittable": 0,
+ "issingle": 0,
+ "istable": 0,
+ "max_attachments": 0,
+ "modified": "2017-12-09 12:10:50.670113",
+ "modified_by": "Administrator",
+ "module": "Hotels",
+ "name": "Hotel Room",
+ "name_case": "",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "amend": 0,
+ "apply_user_permissions": 0,
+ "cancel": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "if_owner": 0,
+ "import": 0,
+ "permlevel": 0,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "set_user_permissions": 0,
+ "share": 1,
+ "submit": 0,
+ "write": 1
+ },
+ {
+ "amend": 0,
+ "apply_user_permissions": 0,
+ "cancel": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "if_owner": 0,
+ "import": 0,
+ "permlevel": 0,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Hotel Manager",
+ "set_user_permissions": 0,
+ "share": 1,
+ "submit": 0,
+ "write": 1
+ }
+ ],
+ "quick_entry": 1,
+ "read_only": 0,
+ "read_only_onload": 0,
+ "restrict_to_domain": "Hospitality",
+ "show_name_in_global_search": 0,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1,
+ "track_seen": 0
+}
\ No newline at end of file
diff --git a/erpnext/hotels/doctype/hotel_room/hotel_room.py b/erpnext/hotels/doctype/hotel_room/hotel_room.py
new file mode 100644
index 0000000..8471aee
--- /dev/null
+++ b/erpnext/hotels/doctype/hotel_room/hotel_room.py
@@ -0,0 +1,13 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe.model.document import Document
+
+class HotelRoom(Document):
+ def validate(self):
+ if not self.capacity:
+ self.capacity, self.extra_bed_capacity = frappe.db.get_value('Hotel Room Type',
+ self.hotel_room_type, ['capacity', 'extra_bed_capacity'])
\ No newline at end of file
diff --git a/erpnext/hotels/doctype/hotel_room/test_hotel_room.js b/erpnext/hotels/doctype/hotel_room/test_hotel_room.js
new file mode 100644
index 0000000..8b2b833
--- /dev/null
+++ b/erpnext/hotels/doctype/hotel_room/test_hotel_room.js
@@ -0,0 +1,23 @@
+/* eslint-disable */
+// rename this file from _test_[name] to test_[name] to activate
+// and remove above this line
+
+QUnit.test("test: Hotel Room", function (assert) {
+ let done = assert.async();
+
+ // number of asserts
+ assert.expect(1);
+
+ frappe.run_serially([
+ // insert a new Hotel Room
+ () => frappe.tests.make('Hotel Room', [
+ // values to be set
+ {key: 'value'}
+ ]),
+ () => {
+ assert.equal(cur_frm.doc.key, 'value');
+ },
+ () => done()
+ ]);
+
+});
diff --git a/erpnext/hotels/doctype/hotel_room/test_hotel_room.py b/erpnext/hotels/doctype/hotel_room/test_hotel_room.py
new file mode 100644
index 0000000..00d3aea
--- /dev/null
+++ b/erpnext/hotels/doctype/hotel_room/test_hotel_room.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+import frappe
+import unittest
+
+test_records = [
+ dict(doctype="Hotel Room", name="1001",
+ hotel_room_type="Basic Room"),
+ dict(doctype="Hotel Room", name="1002",
+ hotel_room_type="Basic Room"),
+ dict(doctype="Hotel Room", name="1003",
+ hotel_room_type="Basic Room"),
+ dict(doctype="Hotel Room", name="1004",
+ hotel_room_type="Basic Room"),
+ dict(doctype="Hotel Room", name="1005",
+ hotel_room_type="Basic Room"),
+ dict(doctype="Hotel Room", name="1006",
+ hotel_room_type="Basic Room")
+]
+
+class TestHotelRoom(unittest.TestCase):
+ pass
diff --git a/erpnext/hotels/doctype/hotel_room_amenity/__init__.py b/erpnext/hotels/doctype/hotel_room_amenity/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/hotels/doctype/hotel_room_amenity/__init__.py
diff --git a/erpnext/hotels/doctype/hotel_room_amenity/hotel_room_amenity.json b/erpnext/hotels/doctype/hotel_room_amenity/hotel_room_amenity.json
new file mode 100644
index 0000000..29a0407
--- /dev/null
+++ b/erpnext/hotels/doctype/hotel_room_amenity/hotel_room_amenity.json
@@ -0,0 +1,103 @@
+{
+ "allow_copy": 0,
+ "allow_guest_to_view": 0,
+ "allow_import": 0,
+ "allow_rename": 0,
+ "beta": 0,
+ "creation": "2017-12-08 12:35:36.572185",
+ "custom": 0,
+ "docstatus": 0,
+ "doctype": "DocType",
+ "document_type": "",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "fields": [
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "item",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "Item",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Item",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "billable",
+ "fieldtype": "Check",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "Billable",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ }
+ ],
+ "has_web_view": 0,
+ "hide_heading": 0,
+ "hide_toolbar": 0,
+ "idx": 0,
+ "image_view": 0,
+ "in_create": 0,
+ "is_submittable": 0,
+ "issingle": 0,
+ "istable": 1,
+ "max_attachments": 0,
+ "modified": "2017-12-09 12:05:07.125687",
+ "modified_by": "Administrator",
+ "module": "Hotels",
+ "name": "Hotel Room Amenity",
+ "name_case": "",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "read_only": 0,
+ "read_only_onload": 0,
+ "restrict_to_domain": "Hospitality",
+ "show_name_in_global_search": 0,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1,
+ "track_seen": 0
+}
\ No newline at end of file
diff --git a/erpnext/hotels/doctype/hotel_room_amenity/hotel_room_amenity.py b/erpnext/hotels/doctype/hotel_room_amenity/hotel_room_amenity.py
new file mode 100644
index 0000000..69da007
--- /dev/null
+++ b/erpnext/hotels/doctype/hotel_room_amenity/hotel_room_amenity.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe.model.document import Document
+
+class HotelRoomAmenity(Document):
+ pass
diff --git a/erpnext/hotels/doctype/hotel_room_package/__init__.py b/erpnext/hotels/doctype/hotel_room_package/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/hotels/doctype/hotel_room_package/__init__.py
diff --git a/erpnext/hotels/doctype/hotel_room_package/hotel_room_package.js b/erpnext/hotels/doctype/hotel_room_package/hotel_room_package.js
new file mode 100644
index 0000000..5b09ae5
--- /dev/null
+++ b/erpnext/hotels/doctype/hotel_room_package/hotel_room_package.js
@@ -0,0 +1,23 @@
+// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Hotel Room Package', {
+ hotel_room_type: function(frm) {
+ if (frm.doc.hotel_room_type) {
+ frappe.model.with_doc('Hotel Room Type', frm.doc.hotel_room_type, () => {
+ let hotel_room_type = frappe.get_doc('Hotel Room Type', frm.doc.hotel_room_type);
+
+ // reset the amenities
+ frm.doc.amenities = [];
+
+ for (let amenity of hotel_room_type.amenities) {
+ let d = frm.add_child('amenities');
+ d.item = amenity.item;
+ d.billable = amenity.billable;
+ }
+
+ frm.refresh();
+ });
+ }
+ }
+});
diff --git a/erpnext/hotels/doctype/hotel_room_package/hotel_room_package.json b/erpnext/hotels/doctype/hotel_room_package/hotel_room_package.json
new file mode 100644
index 0000000..57dad44
--- /dev/null
+++ b/erpnext/hotels/doctype/hotel_room_package/hotel_room_package.json
@@ -0,0 +1,215 @@
+{
+ "allow_copy": 0,
+ "allow_guest_to_view": 0,
+ "allow_import": 0,
+ "allow_rename": 0,
+ "autoname": "prompt",
+ "beta": 1,
+ "creation": "2017-12-08 12:43:17.211064",
+ "custom": 0,
+ "docstatus": 0,
+ "doctype": "DocType",
+ "document_type": "Setup",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "fields": [
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "hotel_room_type",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "Hotel Room Type",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Hotel Room Type",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "column_break_2",
+ "fieldtype": "Column Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "item",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Item",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Item",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "section_break_4",
+ "fieldtype": "Section Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "amenities",
+ "fieldtype": "Table",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Amenities",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Hotel Room Amenity",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ }
+ ],
+ "has_web_view": 0,
+ "hide_heading": 0,
+ "hide_toolbar": 0,
+ "idx": 0,
+ "image_view": 0,
+ "in_create": 0,
+ "is_submittable": 0,
+ "issingle": 0,
+ "istable": 0,
+ "max_attachments": 0,
+ "modified": "2017-12-09 12:10:31.111952",
+ "modified_by": "Administrator",
+ "module": "Hotels",
+ "name": "Hotel Room Package",
+ "name_case": "",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "amend": 0,
+ "apply_user_permissions": 0,
+ "cancel": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "if_owner": 0,
+ "import": 0,
+ "permlevel": 0,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "set_user_permissions": 0,
+ "share": 1,
+ "submit": 0,
+ "write": 1
+ }
+ ],
+ "quick_entry": 0,
+ "read_only": 0,
+ "read_only_onload": 0,
+ "restrict_to_domain": "Hospitality",
+ "show_name_in_global_search": 0,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1,
+ "track_seen": 0
+}
\ No newline at end of file
diff --git a/erpnext/hotels/doctype/hotel_room_package/hotel_room_package.py b/erpnext/hotels/doctype/hotel_room_package/hotel_room_package.py
new file mode 100644
index 0000000..8a62eea
--- /dev/null
+++ b/erpnext/hotels/doctype/hotel_room_package/hotel_room_package.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe.model.document import Document
+
+class HotelRoomPackage(Document):
+ def validate(self):
+ if not self.item:
+ item = frappe.get_doc(dict(
+ doctype = 'Item',
+ item_code = self.name,
+ item_group = 'Products',
+ is_stock_item = 0,
+ stock_uom = 'Unit'
+ ))
+ item.insert()
+ self.item = item.name
diff --git a/erpnext/hotels/doctype/hotel_room_package/test_hotel_room_package.js b/erpnext/hotels/doctype/hotel_room_package/test_hotel_room_package.js
new file mode 100644
index 0000000..f1ebad4
--- /dev/null
+++ b/erpnext/hotels/doctype/hotel_room_package/test_hotel_room_package.js
@@ -0,0 +1,23 @@
+/* eslint-disable */
+// rename this file from _test_[name] to test_[name] to activate
+// and remove above this line
+
+QUnit.test("test: Hotel Room Package", function (assert) {
+ let done = assert.async();
+
+ // number of asserts
+ assert.expect(1);
+
+ frappe.run_serially([
+ // insert a new Hotel Room Package
+ () => frappe.tests.make('Hotel Room Package', [
+ // values to be set
+ {key: 'value'}
+ ]),
+ () => {
+ assert.equal(cur_frm.doc.key, 'value');
+ },
+ () => done()
+ ]);
+
+});
diff --git a/erpnext/hotels/doctype/hotel_room_package/test_hotel_room_package.py b/erpnext/hotels/doctype/hotel_room_package/test_hotel_room_package.py
new file mode 100644
index 0000000..ebf7f2b
--- /dev/null
+++ b/erpnext/hotels/doctype/hotel_room_package/test_hotel_room_package.py
@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+import frappe
+import unittest
+test_records = [
+ dict(doctype='Item', item_code='Breakfast',
+ item_group='Products', is_stock_item=0),
+ dict(doctype='Item', item_code='Lunch',
+ item_group='Products', is_stock_item=0),
+ dict(doctype='Item', item_code='Dinner',
+ item_group='Products', is_stock_item=0),
+ dict(doctype='Item', item_code='WiFi',
+ item_group='Products', is_stock_item=0),
+ dict(doctype='Hotel Room Type', name="Delux Room",
+ capacity=4,
+ extra_bed_capacity=2,
+ amenities = [
+ dict(item='WiFi', billable=0)
+ ]),
+ dict(doctype='Hotel Room Type', name="Basic Room",
+ capacity=4,
+ extra_bed_capacity=2,
+ amenities = [
+ dict(item='Breakfast', billable=0)
+ ]),
+ dict(doctype="Hotel Room Package", name="Basic Room with Breakfast",
+ hotel_room_type="Basic Room",
+ amenities = [
+ dict(item="Breakfast", billable=0)
+ ]),
+ dict(doctype="Hotel Room Package", name="Basic Room with Lunch",
+ hotel_room_type="Basic Room",
+ amenities = [
+ dict(item="Breakfast", billable=0),
+ dict(item="Lunch", billable=0)
+ ]),
+ dict(doctype="Hotel Room Package", name="Basic Room with Dinner",
+ hotel_room_type="Basic Room",
+ amenities = [
+ dict(item="Breakfast", billable=0),
+ dict(item="Dinner", billable=0)
+ ])
+]
+
+class TestHotelRoomPackage(unittest.TestCase):
+ pass
diff --git a/erpnext/hotels/doctype/hotel_room_pricing/__init__.py b/erpnext/hotels/doctype/hotel_room_pricing/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/hotels/doctype/hotel_room_pricing/__init__.py
diff --git a/erpnext/hotels/doctype/hotel_room_pricing/hotel_room_pricing.js b/erpnext/hotels/doctype/hotel_room_pricing/hotel_room_pricing.js
new file mode 100644
index 0000000..87bb192
--- /dev/null
+++ b/erpnext/hotels/doctype/hotel_room_pricing/hotel_room_pricing.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Hotel Room Pricing', {
+ refresh: function(frm) {
+
+ }
+});
diff --git a/erpnext/hotels/doctype/hotel_room_pricing/hotel_room_pricing.json b/erpnext/hotels/doctype/hotel_room_pricing/hotel_room_pricing.json
new file mode 100644
index 0000000..0f5a776
--- /dev/null
+++ b/erpnext/hotels/doctype/hotel_room_pricing/hotel_room_pricing.json
@@ -0,0 +1,266 @@
+{
+ "allow_copy": 0,
+ "allow_guest_to_view": 0,
+ "allow_import": 1,
+ "allow_rename": 0,
+ "autoname": "prompt",
+ "beta": 1,
+ "creation": "2017-12-08 12:51:47.088174",
+ "custom": 0,
+ "docstatus": 0,
+ "doctype": "DocType",
+ "document_type": "Setup",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "fields": [
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "default": "1",
+ "fieldname": "enabled",
+ "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": "Enabled",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "currency",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "Currency",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Currency",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "from_date",
+ "fieldtype": "Date",
+ "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": "From Date",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "to_date",
+ "fieldtype": "Date",
+ "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": "To Date",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "section_break_5",
+ "fieldtype": "Section Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "items",
+ "fieldtype": "Table",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Items",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Hotel Room Pricing Item",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ }
+ ],
+ "has_web_view": 0,
+ "hide_heading": 0,
+ "hide_toolbar": 0,
+ "idx": 0,
+ "image_view": 0,
+ "in_create": 0,
+ "is_submittable": 0,
+ "issingle": 0,
+ "istable": 0,
+ "max_attachments": 0,
+ "modified": "2017-12-09 12:10:41.559559",
+ "modified_by": "Administrator",
+ "module": "Hotels",
+ "name": "Hotel Room Pricing",
+ "name_case": "",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "amend": 0,
+ "apply_user_permissions": 0,
+ "cancel": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "if_owner": 0,
+ "import": 0,
+ "permlevel": 0,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "set_user_permissions": 0,
+ "share": 1,
+ "submit": 0,
+ "write": 1
+ },
+ {
+ "amend": 0,
+ "apply_user_permissions": 0,
+ "cancel": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "if_owner": 0,
+ "import": 0,
+ "permlevel": 0,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Hotel Manager",
+ "set_user_permissions": 0,
+ "share": 1,
+ "submit": 0,
+ "write": 1
+ }
+ ],
+ "quick_entry": 1,
+ "read_only": 0,
+ "read_only_onload": 0,
+ "restrict_to_domain": "Hospitality",
+ "show_name_in_global_search": 0,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1,
+ "track_seen": 0
+}
\ No newline at end of file
diff --git a/erpnext/hotels/doctype/hotel_room_pricing/hotel_room_pricing.py b/erpnext/hotels/doctype/hotel_room_pricing/hotel_room_pricing.py
new file mode 100644
index 0000000..8eee0f2
--- /dev/null
+++ b/erpnext/hotels/doctype/hotel_room_pricing/hotel_room_pricing.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe.model.document import Document
+
+class HotelRoomPricing(Document):
+ pass
diff --git a/erpnext/hotels/doctype/hotel_room_pricing/test_hotel_room_pricing.js b/erpnext/hotels/doctype/hotel_room_pricing/test_hotel_room_pricing.js
new file mode 100644
index 0000000..ba0d1fd
--- /dev/null
+++ b/erpnext/hotels/doctype/hotel_room_pricing/test_hotel_room_pricing.js
@@ -0,0 +1,23 @@
+/* eslint-disable */
+// rename this file from _test_[name] to test_[name] to activate
+// and remove above this line
+
+QUnit.test("test: Hotel Room Pricing", function (assert) {
+ let done = assert.async();
+
+ // number of asserts
+ assert.expect(1);
+
+ frappe.run_serially([
+ // insert a new Hotel Room Pricing
+ () => frappe.tests.make('Hotel Room Pricing', [
+ // values to be set
+ {key: 'value'}
+ ]),
+ () => {
+ assert.equal(cur_frm.doc.key, 'value');
+ },
+ () => done()
+ ]);
+
+});
diff --git a/erpnext/hotels/doctype/hotel_room_pricing/test_hotel_room_pricing.py b/erpnext/hotels/doctype/hotel_room_pricing/test_hotel_room_pricing.py
new file mode 100644
index 0000000..2b7848b
--- /dev/null
+++ b/erpnext/hotels/doctype/hotel_room_pricing/test_hotel_room_pricing.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+import frappe
+import unittest
+
+test_records = [
+ dict(doctype="Hotel Room Pricing", enabled=1,
+ name="Winter 2017",
+ from_date="2017-01-01", to_date="2017-01-10",
+ items = [
+ dict(item="Basic Room with Breakfast", rate=10000),
+ dict(item="Basic Room with Lunch", rate=11000),
+ dict(item="Basic Room with Dinner", rate=12000)
+ ])
+]
+
+class TestHotelRoomPricing(unittest.TestCase):
+ pass
diff --git a/erpnext/hotels/doctype/hotel_room_pricing_item/__init__.py b/erpnext/hotels/doctype/hotel_room_pricing_item/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/hotels/doctype/hotel_room_pricing_item/__init__.py
diff --git a/erpnext/hotels/doctype/hotel_room_pricing_item/hotel_room_pricing_item.json b/erpnext/hotels/doctype/hotel_room_pricing_item/hotel_room_pricing_item.json
new file mode 100644
index 0000000..d6cd826
--- /dev/null
+++ b/erpnext/hotels/doctype/hotel_room_pricing_item/hotel_room_pricing_item.json
@@ -0,0 +1,103 @@
+{
+ "allow_copy": 0,
+ "allow_guest_to_view": 0,
+ "allow_import": 0,
+ "allow_rename": 0,
+ "beta": 0,
+ "creation": "2017-12-08 12:50:13.486090",
+ "custom": 0,
+ "docstatus": 0,
+ "doctype": "DocType",
+ "document_type": "",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "fields": [
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "item",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "Item",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Item",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "rate",
+ "fieldtype": "Currency",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "Rate",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ }
+ ],
+ "has_web_view": 0,
+ "hide_heading": 0,
+ "hide_toolbar": 0,
+ "idx": 0,
+ "image_view": 0,
+ "in_create": 0,
+ "is_submittable": 0,
+ "issingle": 0,
+ "istable": 1,
+ "max_attachments": 0,
+ "modified": "2017-12-09 12:04:58.641703",
+ "modified_by": "Administrator",
+ "module": "Hotels",
+ "name": "Hotel Room Pricing Item",
+ "name_case": "",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "read_only": 0,
+ "read_only_onload": 0,
+ "restrict_to_domain": "Hospitality",
+ "show_name_in_global_search": 0,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1,
+ "track_seen": 0
+}
\ No newline at end of file
diff --git a/erpnext/hotels/doctype/hotel_room_pricing_item/hotel_room_pricing_item.py b/erpnext/hotels/doctype/hotel_room_pricing_item/hotel_room_pricing_item.py
new file mode 100644
index 0000000..6bf01bf
--- /dev/null
+++ b/erpnext/hotels/doctype/hotel_room_pricing_item/hotel_room_pricing_item.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe.model.document import Document
+
+class HotelRoomPricingItem(Document):
+ pass
diff --git a/erpnext/hotels/doctype/hotel_room_pricing_package/__init__.py b/erpnext/hotels/doctype/hotel_room_pricing_package/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/hotels/doctype/hotel_room_pricing_package/__init__.py
diff --git a/erpnext/hotels/doctype/hotel_room_pricing_package/hotel_room_pricing_package.js b/erpnext/hotels/doctype/hotel_room_pricing_package/hotel_room_pricing_package.js
new file mode 100644
index 0000000..f6decd9
--- /dev/null
+++ b/erpnext/hotels/doctype/hotel_room_pricing_package/hotel_room_pricing_package.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Hotel Room Pricing Package', {
+ refresh: function(frm) {
+
+ }
+});
diff --git a/erpnext/hotels/doctype/hotel_room_pricing_package/hotel_room_pricing_package.json b/erpnext/hotels/doctype/hotel_room_pricing_package/hotel_room_pricing_package.json
new file mode 100644
index 0000000..92bd980
--- /dev/null
+++ b/erpnext/hotels/doctype/hotel_room_pricing_package/hotel_room_pricing_package.json
@@ -0,0 +1,162 @@
+{
+ "allow_copy": 0,
+ "allow_guest_to_view": 0,
+ "allow_import": 0,
+ "allow_rename": 0,
+ "beta": 0,
+ "creation": "2017-12-08 12:50:13.486090",
+ "custom": 0,
+ "docstatus": 0,
+ "doctype": "DocType",
+ "document_type": "",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "fields": [
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "from_date",
+ "fieldtype": "Date",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "From Date",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "to_date",
+ "fieldtype": "Date",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "To Date",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "hotel_room_package",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "Hotel Room Package",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Hotel Room Package",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "rate",
+ "fieldtype": "Currency",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "Rate",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ }
+ ],
+ "has_web_view": 0,
+ "hide_heading": 0,
+ "hide_toolbar": 0,
+ "idx": 0,
+ "image_view": 0,
+ "in_create": 0,
+ "is_submittable": 0,
+ "issingle": 0,
+ "istable": 1,
+ "max_attachments": 0,
+ "modified": "2017-12-08 12:52:01.743866",
+ "modified_by": "Administrator",
+ "module": "Hotels",
+ "name": "Hotel Room Pricing Package",
+ "name_case": "",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "read_only": 0,
+ "read_only_onload": 0,
+ "show_name_in_global_search": 0,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1,
+ "track_seen": 0
+}
\ No newline at end of file
diff --git a/erpnext/hotels/doctype/hotel_room_pricing_package/hotel_room_pricing_package.py b/erpnext/hotels/doctype/hotel_room_pricing_package/hotel_room_pricing_package.py
new file mode 100644
index 0000000..9ae9fcf
--- /dev/null
+++ b/erpnext/hotels/doctype/hotel_room_pricing_package/hotel_room_pricing_package.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe.model.document import Document
+
+class HotelRoomPricingPackage(Document):
+ pass
diff --git a/erpnext/hotels/doctype/hotel_room_pricing_package/test_hotel_room_pricing_package.js b/erpnext/hotels/doctype/hotel_room_pricing_package/test_hotel_room_pricing_package.js
new file mode 100644
index 0000000..73a561c
--- /dev/null
+++ b/erpnext/hotels/doctype/hotel_room_pricing_package/test_hotel_room_pricing_package.js
@@ -0,0 +1,23 @@
+/* eslint-disable */
+// rename this file from _test_[name] to test_[name] to activate
+// and remove above this line
+
+QUnit.test("test: Hotel Room Pricing Package", function (assert) {
+ let done = assert.async();
+
+ // number of asserts
+ assert.expect(1);
+
+ frappe.run_serially([
+ // insert a new Hotel Room Pricing Package
+ () => frappe.tests.make('Hotel Room Pricing Package', [
+ // values to be set
+ {key: 'value'}
+ ]),
+ () => {
+ assert.equal(cur_frm.doc.key, 'value');
+ },
+ () => done()
+ ]);
+
+});
diff --git a/erpnext/hotels/doctype/hotel_room_pricing_package/test_hotel_room_pricing_package.py b/erpnext/hotels/doctype/hotel_room_pricing_package/test_hotel_room_pricing_package.py
new file mode 100644
index 0000000..fec1c86
--- /dev/null
+++ b/erpnext/hotels/doctype/hotel_room_pricing_package/test_hotel_room_pricing_package.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+import frappe
+import unittest
+
+class TestHotelRoomPricingPackage(unittest.TestCase):
+ pass
diff --git a/erpnext/hotels/doctype/hotel_room_reservation/__init__.py b/erpnext/hotels/doctype/hotel_room_reservation/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/hotels/doctype/hotel_room_reservation/__init__.py
diff --git a/erpnext/hotels/doctype/hotel_room_reservation/hotel_room_reservation.js b/erpnext/hotels/doctype/hotel_room_reservation/hotel_room_reservation.js
new file mode 100644
index 0000000..2c9fd7b
--- /dev/null
+++ b/erpnext/hotels/doctype/hotel_room_reservation/hotel_room_reservation.js
@@ -0,0 +1,68 @@
+// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Hotel Room Reservation', {
+ refresh: function(frm) {
+ if(frm.doc.docstatus == 1){
+ frm.add_custom_button(__("Make Invoice"), ()=> {
+ frm.trigger("make_invoice");
+ });
+ }
+ },
+ from_date: function(frm) {
+ frm.trigger("recalculate_rates");
+ },
+ to_date: function(frm) {
+ frm.trigger("recalculate_rates");
+ },
+ recalculate_rates: function(frm) {
+ if (!frm.doc.from_date || !frm.doc.to_date
+ || !frm.doc.items.length){
+ return;
+ }
+ frappe.call({
+ "method": "erpnext.hotels.doctype.hotel_room_reservation.hotel_room_reservation.get_room_rate",
+ "args": {"hotel_room_reservation": frm.doc}
+ }).done((r)=> {
+ for (var i = 0; i < r.message.items.length; i++) {
+ frm.doc.items[i].rate = r.message.items[i].rate;
+ frm.doc.items[i].amount = r.message.items[i].amount;
+ }
+ frappe.run_serially([
+ ()=> frm.set_value("net_total", r.message.net_total),
+ ()=> frm.refresh_field("items")
+ ]);
+ });
+ },
+ make_invoice: function(frm) {
+ frappe.model.with_doc("Hotel Settings", "Hotel Settings", ()=>{
+ frappe.model.with_doctype("Sales Invoice", ()=>{
+ let hotel_settings = frappe.get_doc("Hotel Settings", "Hotel Settings");
+ let invoice = frappe.model.get_new_doc("Sales Invoice");
+ invoice.customer = frm.doc.customer || hotel_settings.default_customer;
+ if (hotel_settings.default_invoice_naming_series){
+ invoice.naming_series = hotel_settings.default_invoice_naming_series;
+ }
+ for (let d of frm.doc.items){
+ let invoice_item = frappe.model.add_child(invoice, "items")
+ invoice_item.item_code = d.item;
+ invoice_item.qty = d.qty;
+ invoice_item.rate = d.rate;
+ }
+ if (hotel_settings.default_taxes_and_charges){
+ invoice.taxes_and_charges = hotel_settings.default_taxes_and_charges;
+ }
+ frappe.set_route("Form", invoice.doctype, invoice.name);
+ });
+ });
+ }
+});
+
+frappe.ui.form.on('Hotel Room Reservation Item', {
+ item: function(frm, doctype, name) {
+ frm.trigger("recalculate_rates");
+ },
+ qty: function(frm) {
+ frm.trigger("recalculate_rates");
+ }
+});
diff --git a/erpnext/hotels/doctype/hotel_room_reservation/hotel_room_reservation.json b/erpnext/hotels/doctype/hotel_room_reservation/hotel_room_reservation.json
new file mode 100644
index 0000000..c65c4e1
--- /dev/null
+++ b/erpnext/hotels/doctype/hotel_room_reservation/hotel_room_reservation.json
@@ -0,0 +1,415 @@
+{
+ "allow_copy": 0,
+ "allow_guest_to_view": 0,
+ "allow_import": 1,
+ "allow_rename": 0,
+ "autoname": "HRES.#######",
+ "beta": 1,
+ "creation": "2017-12-08 13:01:34.829175",
+ "custom": 0,
+ "docstatus": 0,
+ "doctype": "DocType",
+ "document_type": "Document",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "fields": [
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "guest_name",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "Guest Name",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "customer",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Customer",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Customer",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "from_date",
+ "fieldtype": "Date",
+ "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": "From Date",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "to_date",
+ "fieldtype": "Date",
+ "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": "To Date",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "late_checkin",
+ "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": "Late Checkin",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "column_break_6",
+ "fieldtype": "Column Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "status",
+ "fieldtype": "Select",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Status",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Booked\nAdvance Paid\nInvoiced\nPaid\nCompleted\nCancelled",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "section_break_8",
+ "fieldtype": "Section Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "items",
+ "fieldtype": "Table",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Items",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Hotel Room Reservation Item",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "net_total",
+ "fieldtype": "Currency",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Net Total",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "amended_from",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Amended From",
+ "length": 0,
+ "no_copy": 1,
+ "options": "Hotel Room Reservation",
+ "permlevel": 0,
+ "print_hide": 1,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ }
+ ],
+ "has_web_view": 0,
+ "hide_heading": 0,
+ "hide_toolbar": 0,
+ "idx": 0,
+ "image_view": 0,
+ "in_create": 0,
+ "is_submittable": 1,
+ "issingle": 0,
+ "istable": 0,
+ "max_attachments": 0,
+ "modified": "2017-12-09 12:11:26.395419",
+ "modified_by": "Administrator",
+ "module": "Hotels",
+ "name": "Hotel Room Reservation",
+ "name_case": "",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "amend": 0,
+ "apply_user_permissions": 0,
+ "cancel": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "if_owner": 0,
+ "import": 0,
+ "permlevel": 0,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "set_user_permissions": 0,
+ "share": 1,
+ "submit": 0,
+ "write": 1
+ },
+ {
+ "amend": 1,
+ "apply_user_permissions": 0,
+ "cancel": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "if_owner": 0,
+ "import": 0,
+ "permlevel": 0,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Hotel Reservation User",
+ "set_user_permissions": 0,
+ "share": 1,
+ "submit": 1,
+ "write": 1
+ }
+ ],
+ "quick_entry": 1,
+ "read_only": 0,
+ "read_only_onload": 0,
+ "restrict_to_domain": "Hospitality",
+ "show_name_in_global_search": 0,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1,
+ "track_seen": 0
+}
\ No newline at end of file
diff --git a/erpnext/hotels/doctype/hotel_room_reservation/hotel_room_reservation.py b/erpnext/hotels/doctype/hotel_room_reservation/hotel_room_reservation.py
new file mode 100644
index 0000000..f3f76a9
--- /dev/null
+++ b/erpnext/hotels/doctype/hotel_room_reservation/hotel_room_reservation.py
@@ -0,0 +1,109 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe, json
+from frappe.model.document import Document
+from frappe import _
+from frappe.utils import date_diff, add_days, flt
+
+class HotelRoomUnavailableError(frappe.ValidationError): pass
+class HotelRoomPricingNotSetError(frappe.ValidationError): pass
+
+class HotelRoomReservation(Document):
+ def validate(self):
+ self.total_rooms = {}
+ self.set_rates()
+ self.validate_availability()
+
+ def validate_availability(self):
+ for i in xrange(date_diff(self.to_date, self.from_date)):
+ day = add_days(self.from_date, i)
+ self.rooms_booked = {}
+
+ for d in self.items:
+ if not d.item in self.rooms_booked:
+ self.rooms_booked[d.item] = 0
+
+ room_type = frappe.db.get_value("Hotel Room Package",
+ d.item, 'hotel_room_type')
+ rooms_booked = get_rooms_booked(room_type, day, exclude_reservation=self.name) \
+ + d.qty + self.rooms_booked.get(d.item)
+ total_rooms = self.get_total_rooms(d.item)
+ if total_rooms < rooms_booked:
+ frappe.throw(_("Hotel Rooms of type {0} are unavailable on {1}".format(d.item,
+ frappe.format(day, dict(fieldtype="Date")))), exc=HotelRoomUnavailableError)
+
+ self.rooms_booked[d.item] += rooms_booked
+
+ def get_total_rooms(self, item):
+ if not item in self.total_rooms:
+ self.total_rooms[item] = frappe.db.sql("""
+ select count(*)
+ from
+ `tabHotel Room Package` package
+ inner join
+ `tabHotel Room` room on package.hotel_room_type = room.hotel_room_type
+ where
+ package.item = %s""", item)[0][0] or 0
+
+ return self.total_rooms[item]
+
+ def set_rates(self):
+ self.net_total = 0
+ for d in self.items:
+ net_rate = 0.0
+ for i in xrange(date_diff(self.to_date, self.from_date)):
+ day = add_days(self.from_date, i)
+ if not d.item:
+ continue
+ day_rate = frappe.db.sql("""
+ select
+ item.rate
+ from
+ `tabHotel Room Pricing Item` item,
+ `tabHotel Room Pricing` pricing
+ where
+ item.parent = pricing.name
+ and item.item = %s
+ and %s between pricing.from_date
+ and pricing.to_date""", (d.item, day))
+
+ if day_rate:
+ net_rate += day_rate[0][0]
+ else:
+ frappe.throw(
+ _("Please set Hotel Room Rate on {}".format(
+ frappe.format(day, dict(fieldtype="Date")))), exc=HotelRoomPricingNotSetError)
+ d.rate = net_rate
+ d.amount = net_rate * flt(d.qty)
+ self.net_total += d.amount
+
+@frappe.whitelist()
+def get_room_rate(hotel_room_reservation):
+ """Calculate rate for each day as it may belong to different Hotel Room Pricing Item"""
+ doc = frappe.get_doc(json.loads(hotel_room_reservation))
+ doc.set_rates()
+ return doc.as_dict()
+
+def get_rooms_booked(room_type, day, exclude_reservation=None):
+ exclude_condition = ''
+ if exclude_reservation:
+ exclude_condition = 'and reservation.name != "{0}"'.format(frappe.db.escape(exclude_reservation))
+
+ return frappe.db.sql("""
+ select sum(item.qty)
+ from
+ `tabHotel Room Package` room_package,
+ `tabHotel Room Reservation Item` item,
+ `tabHotel Room Reservation` reservation
+ where
+ item.parent = reservation.name
+ and room_package.item = item.item
+ and room_package.hotel_room_type = %s
+ and reservation.docstatus = 1
+ {exclude_condition}
+ and %s between reservation.from_date
+ and reservation.to_date""".format(exclude_condition=exclude_condition),
+ (room_type, day))[0][0] or 0
diff --git a/erpnext/hotels/doctype/hotel_room_reservation/hotel_room_reservation_calendar.js b/erpnext/hotels/doctype/hotel_room_reservation/hotel_room_reservation_calendar.js
new file mode 100644
index 0000000..7f7322c
--- /dev/null
+++ b/erpnext/hotels/doctype/hotel_room_reservation/hotel_room_reservation_calendar.js
@@ -0,0 +1,9 @@
+frappe.views.calendar["Hotel Room Reservation"] = {
+ field_map: {
+ "start": "from_date",
+ "end": "to_date",
+ "id": "name",
+ "title": "guest_name",
+ "status": "status"
+ }
+}
\ No newline at end of file
diff --git a/erpnext/hotels/doctype/hotel_room_reservation/test_hotel_room_reservation.js b/erpnext/hotels/doctype/hotel_room_reservation/test_hotel_room_reservation.js
new file mode 100644
index 0000000..2897139
--- /dev/null
+++ b/erpnext/hotels/doctype/hotel_room_reservation/test_hotel_room_reservation.js
@@ -0,0 +1,23 @@
+/* eslint-disable */
+// rename this file from _test_[name] to test_[name] to activate
+// and remove above this line
+
+QUnit.test("test: Hotel Room Reservation", function (assert) {
+ let done = assert.async();
+
+ // number of asserts
+ assert.expect(1);
+
+ frappe.run_serially([
+ // insert a new Hotel Room Reservation
+ () => frappe.tests.make('Hotel Room Reservation', [
+ // values to be set
+ {key: 'value'}
+ ]),
+ () => {
+ assert.equal(cur_frm.doc.key, 'value');
+ },
+ () => done()
+ ]);
+
+});
diff --git a/erpnext/hotels/doctype/hotel_room_reservation/test_hotel_room_reservation.py b/erpnext/hotels/doctype/hotel_room_reservation/test_hotel_room_reservation.py
new file mode 100644
index 0000000..55c6311
--- /dev/null
+++ b/erpnext/hotels/doctype/hotel_room_reservation/test_hotel_room_reservation.py
@@ -0,0 +1,61 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+import frappe
+import unittest
+from erpnext.hotels.doctype.hotel_room_reservation.hotel_room_reservation import HotelRoomPricingNotSetError, HotelRoomUnavailableError
+test_dependencies = ["Hotel Room Pricing", "Hotel Room"]
+
+class TestHotelRoomReservation(unittest.TestCase):
+ def setUp(self):
+ frappe.db.sql("delete from `tabHotel Room Reservation`")
+ frappe.db.sql("delete from `tabHotel Room Reservation Item`")
+
+ def test_reservation(self):
+ reservation = make_reservation(
+ from_date="2017-01-01",
+ to_date="2017-01-03",
+ items=[
+ dict(item="Basic Room with Dinner", qty=2)
+ ]
+ )
+ reservation.insert()
+ self.assertEqual(reservation.net_total, 48000)
+
+ def test_price_not_set(self):
+ reservation = make_reservation(
+ from_date="2016-01-01",
+ to_date="2016-01-03",
+ items=[
+ dict(item="Basic Room with Dinner", qty=2)
+ ]
+ )
+ self.assertRaises(HotelRoomPricingNotSetError, reservation.insert)
+
+ def test_room_unavailable(self):
+ reservation = make_reservation(
+ from_date="2017-01-01",
+ to_date="2017-01-03",
+ items=[
+ dict(item="Basic Room with Dinner", qty=2),
+ ]
+ )
+ reservation.insert()
+
+ reservation = make_reservation(
+ from_date="2017-01-01",
+ to_date="2017-01-03",
+ items=[
+ dict(item="Basic Room with Dinner", qty=20),
+ ]
+ )
+ self.assertRaises(HotelRoomUnavailableError, reservation.insert)
+
+def make_reservation(**kwargs):
+ kwargs["doctype"] = "Hotel Room Reservation"
+ if not "guest_name" in kwargs:
+ kwargs["guest_name"] = "Test Guest"
+ doc = frappe.get_doc(kwargs)
+ return doc
diff --git a/erpnext/hotels/doctype/hotel_room_reservation_item/__init__.py b/erpnext/hotels/doctype/hotel_room_reservation_item/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/hotels/doctype/hotel_room_reservation_item/__init__.py
diff --git a/erpnext/hotels/doctype/hotel_room_reservation_item/hotel_room_reservation_item.json b/erpnext/hotels/doctype/hotel_room_reservation_item/hotel_room_reservation_item.json
new file mode 100644
index 0000000..2b7931e
--- /dev/null
+++ b/erpnext/hotels/doctype/hotel_room_reservation_item/hotel_room_reservation_item.json
@@ -0,0 +1,195 @@
+{
+ "allow_copy": 0,
+ "allow_guest_to_view": 0,
+ "allow_import": 0,
+ "allow_rename": 0,
+ "autoname": "",
+ "beta": 0,
+ "creation": "2017-12-08 12:58:21.733330",
+ "custom": 0,
+ "docstatus": 0,
+ "doctype": "DocType",
+ "document_type": "",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "fields": [
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "item",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "Item",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Item",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "qty",
+ "fieldtype": "Int",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "Qty",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "currency",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Currency",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Currency",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "rate",
+ "fieldtype": "Currency",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "Rate",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "amount",
+ "fieldtype": "Currency",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "Amount",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ }
+ ],
+ "has_web_view": 0,
+ "hide_heading": 0,
+ "hide_toolbar": 0,
+ "idx": 0,
+ "image_view": 0,
+ "in_create": 0,
+ "is_submittable": 0,
+ "issingle": 0,
+ "istable": 1,
+ "max_attachments": 0,
+ "modified": "2017-12-09 12:04:34.562956",
+ "modified_by": "Administrator",
+ "module": "Hotels",
+ "name": "Hotel Room Reservation Item",
+ "name_case": "",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "read_only": 0,
+ "read_only_onload": 0,
+ "restrict_to_domain": "Hospitality",
+ "show_name_in_global_search": 0,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1,
+ "track_seen": 0
+}
\ No newline at end of file
diff --git a/erpnext/hotels/doctype/hotel_room_reservation_item/hotel_room_reservation_item.py b/erpnext/hotels/doctype/hotel_room_reservation_item/hotel_room_reservation_item.py
new file mode 100644
index 0000000..3406fae
--- /dev/null
+++ b/erpnext/hotels/doctype/hotel_room_reservation_item/hotel_room_reservation_item.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe.model.document import Document
+
+class HotelRoomReservationItem(Document):
+ pass
diff --git a/erpnext/hotels/doctype/hotel_room_type/__init__.py b/erpnext/hotels/doctype/hotel_room_type/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/hotels/doctype/hotel_room_type/__init__.py
diff --git a/erpnext/hotels/doctype/hotel_room_type/hotel_room_type.js b/erpnext/hotels/doctype/hotel_room_type/hotel_room_type.js
new file mode 100644
index 0000000..d73835d
--- /dev/null
+++ b/erpnext/hotels/doctype/hotel_room_type/hotel_room_type.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Hotel Room Type', {
+ refresh: function(frm) {
+
+ }
+});
diff --git a/erpnext/hotels/doctype/hotel_room_type/hotel_room_type.json b/erpnext/hotels/doctype/hotel_room_type/hotel_room_type.json
new file mode 100644
index 0000000..3d26413
--- /dev/null
+++ b/erpnext/hotels/doctype/hotel_room_type/hotel_room_type.json
@@ -0,0 +1,204 @@
+{
+ "allow_copy": 0,
+ "allow_guest_to_view": 0,
+ "allow_import": 1,
+ "allow_rename": 1,
+ "autoname": "prompt",
+ "beta": 1,
+ "creation": "2017-12-08 12:38:29.485175",
+ "custom": 0,
+ "docstatus": 0,
+ "doctype": "DocType",
+ "document_type": "Setup",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "fields": [
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "capacity",
+ "fieldtype": "Int",
+ "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": "Capacity",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "extra_bed_capacity",
+ "fieldtype": "Int",
+ "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": "Extra Bed Capacity",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "section_break_3",
+ "fieldtype": "Section Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "amenities",
+ "fieldtype": "Table",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Amenities",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Hotel Room Amenity",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ }
+ ],
+ "has_web_view": 0,
+ "hide_heading": 0,
+ "hide_toolbar": 0,
+ "idx": 0,
+ "image_view": 0,
+ "in_create": 0,
+ "is_submittable": 0,
+ "issingle": 0,
+ "istable": 0,
+ "max_attachments": 0,
+ "modified": "2017-12-09 12:10:23.355486",
+ "modified_by": "Administrator",
+ "module": "Hotels",
+ "name": "Hotel Room Type",
+ "name_case": "",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "amend": 0,
+ "apply_user_permissions": 0,
+ "cancel": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "if_owner": 0,
+ "import": 0,
+ "permlevel": 0,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "set_user_permissions": 0,
+ "share": 1,
+ "submit": 0,
+ "write": 1
+ },
+ {
+ "amend": 0,
+ "apply_user_permissions": 0,
+ "cancel": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "if_owner": 0,
+ "import": 0,
+ "permlevel": 0,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Hotel Manager",
+ "set_user_permissions": 0,
+ "share": 1,
+ "submit": 0,
+ "write": 1
+ }
+ ],
+ "quick_entry": 1,
+ "read_only": 0,
+ "read_only_onload": 0,
+ "restrict_to_domain": "Hospitality",
+ "show_name_in_global_search": 0,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1,
+ "track_seen": 0
+}
\ No newline at end of file
diff --git a/erpnext/hotels/doctype/hotel_room_type/hotel_room_type.py b/erpnext/hotels/doctype/hotel_room_type/hotel_room_type.py
new file mode 100644
index 0000000..1fc1303
--- /dev/null
+++ b/erpnext/hotels/doctype/hotel_room_type/hotel_room_type.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe.model.document import Document
+
+class HotelRoomType(Document):
+ pass
diff --git a/erpnext/hotels/doctype/hotel_room_type/test_hotel_room_type.js b/erpnext/hotels/doctype/hotel_room_type/test_hotel_room_type.js
new file mode 100644
index 0000000..e2dd578
--- /dev/null
+++ b/erpnext/hotels/doctype/hotel_room_type/test_hotel_room_type.js
@@ -0,0 +1,23 @@
+/* eslint-disable */
+// rename this file from _test_[name] to test_[name] to activate
+// and remove above this line
+
+QUnit.test("test: Hotel Room Type", function (assert) {
+ let done = assert.async();
+
+ // number of asserts
+ assert.expect(1);
+
+ frappe.run_serially([
+ // insert a new Hotel Room Type
+ () => frappe.tests.make('Hotel Room Type', [
+ // values to be set
+ {key: 'value'}
+ ]),
+ () => {
+ assert.equal(cur_frm.doc.key, 'value');
+ },
+ () => done()
+ ]);
+
+});
diff --git a/erpnext/hotels/doctype/hotel_room_type/test_hotel_room_type.py b/erpnext/hotels/doctype/hotel_room_type/test_hotel_room_type.py
new file mode 100644
index 0000000..3b243e9
--- /dev/null
+++ b/erpnext/hotels/doctype/hotel_room_type/test_hotel_room_type.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+import frappe
+import unittest
+
+class TestHotelRoomType(unittest.TestCase):
+ pass
diff --git a/erpnext/hotels/doctype/hotel_settings/__init__.py b/erpnext/hotels/doctype/hotel_settings/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/hotels/doctype/hotel_settings/__init__.py
diff --git a/erpnext/hotels/doctype/hotel_settings/hotel_settings.js b/erpnext/hotels/doctype/hotel_settings/hotel_settings.js
new file mode 100644
index 0000000..0b4a2c3
--- /dev/null
+++ b/erpnext/hotels/doctype/hotel_settings/hotel_settings.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Hotel Settings', {
+ refresh: function(frm) {
+
+ }
+});
diff --git a/erpnext/hotels/doctype/hotel_settings/hotel_settings.json b/erpnext/hotels/doctype/hotel_settings/hotel_settings.json
new file mode 100644
index 0000000..d9f5572
--- /dev/null
+++ b/erpnext/hotels/doctype/hotel_settings/hotel_settings.json
@@ -0,0 +1,175 @@
+{
+ "allow_copy": 0,
+ "allow_guest_to_view": 0,
+ "allow_import": 0,
+ "allow_rename": 0,
+ "beta": 1,
+ "creation": "2017-12-08 17:50:24.523107",
+ "custom": 0,
+ "docstatus": 0,
+ "doctype": "DocType",
+ "document_type": "Setup",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "fields": [
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "default_customer",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "Default Customer",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Customer",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "default_taxes_and_charges",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Default Taxes and Charges",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Sales Taxes and Charges Template",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "default_invoice_naming_series",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Default Invoice Naming Series",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ }
+ ],
+ "has_web_view": 0,
+ "hide_heading": 0,
+ "hide_toolbar": 0,
+ "idx": 0,
+ "image_view": 0,
+ "in_create": 0,
+ "is_submittable": 0,
+ "issingle": 1,
+ "istable": 0,
+ "max_attachments": 0,
+ "modified": "2017-12-09 12:11:12.857308",
+ "modified_by": "Administrator",
+ "module": "Hotels",
+ "name": "Hotel Settings",
+ "name_case": "",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "amend": 0,
+ "apply_user_permissions": 0,
+ "cancel": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 0,
+ "if_owner": 0,
+ "import": 0,
+ "permlevel": 0,
+ "print": 1,
+ "read": 1,
+ "report": 0,
+ "role": "System Manager",
+ "set_user_permissions": 0,
+ "share": 1,
+ "submit": 0,
+ "write": 1
+ },
+ {
+ "amend": 0,
+ "apply_user_permissions": 0,
+ "cancel": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 0,
+ "if_owner": 0,
+ "import": 0,
+ "permlevel": 0,
+ "print": 1,
+ "read": 1,
+ "report": 0,
+ "role": "Hotel Manager",
+ "set_user_permissions": 0,
+ "share": 1,
+ "submit": 0,
+ "write": 1
+ }
+ ],
+ "quick_entry": 0,
+ "read_only": 0,
+ "read_only_onload": 0,
+ "restrict_to_domain": "Hospitality",
+ "show_name_in_global_search": 0,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1,
+ "track_seen": 0
+}
\ No newline at end of file
diff --git a/erpnext/hotels/doctype/hotel_settings/hotel_settings.py b/erpnext/hotels/doctype/hotel_settings/hotel_settings.py
new file mode 100644
index 0000000..d78bca1
--- /dev/null
+++ b/erpnext/hotels/doctype/hotel_settings/hotel_settings.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe.model.document import Document
+
+class HotelSettings(Document):
+ pass
diff --git a/erpnext/hotels/doctype/hotel_settings/test_hotel_settings.js b/erpnext/hotels/doctype/hotel_settings/test_hotel_settings.js
new file mode 100644
index 0000000..bc0b7f8
--- /dev/null
+++ b/erpnext/hotels/doctype/hotel_settings/test_hotel_settings.js
@@ -0,0 +1,23 @@
+/* eslint-disable */
+// rename this file from _test_[name] to test_[name] to activate
+// and remove above this line
+
+QUnit.test("test: Hotel Settings", function (assert) {
+ let done = assert.async();
+
+ // number of asserts
+ assert.expect(1);
+
+ frappe.run_serially([
+ // insert a new Hotel Settings
+ () => frappe.tests.make('Hotel Settings', [
+ // values to be set
+ {key: 'value'}
+ ]),
+ () => {
+ assert.equal(cur_frm.doc.key, 'value');
+ },
+ () => done()
+ ]);
+
+});
diff --git a/erpnext/hotels/doctype/hotel_settings/test_hotel_settings.py b/erpnext/hotels/doctype/hotel_settings/test_hotel_settings.py
new file mode 100644
index 0000000..a081acc
--- /dev/null
+++ b/erpnext/hotels/doctype/hotel_settings/test_hotel_settings.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+import frappe
+import unittest
+
+class TestHotelSettings(unittest.TestCase):
+ pass
diff --git a/erpnext/hotels/report/__init__.py b/erpnext/hotels/report/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/hotels/report/__init__.py
diff --git a/erpnext/hotels/report/hotel_room_occupancy/__init__.py b/erpnext/hotels/report/hotel_room_occupancy/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/hotels/report/hotel_room_occupancy/__init__.py
diff --git a/erpnext/hotels/report/hotel_room_occupancy/hotel_room_occupancy.js b/erpnext/hotels/report/hotel_room_occupancy/hotel_room_occupancy.js
new file mode 100644
index 0000000..81efb2d
--- /dev/null
+++ b/erpnext/hotels/report/hotel_room_occupancy/hotel_room_occupancy.js
@@ -0,0 +1,22 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Hotel Room Occupancy"] = {
+ "filters": [
+ {
+ "fieldname":"from_date",
+ "label": __("From Date"),
+ "fieldtype": "Date",
+ "default": frappe.datetime.now_date(),
+ "reqd":1
+ },
+ {
+ "fieldname":"to_date",
+ "label": __("To Date"),
+ "fieldtype": "Date",
+ "default": frappe.datetime.now_date(),
+ "reqd":1
+ }
+ ]
+}
diff --git a/erpnext/hotels/report/hotel_room_occupancy/hotel_room_occupancy.json b/erpnext/hotels/report/hotel_room_occupancy/hotel_room_occupancy.json
new file mode 100644
index 0000000..782a48b
--- /dev/null
+++ b/erpnext/hotels/report/hotel_room_occupancy/hotel_room_occupancy.json
@@ -0,0 +1,26 @@
+{
+ "add_total_row": 1,
+ "apply_user_permissions": 1,
+ "creation": "2017-12-09 14:31:26.306705",
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "idx": 0,
+ "is_standard": "Yes",
+ "modified": "2017-12-09 14:31:26.306705",
+ "modified_by": "Administrator",
+ "module": "Hotels",
+ "name": "Hotel Room Occupancy",
+ "owner": "Administrator",
+ "ref_doctype": "Hotel Room Reservation",
+ "report_name": "Hotel Room Occupancy",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "System Manager"
+ },
+ {
+ "role": "Hotel Reservation User"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/hotels/report/hotel_room_occupancy/hotel_room_occupancy.py b/erpnext/hotels/report/hotel_room_occupancy/hotel_room_occupancy.py
new file mode 100644
index 0000000..aebeb45
--- /dev/null
+++ b/erpnext/hotels/report/hotel_room_occupancy/hotel_room_occupancy.py
@@ -0,0 +1,33 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe import _
+from frappe.utils import add_days, date_diff
+
+from erpnext.hotels.doctype.hotel_room_reservation.hotel_room_reservation import get_rooms_booked
+
+def execute(filters=None):
+ columns = get_columns(filters)
+ data = get_data(filters)
+ return columns, data
+
+def get_columns(filters):
+ columns = [
+ dict(label=_("Room Type"), fieldname="room_type"),
+ dict(label=_("Rooms Booked"), fieldtype="Int")
+ ]
+ return columns
+
+def get_data(filters):
+ out = []
+ for room_type in frappe.get_all('Hotel Room Type'):
+ total_booked = 0
+ for i in xrange(date_diff(filters.to_date, filters.from_date)):
+ day = add_days(filters.from_date, i)
+ total_booked += get_rooms_booked(room_type.name, day)
+
+ out.append([room_type.name, total_booked])
+
+ return out
\ No newline at end of file
diff --git a/erpnext/hr/doctype/attendance/attendance.py b/erpnext/hr/doctype/attendance/attendance.py
index fd7344a..7b04f7d 100644
--- a/erpnext/hr/doctype/attendance/attendance.py
+++ b/erpnext/hr/doctype/attendance/attendance.py
@@ -21,7 +21,7 @@
def check_leave_record(self):
leave_record = frappe.db.sql("""select leave_type, half_day from `tabLeave Application`
- where employee = %s and %s between from_date and to_date and workflow_state = 'Approved'
+ where employee = %s and %s between from_date and to_date
and docstatus = 1""", (self.employee, self.attendance_date), as_dict=True)
if leave_record:
if leave_record[0].half_day:
diff --git a/erpnext/hr/doctype/leave_application/leave_application.js b/erpnext/hr/doctype/leave_application/leave_application.js
index c2d8326..2e6e451 100755
--- a/erpnext/hr/doctype/leave_application/leave_application.js
+++ b/erpnext/hr/doctype/leave_application/leave_application.js
@@ -10,15 +10,6 @@
frm.set_value("posting_date", frappe.datetime.get_today());
}
- frm.set_query("leave_approver", function() {
- return {
- query: "erpnext.hr.doctype.leave_application.leave_application.get_approvers",
- filters: {
- employee: frm.doc.employee
- }
- };
- });
-
frm.set_query("employee", erpnext.queries.employee);
},
@@ -29,14 +20,11 @@
refresh: function(frm) {
if (frm.is_new()) {
- frm.set_value("workflow_state", "Open");
frm.trigger("calculate_total_days");
}
- },
-
- leave_approver: function(frm) {
- if(frm.doc.leave_approver){
- frm.set_value("leave_approver_name", frappe.user.full_name(frm.doc.leave_approver));
+ cur_frm.set_intro("");
+ if(frm.doc.__islocal && !in_list(frappe.user_roles, "Employee")) {
+ frm.set_intro(__("Fill the form and save it"));
}
},
diff --git a/erpnext/hr/doctype/leave_application/leave_application.json b/erpnext/hr/doctype/leave_application/leave_application.json
index 88b5a55..2435d06 100644
--- a/erpnext/hr/doctype/leave_application/leave_application.json
+++ b/erpnext/hr/doctype/leave_application/leave_application.json
@@ -433,96 +433,6 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
- "fieldname": "column_break_15",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "description": "",
- "fieldname": "leave_approver",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 1,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 1,
- "label": "Leave Approver",
- "length": 0,
- "no_copy": 0,
- "options": "User",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "leave_approver_name",
- "fieldtype": "Read Only",
- "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": "Leave Approver Name",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "sb10",
"fieldtype": "Section Break",
"hidden": 0,
@@ -736,7 +646,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 3,
- "modified": "2018-01-22 12:10:40.757274",
+ "modified": "2018-02-12 13:10:05.766762",
"modified_by": "Administrator",
"module": "HR",
"name": "Leave Application",
@@ -885,7 +795,7 @@
"submit": 0,
"write": 1
}
- ],
+ ],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py
index 5be44af..22fd0e5 100755
--- a/erpnext/hr/doctype/leave_application/leave_application.py
+++ b/erpnext/hr/doctype/leave_application/leave_application.py
@@ -21,17 +21,10 @@
from frappe.model.document import Document
class LeaveApplication(Document):
def get_feed(self):
- return _("{0}: From {0} of type {1}").format(self.workflow_state, self.employee_name, self.leave_type)
+ return _("{0}: From {0} of type {1}").format(self.employee_name, self.leave_type)
def validate(self):
- if self.get("__islocal"): self.workflow_state = 'Open'
- if not getattr(self, "__islocal", None) and frappe.db.exists(self.doctype, self.name):
- self.previous_doc = frappe.get_value(self.doctype, self.name, "leave_approver", as_dict=True)
- else:
- self.previous_doc = None
-
set_employee_name(self)
-
self.validate_dates()
self.validate_balance_leaves()
self.validate_leave_overlap()
@@ -39,22 +32,12 @@
self.show_block_day_warning()
self.validate_block_days()
self.validate_salary_processed_days()
- self.validate_leave_approver()
self.validate_attendance()
- def on_update(self):
- if (not self.previous_doc and self.leave_approver) or (self.previous_doc and \
- self.workflow_state == "Open" and self.previous_doc.leave_approver != self.leave_approver):
- # notify leave approver about creation
- self.notify_leave_approver()
-
def on_submit(self):
self.validate_back_dated_application()
- # notify leave applier about approval
- self.notify_employee(self.workflow_state)
-
def on_cancel(self):
# notify leave applier about cancellation
self.notify_employee("cancelled")
@@ -128,7 +111,7 @@
block_dates = get_applicable_block_dates(self.from_date, self.to_date,
self.employee, self.company)
- if block_dates and self.workflow_state == "Approved":
+ if block_dates and self.docstatus == 1:
frappe.throw(_("You are not authorized to approve leaves on Block Dates"), LeaveDayBlockedError)
def validate_balance_leaves(self):
@@ -143,7 +126,7 @@
self.leave_balance = get_leave_balance_on(self.employee, self.leave_type, self.from_date,
consider_all_leaves_in_the_allocation_period=True)
- if self.workflow_state != "Rejected" and self.leave_balance < self.total_leave_days:
+ if self.leave_balance < self.total_leave_days:
if frappe.db.get_value("Leave Type", self.leave_type, "allow_negative"):
frappe.msgprint(_("Note: There is not enough leave balance for Leave Type {0}")
.format(self.leave_type))
@@ -160,7 +143,7 @@
select
name, leave_type, posting_date, from_date, to_date, total_leave_days, half_day_date
from `tabLeave Application`
- where employee = %(employee)s and docstatus < 2 and workflow_state in ("Open", "Approved")
+ where employee = %(employee)s and docstatus < 2
and to_date >= %(from_date)s and from_date <= %(to_date)s
and name != %(name)s""", {
"employee": self.employee,
@@ -190,7 +173,6 @@
leave_count_on_half_day_date = frappe.db.sql("""select count(name) from `tabLeave Application`
where employee = %(employee)s
and docstatus < 2
- and workflow_state in ("Open", "Approved")
and half_day = 1
and half_day_date = %(half_day_date)s
and name != %(name)s""", {
@@ -206,23 +188,6 @@
if max_days and self.total_leave_days > cint(max_days):
frappe.throw(_("Leave of type {0} cannot be longer than {1}").format(self.leave_type, max_days))
- def validate_leave_approver(self):
- employee = frappe.get_doc("Employee", self.employee)
- leave_approvers = [l.leave_approver for l in employee.get("leave_approvers")]
-
- if len(leave_approvers) and self.leave_approver not in leave_approvers:
- frappe.throw(_("Leave approver must be one of {0}")
- .format(comma_or(leave_approvers)), InvalidLeaveApproverError)
-
- elif self.leave_approver and not frappe.db.sql("""select name from `tabHas Role`
- where parent=%s and role='Leave Approver'""", self.leave_approver):
- frappe.throw(_("{0} ({1}) must have role 'Leave Approver'")\
- .format(get_fullname(self.leave_approver), self.leave_approver), InvalidLeaveApproverError)
-
- elif self.docstatus==1 and len(leave_approvers) and self.leave_approver != frappe.session.user:
- frappe.throw(_("Only the selected Leave Approver can submit this Leave Application"),
- LeaveApproverIdentityError)
-
def validate_attendance(self):
attendance = frappe.db.sql("""select name from `tabAttendance` where employee = %s and (attendance_date between %s and %s)
and status = "Present" and docstatus = 1""",
@@ -231,7 +196,7 @@
frappe.throw(_("Attendance for employee {0} is already marked for this day").format(self.employee),
AttendanceAlreadyMarkedError)
- def notify_employee(self, workflow_state):
+ def notify_employee(self):
employee = frappe.get_doc("Employee", self.employee)
if not employee.user_id:
return
@@ -246,19 +211,15 @@
message += "Leave Type: {leave_type}".format(leave_type=self.leave_type)+"<br>"
message += "From Date: {from_date}".format(from_date=self.from_date)+"<br>"
message += "To Date: {to_date}".format(to_date=self.to_date)+"<br>"
- message += "Status: {workflow_state}".format(workflow_state=_(workflow_state))
return message
self.notify({
# for post in messages
"message": _get_message(url=True),
"message_to": employee.user_id,
- "subject": (_("Leave Application") + ": %s - %s") % (self.name, _(workflow_state))
+ "subject": (_("Leave Application") + ": %s - %s") % (self.name)
})
- def notify_leave_approver(self):
- employee = frappe.get_doc("Employee", self.employee)
-
def _get_message(url=False):
name = self.name
employee_name = cstr(employee.employee_name)
@@ -275,7 +236,6 @@
self.notify({
# for post in messages
"message": _get_message(url=True),
- "message_to": self.leave_approver,
# for email
"subject": (_("New Leave Application") + ": %s - " + _("Employee") + ": %s") % (self.name, cstr(employee.employee_name))
@@ -321,23 +281,6 @@
pass
@frappe.whitelist()
-def get_approvers(doctype, txt, searchfield, start, page_len, filters):
- if not filters.get("employee"):
- frappe.throw(_("Please select Employee Record first."))
-
- employee_user = frappe.get_value("Employee", filters.get("employee"), "user_id")
-
- approvers_list = frappe.db.sql("""select user.name, user.first_name, user.last_name from
- tabUser user, `tabEmployee Leave Approver` approver where
- approver.parent = %s
- and user.name like %s
- and approver.leave_approver=user.name""", (filters.get("employee"), "%" + txt + "%"))
-
- if not approvers_list:
- approvers_list = get_approver_list(employee_user)
- return approvers_list
-
-@frappe.whitelist()
def get_number_of_leave_days(employee, leave_type, from_date, to_date, half_day = None, half_day_date = None):
number_of_days = 0
if half_day == 1:
@@ -371,7 +314,7 @@
select employee, leave_type, from_date, to_date, total_leave_days
from `tabLeave Application`
where employee=%(employee)s and leave_type=%(leave_type)s
- and workflow_state="Approved" and docstatus=1
+ and docstatus=1
and (from_date between %(from_date)s and %(to_date)s
or to_date between %(from_date)s and %(to_date)s
or (from_date < %(from_date)s and to_date > %(to_date)s))
@@ -471,11 +414,10 @@
def add_leaves(events, start, end, match_conditions=None):
query = """select name, from_date, to_date, employee_name, half_day,
- workflow_state, employee, docstatus
+ employee, docstatus
from `tabLeave Application` where
from_date <= %(end)s and to_date >= %(start)s <= to_date
- and docstatus < 2
- and workflow_state!="Rejected" """
+ and docstatus < 2"""
if match_conditions:
query += match_conditions
@@ -485,7 +427,6 @@
"doctype": "Leave Application",
"from_date": d.from_date,
"to_date": d.to_date,
- "workflow_state": d.workflow_state,
"title": cstr(d.employee_name) + \
(d.half_day and _(" (Half Day)") or ""),
"docstatus": d.docstatus
diff --git a/erpnext/hr/doctype/leave_application/leave_application_calendar.js b/erpnext/hr/doctype/leave_application/leave_application_calendar.js
index b06b40f..0286f30 100644
--- a/erpnext/hr/doctype/leave_application/leave_application_calendar.js
+++ b/erpnext/hr/doctype/leave_application/leave_application_calendar.js
@@ -7,7 +7,7 @@
"end": "to_date",
"id": "name",
"title": "title",
- "workflow_state": "workflow_state",
+ "docstatus": 1
},
options: {
header: {
diff --git a/erpnext/hr/doctype/leave_application/leave_application_list.js b/erpnext/hr/doctype/leave_application/leave_application_list.js
index 7798ae7..d7588da 100644
--- a/erpnext/hr/doctype/leave_application/leave_application_list.js
+++ b/erpnext/hr/doctype/leave_application/leave_application_list.js
@@ -1,7 +1,3 @@
frappe.listview_settings['Leave Application'] = {
- add_fields: ["workflow_state", "leave_type", "employee", "employee_name", "total_leave_days", "from_date", "to_date"],
- get_indicator: function(doc) {
- return [__(doc.workflow_state), frappe.utils.guess_colour(doc.workflow_state),
- "workflow_state,=," + doc.workflow_state];
- }
+ add_fields: ["leave_type", "employee", "employee_name", "total_leave_days", "from_date", "to_date"]
};
diff --git a/erpnext/hr/doctype/leave_application/test_leave_application.js b/erpnext/hr/doctype/leave_application/test_leave_application.js
index 6d51b71..6d7b6a7 100644
--- a/erpnext/hr/doctype/leave_application/test_leave_application.js
+++ b/erpnext/hr/doctype/leave_application/test_leave_application.js
@@ -16,7 +16,6 @@
{to_date: leave_date},
{half_day: 1},
{employee: employee.message.name},
- {leave_approver: "Administrator"},
{follow_via_email: 0}
]);
},
@@ -36,8 +35,8 @@
() => frappe.set_route("List", "Leave Application", "List"),
() => frappe.timeout(1),
// // check approved application in list
- () => assert.deepEqual(["Test Employee 1", "Approved"], [cur_list.data[0].employee_name, cur_list.data[0].workflow_state]),
- // "leave for correct employee is approved"),
+ () => assert.deepEqual(["Test Employee 1", 1], [cur_list.data[0].employee_name, cur_list.data[0].docstatus]),
+ // "leave for correct employee is submitted"),
() => done()
]);
});
\ No newline at end of file
diff --git a/erpnext/hr/doctype/leave_application/test_leave_application.py b/erpnext/hr/doctype/leave_application/test_leave_application.py
index b2f6054..520d7c9 100644
--- a/erpnext/hr/doctype/leave_application/test_leave_application.py
+++ b/erpnext/hr/doctype/leave_application/test_leave_application.py
@@ -103,7 +103,6 @@
application = self.get_application(_test_records[0])
application.insert()
- application.workflow_state = "Approved"
self.assertRaises(LeaveDayBlockedError, application.submit)
frappe.set_user("test1@example.com")
@@ -127,11 +126,9 @@
make_allocation_record()
application = self.get_application(_test_records[0])
- application.leave_approver = "test2@example.com"
application.insert()
application = self.get_application(_test_records[0])
- application.leave_approver = "test2@example.com"
self.assertRaises(OverlapError, application.insert)
def test_overlap_with_half_day_1(self):
@@ -148,14 +145,12 @@
# leave from 1-5, half day on 3rd
application = self.get_application(_test_records[0])
- application.leave_approver = "test2@example.com"
application.half_day = 1
application.half_day_date = "2013-01-03"
application.insert()
# Apply again for a half day leave on 3rd
application = self.get_application(_test_records[0])
- application.leave_approver = "test2@example.com"
application.from_date = "2013-01-03"
application.to_date = "2013-01-03"
application.half_day = 1
@@ -164,7 +159,6 @@
# Apply again for a half day leave on 3rd
application = self.get_application(_test_records[0])
- application.leave_approver = "test2@example.com"
application.from_date = "2013-01-03"
application.to_date = "2013-01-03"
application.half_day = 1
@@ -186,12 +180,10 @@
# leave from 1-5, no half day
application = self.get_application(_test_records[0])
- application.leave_approver = "test2@example.com"
application.insert()
# Apply again for a half day leave on 1st
application = self.get_application(_test_records[0])
- application.leave_approver = "test2@example.com"
application.half_day = 1
application.half_day_date = application.from_date
@@ -211,14 +203,12 @@
# leave from 1-5, half day on 5th
application = self.get_application(_test_records[0])
- application.leave_approver = "test2@example.com"
application.half_day = 1
application.half_day_date = "2013-01-05"
application.insert()
# Apply leave from 4-7, half day on 5th
application = self.get_application(_test_records[0])
- application.leave_approver = "test2@example.com"
application.from_date = "2013-01-04"
application.to_date = "2013-01-07"
application.half_day = 1
@@ -228,7 +218,6 @@
# Apply leave from 5-7, half day on 5th
application = self.get_application(_test_records[0])
- application.leave_approver = "test2@example.com"
application.from_date = "2013-01-05"
application.to_date = "2013-01-07"
application.half_day = 1
@@ -246,7 +235,6 @@
make_allocation_record(employee="_T-Employee-0002")
application = self.get_application(_test_records[1])
- application.leave_approver = "test@example.com"
frappe.db.set_value("Leave Block List", "_Test Leave Block List",
"applies_to_all_departments", 1)
@@ -257,7 +245,6 @@
application.insert()
frappe.set_user("test@example.com")
- application.workflow_state = "Approved"
# clear permlevel access cache on change user
del application._has_access_to
@@ -267,114 +254,6 @@
frappe.db.set_value("Leave Block List", "_Test Leave Block List",
"applies_to_all_departments", 0)
- def test_leave_approval(self):
- self._clear_roles()
-
- from frappe.utils.user import add_role
- add_role("test@example.com", "Employee")
- add_role("test1@example.com", "HR User")
- add_role("test1@example.com", "Leave Approver")
- add_role("test2@example.com", "Leave Approver")
-
- self._test_leave_approval_basic_case()
- self._test_leave_approval_invalid_leave_approver_insert()
- self._test_leave_approval_invalid_leave_approver_submit()
- self._test_leave_approval_valid_leave_approver_insert()
-
- def _test_leave_approval_basic_case(self):
- self._clear_applications()
-
- self._add_employee_leave_approver("_T-Employee-0001", "test1@example.com")
-
- # create leave application as Employee
- frappe.set_user("test@example.com")
-
- make_allocation_record()
-
- application = self.get_application(_test_records[0])
- application.leave_approver = "test1@example.com"
- application.insert()
-
- # submit leave application by Leave Approver
- frappe.set_user("test1@example.com")
- application.workflow_state = "Approved"
- del application._has_access_to
- application.submit()
- self.assertEqual(frappe.db.get_value("Leave Application", application.name,
- "docstatus"), 1)
-
- def _test_leave_approval_invalid_leave_approver_insert(self):
- from erpnext.hr.doctype.leave_application.leave_application import InvalidLeaveApproverError
-
- self._clear_applications()
-
- # add a different leave approver in the employee's list
- # should raise exception if not a valid leave approver
- self._add_employee_leave_approver("_T-Employee-0001", "test2@example.com")
- self._remove_employee_leave_approver("_T-Employee-0001", "test1@example.com")
-
- make_allocation_record()
-
- application = self.get_application(_test_records[0])
- frappe.set_user("test@example.com")
-
- application.leave_approver = "test1@example.com"
- self.assertRaises(InvalidLeaveApproverError, application.insert)
-
- frappe.db.sql("""delete from `tabEmployee Leave Approver` where parent=%s""",
- "_T-Employee-0001")
-
- def _test_leave_approval_invalid_leave_approver_submit(self):
- self._clear_applications()
- self._add_employee_leave_approver("_T-Employee-0001", "test2@example.com")
-
- # create leave application as employee
- # but submit as invalid leave approver - should raise exception
- frappe.set_user("test@example.com")
-
- make_allocation_record()
-
- application = self.get_application(_test_records[0])
- application.leave_approver = "test2@example.com"
- application.insert()
- frappe.set_user("test1@example.com")
- del application._has_access_to
- application.workflow_state = "Approved"
-
- from erpnext.hr.doctype.leave_application.leave_application import LeaveApproverIdentityError
- self.assertRaises(LeaveApproverIdentityError, application.submit)
-
- frappe.db.sql("""delete from `tabEmployee Leave Approver` where parent=%s""",
- "_T-Employee-0001")
-
- def _test_leave_approval_valid_leave_approver_insert(self):
- self._clear_applications()
- self._add_employee_leave_approver("_T-Employee-0001", "test2@example.com")
-
- original_department = frappe.db.get_value("Employee", "_T-Employee-0001", "department")
- frappe.db.set_value("Employee", "_T-Employee-0001", "department", None)
-
- frappe.set_user("test@example.com")
-
- make_allocation_record()
-
- application = self.get_application(_test_records[0])
- application.leave_approver = "test2@example.com"
- application.insert()
-
- # change to valid leave approver and try to submit leave application
- frappe.set_user("test2@example.com")
- application.workflow_state = "Approved"
- del application._has_access_to
- application.submit()
- self.assertEqual(frappe.db.get_value("Leave Application", application.name,
- "docstatus"), 1)
-
- frappe.db.sql("""delete from `tabEmployee Leave Approver` where parent=%s""",
- "_T-Employee-0001")
-
- frappe.db.set_value("Employee", "_T-Employee-0001", "department", original_department)
-
def make_allocation_record(employee=None, leave_type=None):
frappe.db.sql("delete from `tabLeave Allocation`")
@@ -388,4 +267,4 @@
})
allocation.insert(ignore_permissions=True)
- allocation.submit()
+ allocation.submit()
\ No newline at end of file
diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.py b/erpnext/hr/doctype/salary_slip/salary_slip.py
index 878c6b4..da2598f 100644
--- a/erpnext/hr/doctype/salary_slip/salary_slip.py
+++ b/erpnext/hr/doctype/salary_slip/salary_slip.py
@@ -300,7 +300,6 @@
where t2.name = t1.leave_type
and t2.is_lwp = 1
and t1.docstatus = 1
- and t1.workflow_state = 'Approved'
and t1.employee = %(employee)s
and CASE WHEN t2.include_holiday != 1 THEN %(dt)s not in ('{0}') and %(dt)s between from_date and to_date
WHEN t2.include_holiday THEN %(dt)s between from_date and to_date
diff --git a/erpnext/modules.txt b/erpnext/modules.txt
index 42f0f0b..d469145 100644
--- a/erpnext/modules.txt
+++ b/erpnext/modules.txt
@@ -20,4 +20,5 @@
Restaurant
Agriculture
ERPNext Integrations
-Non Profit
\ No newline at end of file
+Non Profit
+Hotels
\ No newline at end of file
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 6f4c63e..69d8a47 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -489,7 +489,8 @@
erpnext.patches.v10_0.update_assessment_plan
erpnext.patches.v10_0.update_assessment_result
erpnext.patches.v10_0.added_extra_gst_custom_field
-erpnext.patches.v10_0.workflow_leave_application #2018-01-24
+erpnext.patches.v10_0.workflow_leave_application #2018-01-24 #2018-02-02 #2018-02-08
erpnext.patches.v10_0.set_default_payment_terms_based_on_company
erpnext.patches.v10_0.update_sales_order_link_to_purchase_order
erpnext.patches.v10_0.added_extra_gst_custom_field_in_gstr2
+erpnext.patches.v10_0.item_barcode_childtable_migrate
diff --git a/erpnext/patches/v10_0/item_barcode_childtable_migrate.py b/erpnext/patches/v10_0/item_barcode_childtable_migrate.py
new file mode 100644
index 0000000..d985bbf
--- /dev/null
+++ b/erpnext/patches/v10_0/item_barcode_childtable_migrate.py
@@ -0,0 +1,22 @@
+# Copyright (c) 2017, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+
+import frappe
+
+
+def execute():
+ items_barcode = frappe.db.sql("""SELECT name, barcode FROM tabItem
+ WHERE barcode IS NOT NULL and barcode != ''""", as_dict=1)
+
+ frappe.reload_doc("stock", "doctype", "item")
+ frappe.reload_doc("stock", "doctype", "item_barcode")
+
+ for item in items_barcode:
+ doc = frappe.get_doc("Item", item.get("name"))
+ if item.get("barcode"):
+ doc.append("barcodes", {"barcode": item.get("barcode")})
+ doc.flags.ignore_validate = True
+ doc.flags.ignore_mandatory = True
+ doc.save()
\ No newline at end of file
diff --git a/erpnext/patches/v10_0/workflow_leave_application.py b/erpnext/patches/v10_0/workflow_leave_application.py
index 8a68f89..0d33402 100644
--- a/erpnext/patches/v10_0/workflow_leave_application.py
+++ b/erpnext/patches/v10_0/workflow_leave_application.py
@@ -3,11 +3,66 @@
from __future__ import unicode_literals
import frappe
-from erpnext.setup.install import leave_application_workflow
def execute():
- frappe.reload_doc("hr", "doctype", "leave_application")
- frappe.reload_doc("workflow", "doctype", "workflow")
- leave_application_workflow()
- if frappe.db.has_column("Leave Application", "status"):
- frappe.db.sql("""update `tabLeave Application` set workflow_state = status""")
+ if frappe.db.a_row_exists("Leave Application"):
+ frappe.reload_doc("hr", "doctype", "leave_application")
+ frappe.reload_doc("workflow", "doctype", "workflow")
+ states = {'Approved': 'Success', 'Rejected': 'Danger', 'Open': 'Warning'}
+
+ for state, style in states.items():
+ if not frappe.db.exists("Workflow State", state):
+ frappe.get_doc({
+ 'doctype': 'Workflow State',
+ 'workflow_state_name': state,
+ 'style': style
+ }).insert(ignore_permissions=True)
+
+ for action in ['Approve', 'Reject']:
+ if not frappe.db.exists("Workflow Action", action):
+ frappe.get_doc({
+ 'doctype': 'Workflow Action',
+ 'workflow_action_name': action
+ }).insert(ignore_permissions=True)
+
+ if not frappe.db.exists("Workflow", "Leave Approval"):
+ frappe.get_doc({
+ 'doctype': 'Workflow',
+ 'workflow_name': 'Leave Approval',
+ 'document_type': 'Leave Application',
+ 'is_active': 1,
+ 'workflow_state_field': 'workflow_state',
+ 'states': [{
+ "state": 'Open',
+ "doc_status": 0,
+ "allow_edit": 'Employee'
+ }, {
+ "state": 'Approved',
+ "doc_status": 1,
+ "allow_edit": 'Leave Approver'
+ }, {
+ "state": 'Rejected',
+ "doc_status": 0,
+ "allow_edit": 'Leave Approver'
+ }],
+ 'transitions': [{
+ "state": 'Open',
+ "action": 'Approve',
+ "next_state": 'Approved',
+ "allowed": 'Leave Approver'
+ },
+ {
+ "state": 'Open',
+ "action": 'Reject',
+ "next_state": 'Rejected',
+ "allowed": 'Leave Approver'
+ }]
+ }).insert(ignore_permissions=True)
+
+ if frappe.db.has_column("Leave Application", "status"):
+ frappe.db.sql("""update `tabLeave Application` set workflow_state = status""")
+
+ if frappe.db.has_column("Leave Application", "workflow_state"):
+ frappe.db.sql("""update `tabWorkflow Document State` set doc_status = 0 where parent = "Leave Approval" \
+ and state = "Rejected" and doc_status = 1""")
+ frappe.db.sql("""update `tabLeave Application` set docstatus = 0 where workflow_state = "Rejected" and docstatus = 1""")
diff --git a/erpnext/public/js/financial_statements.js b/erpnext/public/js/financial_statements.js
index 1eeb896..29e3999 100644
--- a/erpnext/public/js/financial_statements.js
+++ b/erpnext/public/js/financial_statements.js
@@ -98,6 +98,16 @@
],
"default": "Monthly",
"reqd": 1
+ },
+ // Note:
+ // If you are modifying this array such that the presentation_currency object
+ // is no longer the last object, please make adjustments in cash_flow.js
+ // accordingly.
+ {
+ "fieldname": "presentation_currency",
+ "label": __("Currency"),
+ "fieldtype": "Select",
+ "options": erpnext.get_presentation_currency_list()
}
]
}
diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js
index 6841354..d50fa19 100644
--- a/erpnext/public/js/utils.js
+++ b/erpnext/public/js/utils.js
@@ -13,6 +13,12 @@
return frappe.boot.sysdefaults.currency;
},
+ get_presentation_currency_list: () => {
+ const docs = frappe.boot.docs;
+ const currency_list = docs.filter(d => d.doctype === ":Currency").map(d => d.name);
+ return currency_list;
+ },
+
toggle_naming_series: function() {
if(cur_frm.fields_dict.naming_series) {
cur_frm.toggle_display("naming_series", cur_frm.doc.__islocal?true:false);
diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js
index a4d7265..0fb8730 100644
--- a/erpnext/public/js/utils/serial_no_batch_selector.js
+++ b/erpnext/public/js/utils/serial_no_batch_selector.js
@@ -215,7 +215,7 @@
in_list_view:1,
get_query: function() {
return {
- filters: {item_code: me.item_code },
+ filters: {item: me.item_code },
query: 'erpnext.controllers.queries.get_batch_numbers'
};
},
diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py
index fa0a448..c11337a 100644
--- a/erpnext/regional/india/setup.py
+++ b/erpnext/regional/india/setup.py
@@ -107,7 +107,7 @@
dict(fieldname='reason_for_issuing_document', label='Reason For Issuing document',
fieldtype='Select', insert_after='gst_col_break', print_hide=1,
depends_on='eval:doc.is_return==1', reqd=1,
- options='\n01-Sales Return\n02-Post Sale Discount\n03-Deficiency in services\n04-Correction in Invoice\n05-Change in POS\n06-Finalization of Provisional assessment\n07-Others', default='01-Sales Return')
+ options='\n01-Sales Return\n02-Post Sale Discount\n03-Deficiency in services\n04-Correction in Invoice\n05-Change in POS\n06-Finalization of Provisional assessment\n07-Others')
]
purchase_invoice_gst_fields = [
diff --git a/erpnext/restaurant/doctype/restaurant/restaurant.json b/erpnext/restaurant/doctype/restaurant/restaurant.json
index f4ecba7..8572687 100644
--- a/erpnext/restaurant/doctype/restaurant/restaurant.json
+++ b/erpnext/restaurant/doctype/restaurant/restaurant.json
@@ -4,7 +4,7 @@
"allow_import": 0,
"allow_rename": 0,
"autoname": "prompt",
- "beta": 0,
+ "beta": 1,
"creation": "2017-09-15 12:40:41.546933",
"custom": 0,
"docstatus": 0,
@@ -269,7 +269,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2017-10-05 17:41:14.422242",
+ "modified": "2017-12-09 12:13:10.185496",
"modified_by": "Administrator",
"module": "Restaurant",
"name": "Restaurant",
diff --git a/erpnext/restaurant/doctype/restaurant_menu/restaurant_menu.json b/erpnext/restaurant/doctype/restaurant_menu/restaurant_menu.json
index 264634b..1b1610d 100644
--- a/erpnext/restaurant/doctype/restaurant_menu/restaurant_menu.json
+++ b/erpnext/restaurant/doctype/restaurant_menu/restaurant_menu.json
@@ -4,7 +4,7 @@
"allow_import": 0,
"allow_rename": 0,
"autoname": "prompt",
- "beta": 0,
+ "beta": 1,
"creation": "2017-09-15 12:48:29.818715",
"custom": 0,
"docstatus": 0,
@@ -207,7 +207,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2017-09-21 11:04:20.671542",
+ "modified": "2017-12-09 12:13:13.684500",
"modified_by": "Administrator",
"module": "Restaurant",
"name": "Restaurant Menu",
diff --git a/erpnext/restaurant/doctype/restaurant_reservation/restaurant_reservation.json b/erpnext/restaurant/doctype/restaurant_reservation/restaurant_reservation.json
index 6a2ffa1..0698758 100644
--- a/erpnext/restaurant/doctype/restaurant_reservation/restaurant_reservation.json
+++ b/erpnext/restaurant/doctype/restaurant_reservation/restaurant_reservation.json
@@ -4,7 +4,7 @@
"allow_import": 0,
"allow_rename": 0,
"autoname": "REST.######",
- "beta": 0,
+ "beta": 1,
"creation": "2017-09-15 13:05:51.063661",
"custom": 0,
"docstatus": 0,
@@ -297,7 +297,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2017-09-15 14:40:56.759315",
+ "modified": "2017-12-09 12:13:20.027942",
"modified_by": "Administrator",
"module": "Restaurant",
"name": "Restaurant Reservation",
diff --git a/erpnext/restaurant/doctype/restaurant_table/restaurant_table.json b/erpnext/restaurant/doctype/restaurant_table/restaurant_table.json
index da1bcde..5fc6e62 100644
--- a/erpnext/restaurant/doctype/restaurant_table/restaurant_table.json
+++ b/erpnext/restaurant/doctype/restaurant_table/restaurant_table.json
@@ -4,7 +4,7 @@
"allow_import": 0,
"allow_rename": 0,
"autoname": "",
- "beta": 0,
+ "beta": 1,
"creation": "2017-09-15 12:45:24.717355",
"custom": 0,
"docstatus": 0,
@@ -116,7 +116,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2017-09-15 13:18:05.254106",
+ "modified": "2017-12-09 12:13:24.382345",
"modified_by": "Administrator",
"module": "Restaurant",
"name": "Restaurant Table",
diff --git a/erpnext/selling/doctype/customer/customer.js b/erpnext/selling/doctype/customer/customer.js
index 92734cc..ed8e6c8 100644
--- a/erpnext/selling/doctype/customer/customer.js
+++ b/erpnext/selling/doctype/customer/customer.js
@@ -33,7 +33,38 @@
}
}
})
+ frm.set_query('customer_primary_address', function(doc) {
+ return {
+ query: "erpnext.selling.doctype.customer.customer.get_customer_primary_address",
+ filters: {
+ 'customer': doc.name
+ }
+ }
+ })
},
+ customer_primary_address: function(frm){
+ if(frm.doc.customer_primary_address){
+ frappe.call({
+ method: 'frappe.contacts.doctype.address.address.get_address_display',
+ args: {
+ "address_dict": frm.doc.customer_primary_address
+ },
+ callback: function(r) {
+ frm.set_value("primary_address", r.message);
+ }
+ });
+ }
+ if(!frm.doc.customer_primary_address){
+ frm.set_value("primary_address", "");
+ }
+ },
+ customer_primary_contact: function(frm){
+ if(!frm.doc.customer_primary_contact){
+ frm.set_value("mobile_no", "");
+ frm.set_value("email_id", "");
+ }
+ },
+
refresh: function(frm) {
if(frappe.defaults.get_default("cust_master_name")!="Naming Series") {
frm.toggle_display("naming_series", false);
@@ -43,7 +74,7 @@
frappe.dynamic_link = {doc: frm.doc, fieldname: 'name', doctype: 'Customer'}
- frm.toggle_display(['address_html','contact_html','primary_contact_detail'], !frm.doc.__islocal);
+ frm.toggle_display(['address_html','contact_html','primary_address_and_contact_detail'], !frm.doc.__islocal);
if(!frm.doc.__islocal) {
frappe.contacts.render_address_and_contact(frm);
@@ -72,4 +103,4 @@
validate: function(frm) {
if(frm.doc.lead_name) frappe.model.clear_doc("Lead", frm.doc.lead_name);
},
-});
+});
\ No newline at end of file
diff --git a/erpnext/selling/doctype/customer/customer.json b/erpnext/selling/doctype/customer/customer.json
index b2577d5..d43de10 100644
--- a/erpnext/selling/doctype/customer/customer.json
+++ b/erpnext/selling/doctype/customer/customer.json
@@ -725,7 +725,8 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
- "fieldname": "primary_contact_detail",
+ "description": "Select, to make the customer searchable with these fields",
+ "fieldname": "primary_address_and_contact_detail",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
@@ -734,7 +735,7 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
- "label": "Primary Contact Detail",
+ "label": "Primary Address and Contact Detail",
"length": 0,
"no_copy": 0,
"permlevel": 0,
@@ -755,6 +756,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "description": "Reselect, if the chosen contact is edited after save",
"fieldname": "customer_primary_contact",
"fieldtype": "Link",
"hidden": 0,
@@ -786,6 +788,68 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fieldname": "mobile_no",
+ "fieldtype": "Read Only",
+ "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": "Mobile No",
+ "length": 0,
+ "no_copy": 0,
+ "options": "customer_primary_contact.mobile_no",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "email_id",
+ "fieldtype": "Read Only",
+ "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": "Email Id",
+ "length": 0,
+ "no_copy": 0,
+ "options": "customer_primary_contact.email_id",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
"fieldname": "column_break_26",
"fieldtype": "Column Break",
"hidden": 0,
@@ -815,8 +879,9 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
- "fieldname": "mobile_no",
- "fieldtype": "Read Only",
+ "description": "Reselect, if the chosen address is edited after save",
+ "fieldname": "customer_primary_address",
+ "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@@ -824,10 +889,10 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
- "label": "Mobile Number",
+ "label": "Customer Primary Address",
"length": 0,
"no_copy": 0,
- "options": "customer_primary_contact.mobile_no",
+ "options": "Address",
"permlevel": 0,
"precision": "",
"print_hide": 0,
@@ -846,7 +911,8 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
- "fieldname": "email_id",
+ "default": "",
+ "fieldname": "primary_address",
"fieldtype": "Read Only",
"hidden": 0,
"ignore_user_permissions": 0,
@@ -855,10 +921,10 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
- "label": "Email Id",
+ "label": "Primary Address",
"length": 0,
"no_copy": 0,
- "options": "customer_primary_contact.email_id",
+ "options": "",
"permlevel": 0,
"precision": "",
"print_hide": 0,
@@ -1383,7 +1449,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2018-01-30 11:52:05.497363",
+ "modified": "2018-02-09 10:44:35.801716",
"modified_by": "Administrator",
"module": "Selling",
"name": "Customer",
@@ -1574,7 +1640,7 @@
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
- "search_fields": "customer_name,customer_group,territory, mobile_no",
+ "search_fields": "customer_name,customer_group,territory, mobile_no,primary_address",
"show_name_in_global_search": 1,
"sort_order": "ASC",
"title_field": "customer_name",
diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py
index 2284f85..6f25bf7 100644
--- a/erpnext/selling/doctype/customer/customer.py
+++ b/erpnext/selling/doctype/customer/customer.py
@@ -309,3 +309,15 @@
'customer': customer,
'txt': '%%%s%%' % txt
})
+
+def get_customer_primary_address(doctype, txt, searchfield, start, page_len, filters):
+ customer = frappe.db.escape(filters.get('customer'))
+ return frappe.db.sql("""
+ select `tabAddress`.name from `tabAddress`, `tabDynamic Link`
+ where `tabAddress`.name = `tabDynamic Link`.parent and `tabDynamic Link`.link_name = %(customer)s
+ and `tabDynamic Link`.link_doctype = 'Customer' and `tabAddress`.is_primary_address = 1
+ and `tabAddress`.name like %(txt)s
+ """, {
+ 'customer': customer,
+ 'txt': '%%%s%%' % txt
+ })
\ No newline at end of file
diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.py b/erpnext/selling/page/point_of_sale/point_of_sale.py
index 4f0d250..666284f 100644
--- a/erpnext/selling/page/point_of_sale/point_of_sale.py
+++ b/erpnext/selling/page/point_of_sale/point_of_sale.py
@@ -29,7 +29,7 @@
batch_no, item_code = batch_no_data
if not serial_no and not batch_no:
- barcode_data = frappe.db.get_value('Item', {'barcode': search_value}, ['name', 'barcode'])
+ barcode_data = frappe.db.get_value('Item Barcode', {'barcode': search_value}, ['parent', 'barcode'])
if barcode_data:
item_code, barcode = barcode_data
diff --git a/erpnext/setup/install.py b/erpnext/setup/install.py
index 2cc280f..81f909a 100644
--- a/erpnext/setup/install.py
+++ b/erpnext/setup/install.py
@@ -12,7 +12,6 @@
<a style="color: #888" href="http://erpnext.org">ERPNext</a></div>"""
def after_install():
- leave_application_workflow()
frappe.get_doc({'doctype': "Role", "role_name": "Analytics"}).insert()
set_single_defaults()
create_compact_item_print_custom_field()
@@ -20,58 +19,6 @@
add_all_roles_to("Administrator")
frappe.db.commit()
-def leave_application_workflow():
- states = {'Approved': 'Success', 'Rejected': 'Danger', 'Open': 'Warning'}
-
- for state, style in states.items():
- if not frappe.db.exists("Workflow State", state):
- frappe.get_doc({
- 'doctype': 'Workflow State',
- 'workflow_state_name': state,
- 'style': style
- }).insert(ignore_permissions=True)
-
- for action in ['Approve', 'Reject']:
- if not frappe.db.exists("Workflow Action", action):
- frappe.get_doc({
- 'doctype': 'Workflow Action',
- 'workflow_action_name': action
- }).insert(ignore_permissions=True)
-
- if not frappe.db.exists("Workflow", "Leave Approval"):
- frappe.get_doc({
- 'doctype': 'Workflow',
- 'workflow_name': 'Leave Approval',
- 'document_type': 'Leave Application',
- 'is_active': 1,
- 'workflow_state_field': 'workflow_state',
- 'states': [{
- "state": 'Open',
- "doc_status": 0,
- "allow_edit": 'Employee'
- }, {
- "state": 'Approved',
- "doc_status": 1,
- "allow_edit": 'Leave Approver'
- }, {
- "state": 'Rejected',
- "doc_status": 1,
- "allow_edit": 'Leave Approver'
- }],
- 'transitions': [{
- "state": 'Open',
- "action": 'Approve',
- "next_state": 'Approved',
- "allowed": 'Leave Approver'
- },
- {
- "state": 'Open',
- "action": 'Reject',
- "next_state": 'Rejected',
- "allowed": 'Leave Approver'
- }]
- }).insert(ignore_permissions=True)
-
def check_setup_wizard_not_completed():
if frappe.db.get_default('desktop:home_page') == 'desktop':
print()
diff --git a/erpnext/stock/doctype/batch/batch.js b/erpnext/stock/doctype/batch/batch.js
index 12aa0bd..e2ea7f9 100644
--- a/erpnext/stock/doctype/batch/batch.js
+++ b/erpnext/stock/doctype/batch/batch.js
@@ -25,9 +25,17 @@
}
},
item: (frm) => {
- frappe.db.get_value('Item', {name: frm.doc.item}, 'has_expiry_date', (r) => {
- frm.toggle_reqd('expiry_date', r.has_expiry_date);
- });
+ // frappe.db.get_value('Item', {name: frm.doc.item}, 'has_expiry_date', (r) => {
+ // frm.toggle_reqd('expiry_date', r.has_expiry_date);
+ // });
+ frappe.db.get_value('Item', {name: frm.doc.item}, ['shelf_life_in_days', 'has_expiry_date'], (r) => {
+ if (r.has_expiry_date && r.shelf_life_in_days) {
+ // Calculate expiry date based on shelf_life_in_days
+ frm.set_value('expiry_date', frappe.datetime.add_days(frm.doc.manufacturing_date, r.shelf_life_in_days));
+ }else if(r.has_expiry_date){
+ frm.toggle_reqd('expiry_date', r.has_expiry_date);
+ }
+ })
},
make_dashboard: (frm) => {
if(!frm.is_new()) {
diff --git a/erpnext/stock/doctype/batch/batch.json b/erpnext/stock/doctype/batch/batch.json
index f60798b..cc1aac5 100644
--- a/erpnext/stock/doctype/batch/batch.json
+++ b/erpnext/stock/doctype/batch/batch.json
@@ -1,493 +1,494 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 1,
- "allow_rename": 0,
- "autoname": "",
- "beta": 0,
- "creation": "2013-03-05 14:50:38",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Setup",
- "editable_grid": 0,
- "engine": "InnoDB",
+ "allow_copy": 0,
+ "allow_guest_to_view": 0,
+ "allow_import": 1,
+ "allow_rename": 0,
+ "autoname": "",
+ "beta": 0,
+ "creation": "2013-03-05 14:50:38",
+ "custom": 0,
+ "docstatus": 0,
+ "doctype": "DocType",
+ "document_type": "Setup",
+ "editable_grid": 0,
+ "engine": "InnoDB",
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "eval:doc.__islocal",
- "fieldname": "batch_id",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Batch ID",
- "length": 0,
- "no_copy": 1,
- "oldfieldname": "batch_id",
- "oldfieldtype": "Data",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "depends_on": "eval:doc.__islocal",
+ "fieldname": "batch_id",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "Batch ID",
+ "length": 0,
+ "no_copy": 1,
+ "oldfieldname": "batch_id",
+ "oldfieldtype": "Data",
+ "permlevel": 0,
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
+ "set_only_once": 0,
"unique": 0
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "item",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 1,
- "label": "Item",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "item",
- "oldfieldtype": "Link",
- "options": "Item",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "item",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "Item",
+ "length": 0,
+ "no_copy": 0,
+ "oldfieldname": "item",
+ "oldfieldtype": "Link",
+ "options": "Item",
+ "permlevel": 0,
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
+ "set_only_once": 0,
"unique": 0
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "image",
- "fieldtype": "Attach Image",
- "hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "image",
- "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,
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "image",
+ "fieldtype": "Attach Image",
+ "hidden": 1,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "image",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
"unique": 0
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "eval:doc.parent_batch",
- "fieldname": "parent_batch",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Parent Batch",
- "length": 0,
- "no_copy": 0,
- "options": "Batch",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "depends_on": "eval:doc.parent_batch",
+ "fieldname": "parent_batch",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Parent Batch",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Batch",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
"unique": 0
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_3",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "column_break_3",
+ "fieldtype": "Column Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
"unique": 0
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "manufacturing_date",
- "fieldtype": "Date",
- "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": "Manufacturing Date",
- "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,
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "default": "Today",
+ "fieldname": "manufacturing_date",
+ "fieldtype": "Date",
+ "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": "Manufacturing Date",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
"unique": 0
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "expiry_date",
- "fieldtype": "Date",
- "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": "Expiry Date",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "expiry_date",
- "oldfieldtype": "Date",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "expiry_date",
+ "fieldtype": "Date",
+ "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": "Expiry Date",
+ "length": 0,
+ "no_copy": 0,
+ "oldfieldname": "expiry_date",
+ "oldfieldtype": "Date",
+ "permlevel": 0,
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
"unique": 0
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "source",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Source",
- "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,
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "source",
+ "fieldtype": "Section Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Source",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
"unique": 0
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "supplier",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Supplier",
- "length": 0,
- "no_copy": 0,
- "options": "Supplier",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "supplier",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Supplier",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Supplier",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
"unique": 0
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_9",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "column_break_9",
+ "fieldtype": "Column Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
"unique": 0
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "reference_doctype",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Source Document Type",
- "length": 0,
- "no_copy": 0,
- "options": "DocType",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "reference_doctype",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Source Document Type",
+ "length": 0,
+ "no_copy": 0,
+ "options": "DocType",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
"unique": 0
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "reference_name",
- "fieldtype": "Dynamic Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Source Document Name",
- "length": 0,
- "no_copy": 0,
- "options": "reference_doctype",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "reference_name",
+ "fieldtype": "Dynamic Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Source Document Name",
+ "length": 0,
+ "no_copy": 0,
+ "options": "reference_doctype",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
"unique": 0
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "section_break_7",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "section_break_7",
+ "fieldtype": "Section Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
"unique": 0
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "description",
- "fieldtype": "Small Text",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Batch Description",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "description",
- "oldfieldtype": "Small Text",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0,
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "description",
+ "fieldtype": "Small Text",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Batch Description",
+ "length": 0,
+ "no_copy": 0,
+ "oldfieldname": "description",
+ "oldfieldtype": "Small Text",
+ "permlevel": 0,
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0,
"width": "300px"
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "icon": "fa fa-archive",
- "idx": 1,
- "image_field": "image",
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 5,
- "modified": "2018-01-08 21:55:54.306693",
- "modified_by": "Administrator",
- "module": "Stock",
- "name": "Batch",
- "owner": "harshada@webnotestech.com",
+ ],
+ "has_web_view": 0,
+ "hide_heading": 0,
+ "hide_toolbar": 0,
+ "icon": "fa fa-archive",
+ "idx": 1,
+ "image_field": "image",
+ "image_view": 0,
+ "in_create": 0,
+ "is_submittable": 0,
+ "issingle": 0,
+ "istable": 0,
+ "max_attachments": 5,
+ "modified": "2018-01-23 17:41:06.862477",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Batch",
+ "owner": "harshada@webnotestech.com",
"permissions": [
{
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Item Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "amend": 0,
+ "apply_user_permissions": 0,
+ "cancel": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 0,
+ "if_owner": 0,
+ "import": 0,
+ "permlevel": 0,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Item Manager",
+ "set_user_permissions": 0,
+ "share": 1,
+ "submit": 0,
"write": 1
}
- ],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_order": "DESC",
- "title_field": "item",
- "track_changes": 0,
+ ],
+ "quick_entry": 1,
+ "read_only": 0,
+ "read_only_onload": 0,
+ "show_name_in_global_search": 0,
+ "sort_order": "DESC",
+ "title_field": "item",
+ "track_changes": 0,
"track_seen": 0
-}
\ No newline at end of file
+}
diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py
index b5674d3..645ab1b 100644
--- a/erpnext/stock/doctype/batch/batch.py
+++ b/erpnext/stock/doctype/batch/batch.py
@@ -7,24 +7,13 @@
from frappe.model.document import Document
from frappe.model.naming import make_autoname, revert_series_if_last
from frappe.utils import flt, cint
-
+from frappe.utils.jinja import render_template
+from frappe.utils.data import add_days
class UnableToSelectBatchError(frappe.ValidationError):
pass
-def get_name_from_naming_series():
- """
- Get a name generated for a Batch from the Batch's naming series.
- :return: The string that was generated.
- """
- naming_series_prefix = _get_batch_prefix()
- key = _make_naming_series_key(naming_series_prefix)
- name = make_autoname(key)
-
- return name
-
-
def get_name_from_hash():
"""
Get a name for a Batch by generating a unique hash.
@@ -99,7 +88,7 @@
if not self.batch_id:
if frappe.db.get_value('Item', self.item, 'create_new_batch'):
if batch_uses_naming_series():
- self.batch_id = get_name_from_naming_series()
+ self.batch_id = self.get_name_from_naming_series()
else:
self.batch_id = get_name_from_hash()
else:
@@ -120,6 +109,28 @@
if frappe.db.get_value("Item", self.item, "has_batch_no") == 0:
frappe.throw(_("The selected item cannot have Batch"))
+ def before_save(self):
+ has_expiry_date, shelf_life_in_days = frappe.db.get_value('Item', self.item, ['has_expiry_date', 'shelf_life_in_days'])
+ if not self.expiry_date and has_expiry_date and shelf_life_in_days:
+ self.expiry_date = add_days(self.manufacturing_date, shelf_life_in_days)
+
+ if has_expiry_date and not self.expiry_date:
+ frappe.throw('Expiry date is mandatory for selected item')
+ frappe.msgprint('Set items shelf life in days, to set expiry based on manufacturing_date plus self life ')
+
+ def get_name_from_naming_series(self):
+ """
+ Get a name generated for a Batch from the Batch's naming series.
+ :return: The string that was generated.
+ """
+ naming_series_prefix = _get_batch_prefix()
+ # validate_template(naming_series_prefix)
+ naming_series_prefix = render_template(str(naming_series_prefix), self.__dict__)
+ key = _make_naming_series_key(naming_series_prefix)
+ name = make_autoname(key)
+
+ return name
+
@frappe.whitelist()
def get_batch_qty(batch_no=None, warehouse=None, item_code=None):
@@ -206,6 +217,7 @@
frappe.throw(_("Row #{0}: The batch {1} has only {2} qty. Please select another batch which has {3} qty available or split the row into multiple rows, to deliver/issue from multiple batches").format(d.idx, d.batch_no, batch_qty, qty))
+
@frappe.whitelist()
def get_batch_no(item_code, warehouse, qty=1, throw=False):
"""
diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json
index 2b87069..f334973 100644
--- a/erpnext/stock/doctype/item/item.json
+++ b/erpnext/stock/doctype/item/item.json
@@ -178,35 +178,6 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
- "fieldname": "barcode",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Barcode",
- "length": 0,
- "no_copy": 1,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"description": "",
"fieldname": "item_group",
"fieldtype": "Link",
@@ -702,6 +673,67 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "sb_barcodes",
+ "fieldtype": "Section Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Barcodes",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "barcodes",
+ "fieldtype": "Table",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Barcodes",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Item Barcode",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
@@ -3484,7 +3516,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 1,
- "modified": "2018-01-24 20:42:23.303090",
+ "modified": "2018-02-12 15:42:23.303090",
"modified_by": "Administrator",
"module": "Stock",
"name": "Item",
diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py
index df90ad9..27f2c7b 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -2,30 +2,38 @@
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
-import frappe
-import erpnext
-import json
-import itertools
-from frappe import msgprint, _
-from frappe.utils import (cstr, flt, cint, getdate, now_datetime, formatdate,
- strip, get_timestamp, random_string)
-from frappe.utils.html_utils import clean_html
-from frappe.website.website_generator import WebsiteGenerator
-from erpnext.setup.doctype.item_group.item_group import invalidate_cache_for, get_parent_item_groups
-from frappe.website.render import clear_cache
-from frappe.website.doctype.website_slideshow.website_slideshow import get_slideshow
-from erpnext.controllers.item_variant import (get_variant, copy_attributes_to_variant,
- make_variant_item_code, validate_item_variant_attributes, ItemVariantExistsError)
-class DuplicateReorderRows(frappe.ValidationError): pass
-class StockExistsForTemplate(frappe.ValidationError): pass
+import itertools
+import json
+import erpnext
+import frappe
+from erpnext.controllers.item_variant import (ItemVariantExistsError,
+ copy_attributes_to_variant, get_variant, make_variant_item_code, validate_item_variant_attributes)
+from erpnext.setup.doctype.item_group.item_group import (get_parent_item_groups, invalidate_cache_for)
+from frappe import _, msgprint
+from frappe.utils import (cint, cstr, flt, formatdate, get_timestamp, getdate,
+ now_datetime, random_string, strip)
+from frappe.utils.html_utils import clean_html
+from frappe.website.doctype.website_slideshow.website_slideshow import \
+ get_slideshow
+from frappe.website.render import clear_cache
+from frappe.website.website_generator import WebsiteGenerator
+
+
+class DuplicateReorderRows(frappe.ValidationError):
+ pass
+
+
+class StockExistsForTemplate(frappe.ValidationError):
+ pass
+
class Item(WebsiteGenerator):
website = frappe._dict(
- page_title_field = "item_name",
- condition_field = "show_in_website",
- template = "templates/generators/item.html",
- no_cache = 1
+ page_title_field="item_name",
+ condition_field="show_in_website",
+ template="templates/generators/item.html",
+ no_cache=1
)
def onload(self):
@@ -37,14 +45,14 @@
self.set_onload("asset_exists", True if asset else False)
def autoname(self):
- if frappe.db.get_default("item_naming_by")=="Naming Series":
+ if frappe.db.get_default("item_naming_by") == "Naming Series":
if self.variant_of:
if not self.item_code:
template_item_name = frappe.db.get_value("Item", self.variant_of, "item_name")
self.item_code = make_variant_item_code(self.variant_of, template_item_name, self)
else:
from frappe.model.naming import make_autoname
- self.item_code = make_autoname(self.naming_series+'.#####')
+ self.item_code = make_autoname(self.naming_series + '.#####')
elif not self.item_code:
msgprint(_("Item Code is mandatory because Item is not automatically numbered"), raise_exception=1)
@@ -102,8 +110,8 @@
if not self.get("__islocal"):
self.old_item_group = frappe.db.get_value(self.doctype, self.name, "item_group")
self.old_website_item_groups = frappe.db.sql_list("""select item_group
- from `tabWebsite Item Group`
- where parentfield='website_item_groups' and parenttype='Item' and parent=%s""", self.name)
+ from `tabWebsite Item Group`
+ where parentfield='website_item_groups' and parenttype='Item' and parent=%s""", self.name)
def on_update(self):
invalidate_cache_for_item(self)
@@ -121,7 +129,7 @@
'''Add a new price'''
if not price_list:
price_list = (frappe.db.get_single_value('Selling Settings', 'selling_price_list')
- or frappe.db.get_value('Price List', _('Standard Selling')))
+ or frappe.db.get_value('Price List', _('Standard Selling')))
if price_list:
item_price = frappe.get_doc({
"doctype": "Item Price",
@@ -147,19 +155,19 @@
# default warehouse, or Stores
default_warehouse = (self.default_warehouse
- or frappe.db.get_single_value('Stock Settings', 'default_warehouse')
- or frappe.db.get_value('Warehouse', {'warehouse_name': _('Stores')}))
+ or frappe.db.get_single_value('Stock Settings', 'default_warehouse')
+ or frappe.db.get_value('Warehouse', {'warehouse_name': _('Stores')}))
if default_warehouse:
stock_entry = make_stock_entry(item_code=self.name, target=default_warehouse,
- qty=self.opening_stock, rate=self.valuation_rate)
+ qty=self.opening_stock, rate=self.valuation_rate)
stock_entry.add_comment("Comment", _("Opening Stock"))
def make_route(self):
if not self.route:
return cstr(frappe.db.get_value('Item Group', self.item_group,
- 'route')) + '/' + self.scrub((self.item_name if self.item_name else self.item_code) + '-' + random_string(5))
+ 'route')) + '/' + self.scrub((self.item_name if self.item_name else self.item_code) + '-' + random_string(5))
def validate_website_image(self):
"""Validate if the website image is a public file"""
@@ -176,14 +184,13 @@
"file_url": self.website_image
}, fields=["name", "is_private"], order_by="is_private asc", limit_page_length=1)
-
if file_doc:
file_doc = file_doc[0]
if not file_doc:
if not auto_set_website_image:
frappe.msgprint(_("Website Image {0} attached to Item {1} cannot be found")
- .format(self.website_image, self.name))
+ .format(self.website_image, self.name))
self.website_image = None
@@ -219,7 +226,8 @@
self.website_image = None
except requests.exceptions.SSLError:
- frappe.msgprint(_("Warning: Invalid SSL certificate on attachment {0}").format(self.website_image))
+ frappe.msgprint(
+ _("Warning: Invalid SSL certificate on attachment {0}").format(self.website_image))
self.website_image = None
# for CSV import
@@ -259,12 +267,13 @@
def validate_retain_sample(self):
if self.retain_sample and not frappe.db.get_single_value('Stock Settings', 'sample_retention_warehouse'):
- frappe.throw(_("Please select Sample Retention Warehouse in Stock Settings first"));
+ frappe.throw(_("Please select Sample Retention Warehouse in Stock Settings first"))
if self.retain_sample and not self.has_batch_no:
- frappe.throw(_(" {0} Retain Sample is based on batch, please check Has Batch No to retain sample of item").format(self.item_code))
+ frappe.throw(_(" {0} Retain Sample is based on batch, please check Has Batch No to retain sample of item").format(
+ self.item_code))
def get_context(self, context):
- context.show_search=True
+ context.show_search = True
context.search_link = '/product_search'
context.parents = get_parent_item_groups(self.item_group)
@@ -282,8 +291,8 @@
# load variants
# also used in set_attribute_context
context.variants = frappe.get_all("Item",
- filters={"variant_of": self.name, "show_variant_in_website": 1},
- order_by="name asc")
+ filters={"variant_of": self.name, "show_variant_in_website": 1},
+ order_by="name asc")
variant = frappe.form_dict.variant
if not variant and context.variants:
@@ -294,7 +303,7 @@
context.variant = frappe.get_doc("Item", variant)
for fieldname in ("website_image", "web_long_description", "description",
- "website_specifications"):
+ "website_specifications"):
if context.variant.get(fieldname):
value = context.variant.get(fieldname)
if isinstance(value, list):
@@ -317,14 +326,14 @@
# load attributes
for v in context.variants:
v.attributes = frappe.get_all("Item Variant Attribute",
- fields=["attribute", "attribute_value"], filters={"parent": v.name})
+ fields=["attribute", "attribute_value"], filters={"parent": v.name})
for attr in v.attributes:
values = attribute_values_available.setdefault(attr.attribute, [])
if attr.attribute_value not in values:
values.append(attr.attribute_value)
- if v.name==context.variant.name:
+ if v.name == context.variant.name:
context.selected_attributes[attr.attribute] = attr.attribute_value
# filter attributes, order based on attribute table
@@ -338,7 +347,7 @@
else:
# get list of values defined (for sequence)
for attr_value in frappe.db.get_all("Item Attribute Value",
- fields=["attribute_value"], filters={"parent": attr.attribute}, order_by="idx asc"):
+ fields=["attribute_value"], filters={"parent": attr.attribute}, order_by="idx asc"):
if attr_value.attribute_value in attribute_values_available.get(attr.attribute, []):
values.append(attr_value.attribute_value)
@@ -373,7 +382,7 @@
return True
for i, attr in enumerate(self.attributes):
- if i==0:
+ if i == 0:
continue
combination_source = []
@@ -414,7 +423,7 @@
for d in template.get("reorder_levels"):
n = {}
for k in ("warehouse", "warehouse_reorder_level",
- "warehouse_reorder_qty", "material_request_type"):
+ "warehouse_reorder_qty", "material_request_type"):
n[k] = d.get(k)
self.append("reorder_levels", n)
@@ -422,12 +431,14 @@
check_list = []
for d in self.get('uoms'):
if cstr(d.uom) in check_list:
- frappe.throw(_("Unit of Measure {0} has been entered more than once in Conversion Factor Table").format(d.uom))
+ frappe.throw(
+ _("Unit of Measure {0} has been entered more than once in Conversion Factor Table").format(d.uom))
else:
check_list.append(cstr(d.uom))
if d.uom and cstr(d.uom) == cstr(self.stock_uom) and flt(d.conversion_factor) != 1:
- frappe.throw(_("Conversion factor for default Unit of Measure must be 1 in row {0}").format(d.idx))
+ frappe.throw(
+ _("Conversion factor for default Unit of Measure must be 1 in row {0}").format(d.idx))
def validate_item_type(self):
if self.has_serial_no == 1 and self.is_stock_item == 0:
@@ -436,29 +447,30 @@
if self.has_serial_no == 0 and self.serial_no_series:
self.serial_no_series = None
-
def check_for_active_boms(self):
if self.default_bom:
bom_item = frappe.db.get_value("BOM", self.default_bom, "item")
if bom_item not in (self.name, self.variant_of):
- frappe.throw(_("Default BOM ({0}) must be active for this item or its template").format(bom_item))
+ frappe.throw(
+ _("Default BOM ({0}) must be active for this item or its template").format(bom_item))
def fill_customer_code(self):
""" Append all the customer codes and insert into "customer_code" field of item table """
- cust_code=[]
+ cust_code = []
for d in self.get('customer_items'):
cust_code.append(d.ref_code)
- self.customer_code=','.join(cust_code)
+ self.customer_code = ','.join(cust_code)
def check_item_tax(self):
"""Check whether Tax Rate is not entered twice for same Tax Type"""
- check_list=[]
+ check_list = []
for d in self.get('taxes'):
if d.tax_type:
account_type = frappe.db.get_value("Account", d.tax_type, "account_type")
if account_type not in ['Tax', 'Chargeable', 'Income Account', 'Expense Account']:
- frappe.throw(_("Item Tax Row {0} must have account of type Tax or Income or Expense or Chargeable").format(d.idx))
+ frappe.throw(
+ _("Item Tax Row {0} must have account of type Tax or Income or Expense or Chargeable").format(d.idx))
else:
if d.tax_type in check_list:
frappe.throw(_("{0} entered twice in Item Tax").format(d.tax_type))
@@ -466,12 +478,20 @@
check_list.append(d.tax_type)
def validate_barcode(self):
- if self.barcode:
- duplicate = frappe.db.sql("""select name from tabItem where barcode = %s
- and name != %s""", (self.barcode, self.name))
- if duplicate:
- frappe.throw(_("Barcode {0} already used in Item {1}").format(self.barcode, duplicate[0][0]))
+ from stdnum import ean
+ if len(self.barcodes) > 0:
+ for item_barcode in self.barcodes:
+ if item_barcode.barcode:
+ duplicate = frappe.db.sql(
+ """select parent from `tabItem Barcode` where barcode = %s and parent != %s""", (item_barcode.barcode, self.name))
+ if duplicate:
+ frappe.throw(_("Barcode {0} already used in Item {1}").format(
+ item_barcode.barcode, duplicate[0][0]))
+ if item_barcode.barcode_type:
+ if not ean.is_valid(item_barcode.barcode):
+ frappe.throw(_("Barcode {0} is not a valid {1} code").format(
+ item_barcode.barcode, item_barcode.barcode_type))
def validate_warehouse_for_reorder(self):
'''Validate Reorder level table for duplicate and conditional mandatory'''
@@ -483,7 +503,7 @@
warehouse += [d.get("warehouse")]
else:
frappe.throw(_("Row {0}: An Reorder entry already exists for this warehouse {1}")
- .format(d.idx, d.warehouse), DuplicateReorderRows)
+ .format(d.idx, d.warehouse), DuplicateReorderRows)
if d.warehouse_reorder_level and not d.warehouse_reorder_qty:
frappe.throw(_("Row #{0}: Please set reorder quantity").format(d.idx))
@@ -497,12 +517,13 @@
def validate_name_with_item_group(self):
# causes problem with tree build
if frappe.db.exists("Item Group", self.name):
- frappe.throw(_("An Item Group exists with same name, please change the item name or rename the item group"))
+ frappe.throw(
+ _("An Item Group exists with same name, please change the item name or rename the item group"))
def update_item_price(self):
frappe.db.sql("""update `tabItem Price` set item_name=%s,
item_description=%s, modified=NOW() where item_code=%s""",
- (self.item_name, self.description, self.name))
+ (self.item_name, self.description, self.name))
def on_trash(self):
super(Item, self).on_trash()
@@ -512,7 +533,7 @@
frappe.delete_doc("Item", variant_of.name)
def before_rename(self, old_name, new_name, merge=False):
- if self.item_name==old_name:
+ if self.item_name == old_name:
frappe.db.set_value("Item", old_name, "item_name", new_name)
if merge:
@@ -524,7 +545,7 @@
new_properties = [cstr(d) for d in frappe.db.get_value("Item", new_name, field_list)]
if new_properties != [cstr(self.get(fld)) for fld in field_list]:
frappe.throw(_("To merge, following properties must be same for both items")
- + ": \n" + ", ".join([self.meta.get_label(fld) for fld in field_list]))
+ + ": \n" + ", ".join([self.meta.get_label(fld) for fld in field_list]))
def after_rename(self, old_name, new_name, merge):
if self.route:
@@ -547,7 +568,7 @@
item_wise_tax_detail.pop(old_name)
frappe.db.set_value(dt, d.name, "item_wise_tax_detail",
- json.dumps(item_wise_tax_detail), update_modified=False)
+ json.dumps(item_wise_tax_detail), update_modified=False)
def set_last_purchase_rate(self, new_name):
last_purchase_rate = get_last_purchase_details(new_name).get("base_rate", 0)
@@ -575,13 +596,14 @@
self.set("website_specifications", [])
if self.item_group:
for label, desc in frappe.db.get_values("Item Website Specification",
- {"parent": self.item_group}, ["label", "description"]):
- row = self.append("website_specifications")
- row.label = label
- row.description = desc
+ {"parent": self.item_group}, ["label", "description"]):
+ row = self.append("website_specifications")
+ row.label = label
+ row.description = desc
def update_bom_item_desc(self):
- if self.is_new(): return
+ if self.is_new():
+ return
if self.db_get('description') != self.description:
frappe.db.sql("""
@@ -620,11 +642,11 @@
def update_variants(self):
if self.flags.dont_update_variants or \
- frappe.db.get_single_value('Item Variant Settings', 'do_not_update_variants'):
+ frappe.db.get_single_value('Item Variant Settings', 'do_not_update_variants'):
return
if self.has_variants:
updated = []
- variants = frappe.db.get_all("Item", fields=["item_code"], filters={"variant_of": self.name })
+ variants = frappe.db.get_all("Item", fields=["item_code"], filters={"variant_of": self.name})
for d in variants:
variant = frappe.get_doc("Item", d)
copy_attributes_to_variant(self, variant)
@@ -641,39 +663,41 @@
def validate_stock_exists_for_template_item(self):
if self.stock_ledger_created() and self._doc_before_save:
if (self._doc_before_save.has_variants != self.has_variants
- or self._doc_before_save.variant_of != self.variant_of):
+ or self._doc_before_save.variant_of != self.variant_of):
frappe.throw(_("Cannot change Variant properties after stock transction. You will have to make a new Item to do this.").format(self.name),
- StockExistsForTemplate)
+ StockExistsForTemplate)
if self.has_variants or self.variant_of:
if not self.is_child_table_same('attributes'):
- frappe.throw(_('Cannot change Attributes after stock transaction. Make a new Item and transfer stock to the new Item'))
+ frappe.throw(
+ _('Cannot change Attributes after stock transaction. Make a new Item and transfer stock to the new Item'))
def validate_uom(self):
if not self.get("__islocal"):
check_stock_uom_with_bin(self.name, self.stock_uom)
if self.has_variants:
- for d in frappe.db.get_all("Item", filters= {"variant_of": self.name}):
+ for d in frappe.db.get_all("Item", filters={"variant_of": self.name}):
check_stock_uom_with_bin(d.name, self.stock_uom)
if self.variant_of:
template_uom = frappe.db.get_value("Item", self.variant_of, "stock_uom")
if template_uom != self.stock_uom:
frappe.throw(_("Default Unit of Measure for Variant '{0}' must be same as in Template '{1}'")
- .format(self.stock_uom, template_uom))
+ .format(self.stock_uom, template_uom))
def validate_attributes(self):
- 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 = []
if not self.attributes:
frappe.throw(_("Attribute table is mandatory"))
for d in self.attributes:
if d.attribute in attributes:
- frappe.throw(_("Attribute {0} selected multiple times in Attributes Table".format(d.attribute)))
+ frappe.throw(
+ _("Attribute {0} selected multiple times in Attributes Table".format(d.attribute)))
else:
attributes.append(d.attribute)
def validate_variant_attributes(self):
- if self.variant_of and self.variant_based_on=='Item Attribute':
+ if self.variant_of and self.variant_based_on == 'Item Attribute':
args = {}
for d in self.attributes:
if not d.attribute_value:
@@ -683,10 +707,11 @@
variant = get_variant(self.variant_of, args, self.name)
if variant:
frappe.throw(_("Item variant {0} exists with same attributes")
- .format(variant), ItemVariantExistsError)
+ .format(variant), ItemVariantExistsError)
validate_item_variant_attributes(self, args)
+
def get_timeline_data(doctype, name):
'''returns timeline data based on stock ledger entry'''
out = {}
@@ -697,21 +722,23 @@
for date, count in items.iteritems():
timestamp = get_timestamp(date)
- out.update({ timestamp: count })
+ out.update({timestamp: count})
return out
+
def validate_end_of_life(item_code, end_of_life=None, disabled=None, verbose=1):
if (not end_of_life) or (disabled is None):
end_of_life, disabled = frappe.db.get_value("Item", item_code, ["end_of_life", "disabled"])
- if end_of_life and end_of_life!="0000-00-00" and getdate(end_of_life) <= now_datetime().date():
+ if end_of_life and end_of_life != "0000-00-00" and getdate(end_of_life) <= now_datetime().date():
msg = _("Item {0} has reached its end of life on {1}").format(item_code, formatdate(end_of_life))
_msgprint(msg, verbose)
if disabled:
_msgprint(_("Item {0} is disabled").format(item_code), verbose)
+
def validate_is_stock_item(item_code, is_stock_item=None, verbose=1):
if not is_stock_item:
is_stock_item = frappe.db.get_value("Item", item_code, "is_stock_item")
@@ -721,6 +748,7 @@
_msgprint(msg, verbose)
+
def validate_cancelled_item(item_code, docstatus=None, verbose=1):
if docstatus is None:
docstatus = frappe.db.get_value("Item", item_code, "docstatus")
@@ -729,6 +757,7 @@
msg = _("Item {0} is cancelled").format(item_code)
_msgprint(msg, verbose)
+
def _msgprint(msg, verbose):
if verbose:
msgprint(msg, raise_exception=True)
@@ -760,19 +789,19 @@
order by pr.posting_date desc, pr.posting_time desc, pr.name desc
limit 1""", (item_code, cstr(doc_name)), as_dict=1)
- purchase_order_date = getdate(last_purchase_order and last_purchase_order[0].transaction_date \
- or "1900-01-01")
- purchase_receipt_date = getdate(last_purchase_receipt and \
- last_purchase_receipt[0].posting_date or "1900-01-01")
+ purchase_order_date = getdate(last_purchase_order and last_purchase_order[0].transaction_date
+ or "1900-01-01")
+ purchase_receipt_date = getdate(last_purchase_receipt and
+ last_purchase_receipt[0].posting_date or "1900-01-01")
if (purchase_order_date > purchase_receipt_date) or \
- (last_purchase_order and not last_purchase_receipt):
+ (last_purchase_order and not last_purchase_receipt):
# use purchase order
last_purchase = last_purchase_order[0]
purchase_date = purchase_order_date
elif (purchase_receipt_date > purchase_order_date) or \
- (last_purchase_receipt and not last_purchase_order):
+ (last_purchase_receipt and not last_purchase_order):
# use purchase receipt
last_purchase = last_purchase_receipt[0]
purchase_date = purchase_receipt_date
@@ -797,11 +826,12 @@
return out
+
def invalidate_cache_for_item(doc):
invalidate_cache_for(doc, doc.item_group)
website_item_groups = list(set((doc.get("old_website_item_groups") or [])
- + [d.item_group for d in doc.get({"doctype":"Website Item Group"}) if d.item_group]))
+ + [d.item_group for d in doc.get({"doctype": "Website Item Group"}) if d.item_group]))
for item_group in website_item_groups:
invalidate_cache_for(doc, item_group)
@@ -809,13 +839,14 @@
if doc.get("old_item_group") and doc.get("old_item_group") != doc.item_group:
invalidate_cache_for(doc, doc.old_item_group)
+
def check_stock_uom_with_bin(item, stock_uom):
if stock_uom == frappe.db.get_value("Item", item, "stock_uom"):
return
- matched=True
+ matched = True
ref_uom = frappe.db.get_value("Stock Ledger Entry",
- {"item_code": item}, "stock_uom")
+ {"item_code": item}, "stock_uom")
if ref_uom:
if cstr(ref_uom) != cstr(stock_uom):
@@ -823,13 +854,14 @@
else:
bin_list = frappe.db.sql("select * from tabBin where item_code=%s", item, as_dict=1)
for bin in bin_list:
- if (bin.reserved_qty > 0 or bin.ordered_qty > 0 or bin.indented_qty > 0 \
- or bin.planned_qty > 0) and cstr(bin.stock_uom) != cstr(stock_uom):
- matched = False
- break
+ if (bin.reserved_qty > 0 or bin.ordered_qty > 0 or bin.indented_qty > 0
+ or bin.planned_qty > 0) and cstr(bin.stock_uom) != cstr(stock_uom):
+ matched = False
+ break
if matched and bin_list:
frappe.db.sql("""update tabBin set stock_uom=%s where item_code=%s""", (stock_uom, item))
if not matched:
- frappe.throw(_("Default Unit of Measure for Item {0} cannot be changed directly because you have already made some transaction(s) with another UOM. You will need to create a new Item to use a different Default UOM.").format(item))
+ frappe.throw(
+ _("Default Unit of Measure for Item {0} cannot be changed directly because you have already made some transaction(s) with another UOM. You will need to create a new Item to use a different Default UOM.").format(item))
diff --git a/erpnext/stock/doctype/item_barcode/__init__.py b/erpnext/stock/doctype/item_barcode/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/stock/doctype/item_barcode/__init__.py
diff --git a/erpnext/stock/doctype/item_barcode/item_barcode.json b/erpnext/stock/doctype/item_barcode/item_barcode.json
new file mode 100644
index 0000000..c8a3a89
--- /dev/null
+++ b/erpnext/stock/doctype/item_barcode/item_barcode.json
@@ -0,0 +1,103 @@
+{
+ "allow_copy": 0,
+ "allow_guest_to_view": 0,
+ "allow_import": 0,
+ "allow_rename": 0,
+ "autoname": "field:barcode",
+ "beta": 0,
+ "creation": "2017-12-09 18:54:50.562438",
+ "custom": 0,
+ "docstatus": 0,
+ "doctype": "DocType",
+ "document_type": "",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "fields": [
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "barcode",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 1,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "Barcode",
+ "length": 0,
+ "no_copy": 1,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 1
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "barcode_type",
+ "fieldtype": "Select",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "Barcode Type",
+ "length": 0,
+ "no_copy": 0,
+ "options": "\nEAN\nUPC-A",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ }
+ ],
+ "has_web_view": 0,
+ "hide_heading": 0,
+ "hide_toolbar": 0,
+ "idx": 0,
+ "image_view": 0,
+ "in_create": 0,
+ "is_submittable": 0,
+ "issingle": 0,
+ "istable": 1,
+ "max_attachments": 0,
+ "modified": "2017-12-10 20:55:23.814039",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Item Barcode",
+ "name_case": "",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "read_only": 0,
+ "read_only_onload": 0,
+ "show_name_in_global_search": 0,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1,
+ "track_seen": 0
+}
\ No newline at end of file
diff --git a/erpnext/stock/doctype/item_barcode/item_barcode.py b/erpnext/stock/doctype/item_barcode/item_barcode.py
new file mode 100644
index 0000000..e85f93b
--- /dev/null
+++ b/erpnext/stock/doctype/item_barcode/item_barcode.py
@@ -0,0 +1,11 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+
+from frappe.model.document import Document
+
+
+class ItemBarcode(Document):
+ pass
diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.py b/erpnext/stock/doctype/stock_settings/stock_settings.py
index 89ece33..23a18f4 100644
--- a/erpnext/stock/doctype/stock_settings/stock_settings.py
+++ b/erpnext/stock/doctype/stock_settings/stock_settings.py
@@ -25,7 +25,7 @@
frappe.msgprint (_("`Freeze Stocks Older Than` should be smaller than %d days.") %stock_frozen_limit)
# show/hide barcode field
- frappe.make_property_setter({'fieldname': 'barcode', 'property': 'hidden',
+ frappe.make_property_setter({'fieldname': 'barcodes', 'property': 'hidden',
'value': 0 if self.show_barcode_field else 1})
self.cant_change_valuation_method()
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index 6b67233..01f5206 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -120,7 +120,7 @@
@frappe.whitelist()
def get_item_code(barcode=None, serial_no=None):
if barcode:
- item_code = frappe.db.get_value("Item", {"barcode": barcode})
+ item_code = frappe.db.get_value("Item Barcode", {"barcode": barcode}, fieldname=["parent"])
if not item_code:
frappe.throw(_("No Item with Barcode {0}").format(barcode))
elif serial_no:
@@ -273,7 +273,7 @@
if not out[d[1]] or (company and args.company != company):
out[d[1]] = frappe.db.get_value("Company", args.company, d[2]) if d[2] else None
- for fieldname in ("item_name", "item_group", "barcode", "brand", "stock_uom"):
+ for fieldname in ("item_name", "item_group", "barcodes", "brand", "stock_uom"):
out[fieldname] = item.get(fieldname)
return out
diff --git a/requirements.txt b/requirements.txt
index a6cfaf2..7b5d8da 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,3 +2,4 @@
unidecode
pygithub
googlemaps
+python-stdnum