Merge branch 'hotfix'

This commit is contained in:
Nabin Hait 2018-08-29 13:13:54 +05:30
commit d982e8fcda
38 changed files with 2831 additions and 3210 deletions

View File

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

View File

@ -207,10 +207,13 @@ def validate_account_number(name, account_number, company):
@frappe.whitelist()
def update_account_number(name, account_name, account_number=None):
account = frappe.db.get_value("Account", name, ["company"], as_dict=True)
account = frappe.db.get_value("Account", name, "company", as_dict=True)
if not account: return
validate_account_number(name, account_number, account.company)
if account_number:
frappe.db.set_value("Account", name, "account_number", account_number.strip())
else:
frappe.db.set_value("Account", name, "account_number", "")
frappe.db.set_value("Account", name, "account_name", account_name.strip())
new_name = get_account_autoname(account_number, account_name, account.company)

View File

@ -27,13 +27,21 @@ class Budget(Document):
def validate_duplicate(self):
budget_against_field = frappe.scrub(self.budget_against)
budget_against = self.get(budget_against_field)
existing_budget = frappe.db.get_value("Budget", {budget_against_field: budget_against,
"fiscal_year": self.fiscal_year, "company": self.company,
"name": ["!=", self.name], "docstatus": ["!=", 2]})
if existing_budget:
frappe.throw(_("Another Budget record '{0}' already exists against {1} '{2}' for fiscal year {3}")
.format(existing_budget, self.budget_against, budget_against, self.fiscal_year), DuplicateBudgetError)
accounts = [d.account for d in self.accounts] or []
existing_budget = frappe.db.sql("""
select
b.name, ba.account from `tabBudget` b, `tabBudget Account` ba
where
ba.parent = b.name and b.company = %s and %s=%s and
b.fiscal_year=%s and b.name != %sand ba.account in (%s) """
% ('%s', budget_against_field, '%s', '%s', '%s', ','.join(['%s'] * len(accounts))),
(self.company, budget_against, self.fiscal_year, self.name) + tuple(accounts), as_dict=1)
for d in existing_budget:
frappe.throw(_("Another Budget record '{0}' already exists against {1} '{2}' and account '{3}' for fiscal year {4}")
.format(d.name, self.budget_against, budget_against, d.account, self.fiscal_year), DuplicateBudgetError)
def validate_accounts(self):
account_list = []
for d in self.get('accounts'):

View File

@ -132,7 +132,7 @@
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"search_index": 1,
"set_only_once": 0,
"unique": 0
},
@ -702,7 +702,7 @@
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"search_index": 1,
"set_only_once": 0,
"unique": 0
}
@ -718,7 +718,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2017-12-20 12:40:09.611951",
"modified": "2018-08-10 16:16:53.019380",
"modified_by": "Administrator",
"module": "Accounts",
"name": "GL Entry",
@ -794,4 +794,4 @@
"sort_order": "DESC",
"track_changes": 0,
"track_seen": 0
}
}

View File

@ -381,7 +381,7 @@
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"search_index": 1,
"set_only_once": 0,
"unique": 0
},
@ -1443,7 +1443,7 @@
"istable": 0,
"max_attachments": 0,
"menu_index": 0,
"modified": "2017-08-31 11:21:09.442695",
"modified": "2018-08-10 16:35:31.361030",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Journal Entry",

View File

@ -199,7 +199,7 @@
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"search_index": 1,
"set_only_once": 0,
"unique": 0
},
@ -661,7 +661,7 @@
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
@ -795,7 +795,7 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2017-12-07 19:54:19.851534",
"modified": "2018-08-10 16:35:42.833549",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Journal Entry Account",

View File

@ -286,7 +286,7 @@
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"search_index": 1,
"set_only_once": 0,
"unique": 0
},
@ -1439,7 +1439,7 @@
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"search_index": 1,
"set_only_once": 0,
"unique": 0
},
@ -1791,7 +1791,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-07-27 01:49:24.720317",
"modified": "2018-08-10 16:34:46.771275",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry",

View File

@ -585,6 +585,38 @@
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "discount_percentage",
"fieldname": "discount_amount",
"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": "Discount Amount",
"length": 0,
"no_copy": 0,
"options": "currency",
"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,
@ -2156,7 +2188,7 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2017-11-30 14:21:00.962126",
"modified": "2018-08-06 05:17:38.205356",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice Item",

View File

@ -123,9 +123,6 @@ class ReceivablePayableReport(object):
currency_precision = get_currency_precision() or 2
dr_or_cr = "debit" if args.get("party_type") == "Customer" else "credit"
dn_details = get_dn_details(args.get("party_type"))
voucher_details = self.get_voucher_details(args.get("party_type"), dn_details)
future_vouchers = self.get_entries_after(self.filters.report_date, args.get("party_type"))
if not self.filters.get("company"):
@ -138,7 +135,14 @@ class ReceivablePayableReport(object):
data = []
pdc_details = get_pdc_details(args.get("party_type"), self.filters.report_date)
for gle in self.get_entries_till(self.filters.report_date, args.get("party_type")):
gl_entries_data = self.get_entries_till(self.filters.report_date, args.get("party_type"))
if gl_entries_data:
voucher_nos = [d.voucher_no for d in gl_entries_data] or []
dn_details = get_dn_details(args.get("party_type"), voucher_nos)
voucher_details = get_voucher_details(args.get("party_type"), voucher_nos, dn_details)
for gle in gl_entries_data:
if self.is_receivable_or_payable(gle, dr_or_cr, future_vouchers):
outstanding_amount, credit_note_amount = self.get_outstanding_amount(gle,
self.filters.report_date, dr_or_cr, return_entries, currency_precision)
@ -171,7 +175,7 @@ class ReceivablePayableReport(object):
if self.filters.ageing_based_on == "Due Date":
entry_date = due_date
elif self.filters.ageing_based_on == "Supplier Invoice Date":
entry_date = bill_date
entry_date = bill_date
else:
entry_date = gle.posting_date
row += get_ageing_data(cint(self.filters.range1), cint(self.filters.range2),
@ -218,12 +222,11 @@ class ReceivablePayableReport(object):
def get_entries_after(self, report_date, party_type):
# returns a distinct list
return list(set([(e.voucher_type, e.voucher_no) for e in self.get_gl_entries(party_type)
if getdate(e.posting_date) > report_date]))
return list(set([(e.voucher_type, e.voucher_no) for e in self.get_gl_entries(party_type, report_date, for_future=True)]))
def get_entries_till(self, report_date, party_type):
# returns a generator
return (e for e in self.get_gl_entries(party_type) if getdate(e.posting_date) <= report_date)
return self.get_gl_entries(party_type, report_date)
def is_receivable_or_payable(self, gle, dr_or_cr, future_vouchers):
return (
@ -286,42 +289,31 @@ class ReceivablePayableReport(object):
return self.party_map
def get_voucher_details(self, party_type, dn_details):
voucher_details = frappe._dict()
def get_gl_entries(self, party_type, date=None, for_future=False):
conditions, values = self.prepare_conditions(party_type)
if party_type == "Customer":
for si in frappe.db.sql("""select name, due_date, po_no
from `tabSales Invoice` where docstatus=1""", as_dict=1):
si['delivery_note'] = dn_details.get(si.name)
voucher_details.setdefault(si.name, si)
if self.filters.get(scrub(party_type)):
select_fields = "sum(debit_in_account_currency) as debit, sum(credit_in_account_currency) as credit"
else:
select_fields = "sum(debit) as debit, sum(credit) as credit"
if party_type == "Supplier":
for pi in frappe.db.sql("""select name, due_date, bill_no, bill_date
from `tabPurchase Invoice` where docstatus = 1
union
select name, due_date, bill_no, bill_date from `tabJournal Entry`
where docstatus = 1 and bill_no is not NULL""", as_dict=1):
voucher_details.setdefault(pi.name, pi)
if date and not for_future:
conditions += " and posting_date <= '%s'" % date
return voucher_details
if date and for_future:
conditions += " and posting_date > '%s'" % date
def get_gl_entries(self, party_type):
if not hasattr(self, "gl_entries"):
conditions, values = self.prepare_conditions(party_type)
if self.filters.get(scrub(party_type)):
select_fields = "sum(debit_in_account_currency) as debit, sum(credit_in_account_currency) as credit"
else:
select_fields = "sum(debit) as debit, sum(credit) as credit"
self.gl_entries = frappe.db.sql("""select name, posting_date, account, party_type, party,
voucher_type, voucher_no, against_voucher_type, against_voucher,
account_currency, remarks, {0}
from `tabGL Entry`
where docstatus < 2 and party_type=%s and (party is not null and party != '') {1}
self.gl_entries = frappe.db.sql("""
select
name, posting_date, account, party_type, party, voucher_type, voucher_no,
against_voucher_type, against_voucher, account_currency, remarks, {0}
from
`tabGL Entry`
where
docstatus < 2 and party_type=%s and (party is not null and party != '') {1}
group by voucher_type, voucher_no, against_voucher_type, against_voucher, party
order by posting_date, party"""
.format(select_fields, conditions), values, as_dict=True)
.format(select_fields, conditions), values, as_dict=True)
return self.gl_entries
@ -457,18 +449,57 @@ def get_pdc_details(party_type, report_date):
return pdc_details
def get_dn_details(party_type):
def get_dn_details(party_type, voucher_nos):
dn_details = frappe._dict()
if party_type == "Customer":
for si in frappe.db.sql("""select parent, GROUP_CONCAT(delivery_note SEPARATOR ', ') as dn
from `tabSales Invoice Item`
where docstatus=1 and delivery_note is not null and delivery_note != '' group by parent
Union
select against_sales_invoice as parent, GROUP_CONCAT(parent SEPARATOR ', ') as dn
from `tabDelivery Note Item`
where docstatus=1 and against_sales_invoice is not null
and against_sales_invoice != '' group by against_sales_invoice""", as_dict=1):
for si in frappe.db.sql("""
select
parent, GROUP_CONCAT(delivery_note SEPARATOR ', ') as dn
from
`tabSales Invoice Item`
where
docstatus=1 and delivery_note is not null and delivery_note != ''
and parent in (%s) group by parent
""" %(','.join(['%s'] * len(voucher_nos))), tuple(voucher_nos) , as_dict=1):
dn_details.setdefault(si.parent, si.dn)
for si in frappe.db.sql("""
select
against_sales_invoice as parent, GROUP_CONCAT(parent SEPARATOR ', ') as dn
from
`tabDelivery Note Item`
where
docstatus=1 and against_sales_invoice is not null and against_sales_invoice != ''
and against_sales_invoice in (%s)
group by against_sales_invoice
""" %(','.join(['%s'] * len(voucher_nos))), tuple(voucher_nos) , as_dict=1):
if si.parent in dn_details:
dn_details[si.parent] += ', %s' %(si.dn)
else:
dn_details.setdefault(si.parent, si.dn)
return dn_details
def get_voucher_details(party_type, voucher_nos, dn_details):
voucher_details = frappe._dict()
if party_type == "Customer":
for si in frappe.db.sql("""select name, due_date, po_no
from `tabSales Invoice` where docstatus=1 and name in (%s)
""" %(','.join(['%s'] *len(voucher_nos))), (tuple(voucher_nos)), as_dict=1):
si['delivery_note'] = dn_details.get(si.name)
voucher_details.setdefault(si.name, si)
if party_type == "Supplier":
for pi in frappe.db.sql("""select name, due_date, bill_no, bill_date
from `tabPurchase Invoice` where docstatus = 1 and name in (%s)
""" %(','.join(['%s'] *len(voucher_nos))), (tuple(voucher_nos)), as_dict=1):
voucher_details.setdefault(pi.name, pi)
for pi in frappe.db.sql("""select name, due_date, bill_no, bill_date from
`tabJournal Entry` where docstatus = 1 and bill_no is not NULL and name in (%s)
""" %(','.join(['%s'] *len(voucher_nos))), (tuple(voucher_nos)), as_dict=1):
voucher_details.setdefault(pi.name, pi)
return voucher_details

View File

@ -22,7 +22,7 @@ frappe.ui.form.on('Asset Category', {
var d = locals[cdt][cdn];
return {
"filters": {
"root_type": "Asset",
"account_type": "Accumulated Depreciation",
"is_group": 0,
"company": d.company_name
}
@ -41,4 +41,4 @@ frappe.ui.form.on('Asset Category', {
});
}
});
});

View File

@ -629,6 +629,38 @@
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "discount_percentage",
"fieldname": "discount_amount",
"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": "Discount Amount",
"length": 0,
"no_copy": 0,
"options": "currency",
"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,
@ -1897,7 +1929,7 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2018-07-18 07:53:54.677844",
"modified": "2018-08-06 05:16:58.258276",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order Item",

View File

@ -470,6 +470,38 @@
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "discount_percentage",
"fieldname": "discount_amount",
"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": "Discount Amount",
"length": 0,
"no_copy": 0,
"options": "currency",
"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,
@ -1645,7 +1677,7 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2018-01-25 15:04:40.171617",
"modified": "2018-08-06 05:33:07.404385",
"modified_by": "Administrator",
"module": "Buying",
"name": "Supplier Quotation Item",

View File

@ -218,7 +218,8 @@ def bom(doctype, txt, searchfield, start, page_len, filters):
def get_project_name(doctype, txt, searchfield, start, page_len, filters):
cond = ''
if filters.get('customer'):
cond = '(`tabProject`.customer = "' + filters['customer'] + '" or ifnull(`tabProject`.customer,"")="") and'
cond = """(`tabProject`.customer = '%s' or
ifnull(`tabProject`.customer,"")="") and""" %(frappe.db.escape(filters.get("customer")))
return frappe.db.sql("""select `tabProject`.name from `tabProject`
where `tabProject`.status not in ("Completed", "Cancelled")

View File

@ -151,6 +151,7 @@ def get_ref_item_dict(valid_items, ref_item_row):
"rejected_qty": 0,
"received_qty": 0,
"serial_no": [],
"conversion_factor": ref_item_row.get("conversion_factor", 1),
"batch_no": []
}))
item_dict = valid_items[ref_item_row.item_code]

View File

@ -1,7 +1,7 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_import": 1,
"allow_rename": 0,
"autoname": "RES.######",
"beta": 0,
@ -662,7 +662,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-01-09 14:14:30.090317",
"modified": "2018-08-20 11:21:19.248393",
"modified_by": "Administrator",
"module": "Education",
"name": "Assessment Result",

View File

@ -1,873 +0,0 @@
/* globals Hub, HubList */
frappe.provide('erpnext.hub');
frappe.pages['hub'].on_page_load = function(wrapper) {
const page = frappe.ui.make_app_page({
parent: wrapper,
title: 'Hub',
single_col: false
});
wrapper.hub_page = new erpnext.hub.Hub({ page });
};
frappe.pages['hub'].on_page_show = function(wrapper) {
const hub_page = wrapper.hub_page;
const [hub, type, id] = frappe.get_route();
if (!(hub || type || id)) {
hub_page.go_to_home_page();
return;
}
if (type === "Products") {
hub_page.go_to_item_page(id);
} else if (type === "Company") {
hub_page.go_to_company_page(id);
}
}
erpnext.hub.Hub = class Hub {
constructor({ page }) {
this.page = page;
frappe.require('/assets/erpnext/css/hub.css', () => {
this.setup();
});
}
setup() {
this.setup_header();
this.company_cache = {};
this.item_cache = {};
this.filters = {};
this.order_by = '';
this.$hub_main_section =
$(`<div class='hub-main-section'>`).appendTo(this.page.body);
this.bind_events();
this.refresh();
}
refresh() {
this.$hub_main_section.empty();
this.page.page_form.hide();
const $layout_main = this.page.wrapper.find('.layout-main');
const $page_head = this.page.wrapper.find('.page-head');
frappe.model.with_doc('Hub Settings', 'Hub Settings', () => {
this.hub_settings = frappe.get_doc('Hub Settings');
if(this.hub_settings.enabled == 0) {
let $empty_state = this.page.get_empty_state(
__("Register for Hub"),
__(`Let other ERPNext users discover your products
and automate workflow with Supplier from within ERPNext.`),
__("Register")
);
$page_head.hide();
$layout_main
.find('.layout-side-section, .layout-main-section-wrapper')
.hide();
$layout_main.append($empty_state);
$empty_state.find('.btn-primary').on('click', () => {
this.register_for_hub();
});
} else {
$page_head.show();
$layout_main.find('.page-card-container').remove();
$layout_main.find('.layout-side-section, .layout-main-section-wrapper').show();
this.setup_live_state();
}
});
}
register_for_hub() {
if (frappe.session.user.includes('Administrator')) {
frappe.throw(__('Please login as another user.'))
}
frappe.verify_password(() => {
frappe.call({
method: 'erpnext.hub_node.enable_hub',
callback: (r) => {
if(r.message.enabled == 1) {
Object.assign(this.hub_settings, r.message);
this.refresh();
this.prompt_for_item_sync();
}
}
});
});
}
prompt_for_item_sync() {
frappe.call({
method: 'frappe.client.get_list',
args: {
doctype: 'Data Migration Run',
filters: {
'data_migration_plan': 'Hub Sync'
},
limit_page_length: 1
},
callback: function(r) {
if (!r) {
frappe.confirm(__('Do you want to publish your Items to Hub ?'), () => {
this.sync_items_to_hub();
});
}
}
})
}
setup_header() {
this.page.page_title = this.page.wrapper.find('.page-title');
this.tag_line = $(`
<div class='tag-line-container'>
<span class='tag-line text-muted small'>
${__('Product listing and discovery for ERPNext users')}
</span>
</div>`)
.appendTo(this.page.page_title);
this.bind_title();
}
setup_live_state() {
if(!this.$search) {
this.setup_filters();
}
this.page.page_form.show();
this.setup_menu();
this.setup_sidebar();
this.render_body();
this.setup_lists();
}
setup_filters() {
// frappe.call({
// method: 'erpnext.hub_node.get_categories'
// }).then((r) => {
// if (r.message) {
// const categories = r.message;
// console.log("categories", categories);
// categories
// .map(c => c.hub_category_name)
// .map(c => this.sidebar.add_item({
// label: c,
// on_click: () => {
// this.home_item_list &&
// this.home_item_list.refresh({
// text: '',
// start: 0,
// limit: 20,
// category: c && c !== 'All Categories' ? c : undefined
// });
// }
// }, __('Hub Category')));
// }
// });
// this.category_select = this.page.add_select(__('Category'),
// [
// {label: __('Sort by Price ...'), value: '' },
// {label: __('High to Low'), value: 'price desc' },
// {label: __('Low to High'), value: 'price' },
// ]
// );
this.price_sort_select = this.page.add_select(__('Sort by Price'),
[
{label: __('Sort by Price ...'), value: '' },
{label: __('High to Low'), value: 'price desc' },
{label: __('Low to High'), value: 'price' },
]
);
this.criteria_select = this.page.add_select(__('Sort by Criteria'),
[
{label: __('Most Popular'), value: 'request_count' },
{label: __('Newest'), value: 'creation' },
]
);
this.price_sort_select.on('change', () => {
this.refresh_item_only_page();
});
this.criteria_select.on('change', () => {
this.refresh_item_only_page();
});
this.setup_hub_category_filter();
this.setup_search();
}
bind_events() {
const me = this;
this.$hub_main_section
.on('click', '.company-link a', function(e) {
e.preventDefault();
const company_name = $(this).attr('data-company-name');
frappe.set_route('hub', 'Company', company_name);
})
.on('click', '.breadcrumb li', function(e) {
e.preventDefault();
const $li = $(this);
if ($li.attr('data-route') === 'Home') {
me.go_to_home_page();
}
});
}
update_filters() {
let price_sort = $(this.price_sort_select).val() || '';
let criteria = $(this.criteria_select).val() || '';
let order_by_params = [];
let query_string = '';
if(criteria) {
order_by_params.push(criteria);
// query_string += 'sort_by=' + criteria
}
if(price_sort) order_by_params.push(price_sort);
this.order_by = order_by_params.join(",");
// return query_string;
}
reset_filters() {
this.order_by = '';
$(this.category_select).val('');
$(this.price_sort_select).val('');
$(this.criteria_select).val('Most Popular');
}
refresh_item_only_page() {
this.reset_search();
this.update_filters();
this.go_to_items_only_page(
['hub', 'Products'],
'', 'product-list'
);
}
bind_title() {
this.page.page_title.find('.title-text').on('click', () => {
this.go_to_home_page();
});
}
render_body() {
this.$home_page = $(`
<div class = 'hub-home-page'>
<div class='banner'></div>
<div class='listing-body row'>
<div class='main-list-section'></div>
</div>
</div>
`).appendTo(this.$hub_main_section);
this.$banner = this.$hub_main_section.find('.banner');
this.$listing_body = this.$hub_main_section.find('.listing-body');
this.$main_list_section = this.$hub_main_section.find('.main-list-section');
this.$side_list_section = this.$hub_main_section.find('.side-list-section');
}
setup_lists() {
this.home_item_list = new erpnext.hub.HubList({
parent: this.$main_list_section,
title: 'New',
page_length: 20,
list_css_class: 'home-product-list',
method: 'erpnext.hub_node.get_items',
// order_by: 'request_count',
filters: {text: '', country: this.country}, // filters at the time of creation
on_item_click: (item_code) => {
frappe.set_route('hub', 'Products', item_code);
}
});
this.home_item_list.setup();
}
setup_hub_category_filter() {
const me = this;
this.hub_category_field = this.page.add_field({
fieldtype: 'Autocomplete',
label: 'Hub Category',
change() {
let value = this.get_value();
let title = value;
if (value === 'All Categories') {
// show all items
value = null;
}
me.home_item_list.title = title;
me.home_item_list.refresh({
text: '',
start: 0,
limit: 20,
category: value
});
}
});
frappe.call('erpnext.hub_node.get_categories')
.then((r) => {
if (r.message) {
const categories = r.message;
this.hub_category_field.set_data(
categories.map(c => c.hub_category_name)
);
}
});
}
setup_search() {
this.$search = this.page.add_data(__('Search'));
this.$search.on('keypress', (e) => {
if(e.which === 13) {
var search_term = ($(this.$search).val() || '').toLowerCase();
this.go_to_items_only_page(
['hub', 'search', search_term],
'Search results for \'' + search_term + '\'',
'search-product-list',
{text: search_term}
);
}
});
}
go_to_items_only_page(route, title, class_name, filters = {text: ''}, by_item_codes=0) {
frappe.set_route(route);
this.$hub_main_section.empty();
this.filtered_item_list = new erpnext.hub.HubList({
parent: this.$hub_main_section,
title: title,
page_length: 20,
list_css_class: class_name,
method: 'erpnext.hub_node.get_items',
order_by: this.order_by,
filters: filters,
by_item_codes: by_item_codes
});
this.filtered_item_list.on_item_click = (item_code) => {
frappe.set_route('hub', 'Products', item_code);
}
this.filtered_item_list.setup();
}
go_to_item_page(item_code) {
if(this.item_cache) {
let item = this.item_cache[item_code];
if(item) {
this.render_item_page(item);
return;
}
} else {
this.item_cache = {};
}
frappe.call({
args:{
hub_sync_id: item_code
},
method: "erpnext.hub_node.get_item_details",
callback: (r) => {
if (!r || !r.message) return;
let item = r.message;
this.item_cache[item_code] = item;
this.render_item_page(item);
}
});
}
render_item_page(item) {
this.$hub_main_section.empty();
let $item_page =
$(this.get_item_page(item))
.appendTo(this.$hub_main_section);
let $company_items = $item_page.find('.company-items');
let company_item_list = new erpnext.hub.HubList({
parent: $company_items,
title: 'More by ' + item.company_name,
page_length: 5,
list_css_class: 'company-item-list',
method: 'erpnext.hub_node.get_items',
// order_by: 'request_count',
filters: {text: '', company_name: item.company_name, country: this.country},
paginated: 0,
img_size: 150
});
company_item_list.on_item_click = (item_code) => {
frappe.set_route('hub', 'Products', item_code);
}
company_item_list.setup();
$item_page.find('.rfq-btn')
.click((e) => {
const $btn = $(e.target);
this.show_rfq_modal(item)
.then(values => {
item.item_code = values.item_code;
delete values.item_code;
const supplier = values;
return [item, supplier];
})
.then(([item, supplier]) => {
return this.make_rfq(item, supplier, $btn);
})
.then(r => {
console.log(r);
if (r.message && r.message.rfq) {
$btn.addClass('disabled').html(`<span><i class='fa fa-check'></i> ${__('Quote Requested')}</span>`);
} else {
throw r;
}
})
.catch((e) => {
console.log(e); //eslint-disable-line
});
});
}
show_rfq_modal(item) {
return new Promise(res => {
let fields = [
{ label: __('Item Code'), fieldtype: 'Data', fieldname: 'item_code', default: item.item_code },
{ fieldtype: 'Column Break' },
{ label: __('Item Group'), fieldtype: 'Link', fieldname: 'item_group', default: item.item_group },
{ label: __('Supplier Details'), fieldtype: 'Section Break' },
{ label: __('Supplier Name'), fieldtype: 'Data', fieldname: 'supplier_name', default: item.company_name },
{ label: __('Supplier Email'), fieldtype: 'Data', fieldname: 'supplier_email', default: item.seller },
{ fieldtype: 'Column Break' },
{ label: __('Supplier Type'), fieldname: 'supplier_type',
fieldtype: 'Link', options: 'Supplier Type' }
];
fields = fields.map(f => { f.reqd = 1; return f; });
const d = new frappe.ui.Dialog({
title: __('Request for Quotation'),
fields: fields,
primary_action_label: __('Send'),
primary_action: (values) => {
res(values);
d.hide();
}
});
d.show();
});
}
get_company_details(company_id) {
this.company_cache = this.company_cache || {};
return new Promise(resolve => {
// get from cache if exists
let company_details = this.company_cache[company_id];
if(company_details) {
resolve(company_details);
return;
}
frappe.call({
method: 'erpnext.hub_node.get_company_details',
args: {hub_sync_id: company_id}
}).then((r) => {
if (r.message) {
const company_details = r.message;
this.company_cache[company_id] = company_details;
resolve(company_details)
}
});
})
}
go_to_company_page(company_id) {
this.get_company_details(company_id)
.then(this.show_company_page.bind(this));
}
show_company_page(company_details) {
this.$hub_main_section.empty();
let $company_page =
$(this.get_company_page(company_details))
.appendTo(this.$hub_main_section);
let $company_items = $company_page.find('.company-items');
let company_item_list = new erpnext.hub.HubList({
parent: $company_items,
title: 'More by ' + company_details.company_name,
page_length: 5,
list_css_class: 'company-item-list',
method: 'erpnext.hub_node.get_items',
// order_by: 'request_count',
filters: {text: '', company: company_details.company_name, country: this.country},
paginated: 0,
img_size: 150
});
company_item_list.on_item_click = (item_code) => {
frappe.set_route('hub', 'Products', item_code);
}
company_item_list.setup();
}
get_item_page(item) {
return `
<div class="hub-item-page">
<div class="item-header">
<div class="item-page-image">
${ this.home_item_list.get_item_image(item) }
</div>
<div class="title-content">
<div class="breadcrumbs">
${this.get_breadcrumb(item.item_name, "Products") }
</div>
<div class="title">
<h2>${ item.item_name }</h2>
</div>
<div class="company">
<span class="">${ item.company_name }</span>
</div>
<div class="category">
<span class="text-muted">Products</span>
</div>
<div class="description">
<span class="small">${ item.description ? item.description : "" }</span>
</div>
<div class="price">
${ item.formatted_price ? item.formatted_price : '' }
</div>
<div class="actions">
<a class="btn btn-primary rfq-btn">Request A Quote</a>
</div>
</div>
</div>
<div class="item-more-info"></div>
<div class="company-items">
</div>
</div>
`;
}
get_company_page(company_details) {
return `
<div class="hub-item-page">
<div class="item-header">
<div class="title-content">
<div class="breadcrumbs">
${this.get_breadcrumb(company_details.company_name, "Company") }
</div>
<div class="title">
<h2>${ company_details.company_name }</h2>
</div>
<div class="company">
<span class="">${ company_details.country }</span>
</div>
<div class="description">
<span class="small">${ company_details.site_name }</span>
</div>
</div>
</div>
<div class="item-more-info"></div>
<div class="company-items">
</div>
</div>
`;
}
get_breadcrumb(name, type) {
return `
<ul class="breadcrumb">
<li data-route="Home">
<a href><span>Home</span></a>
</li>
<li data-route="List">
<a href><span>${type}</span></a>
</li>
<li class="active">
<span>${name}</span>
</li>
</ul>
`;
}
go_to_home_page() {
frappe.set_route('hub');
this.reset_filters();
this.refresh();
}
setup_menu() {
if (this.menu_setup) return;
this.page.add_menu_item(__('Hub Settings'),
() => frappe.set_route('Form', 'Hub Settings'));
this.page.add_menu_item(__('Refresh'), () => this.refresh());
this.page.add_menu_item(__('Sync'), () => this.sync_items_to_hub());
this.menu_setup = true;
}
sync_items_to_hub() {
frappe.call('erpnext.hub_node.doctype.hub_settings.hub_settings.sync')
}
setup_sidebar() {
var me = this;
this.sidebar = new frappe.ui.Sidebar({
wrapper: this.page.wrapper.find('.layout-side-section'),
css_class: 'hub-sidebar'
});
this.add_account_to_sidebar();
}
add_account_to_sidebar() {
this.sidebar.add_item({
label: this.hub_settings.company,
on_click: () => frappe.set_route('Form', 'Company', this.hub_settings.company)
}, __("Account"));
this.sidebar.add_item({
label: __("My Orders"),
on_click: () => frappe.set_route('List', 'Request for Quotation')
}, __("Account"));
}
get_search_term() {
return this.$search.val();
}
reset_search() {
this.$search.val('');
}
make_rfq(item, supplier, btn) {
console.log(supplier);
return new Promise((resolve, reject) => {
frappe.call({
method: 'erpnext.hub_node.make_rfq_and_send_opportunity',
args: { item, supplier },
callback: resolve,
btn,
}).fail(reject);
});
}
go_to_seen_items() {
this.go_to_items_only_page(
['hub', 'Requested Products'],
__('Requested Products'),
'requested-product-list',
{}, 1
);
}
}
erpnext.hub.HubList = class HubList {
constructor({
parent = null,
title = 'Products',
page_length = 20,
list_css_class = '',
method = 'erpnext.hub_node.get_items',
filters = {text: ''},
order_by = '',
by_item_codes = 0,
paginated = 1,
on_item_click = null,
img_size = 200
}) {
this.parent = parent;
this.title = title;
this.page_length = page_length;
this.list_css_class = list_css_class;
this.method = method;
this.filters = filters;
this.order_by = order_by;
this.by_item_codes = by_item_codes;
this.paginated = paginated;
this.on_item_click = on_item_click;
this.img_size = img_size;
}
// to be called on demand
setup() {
this.container = $(`
<div class='item-list-container ${this.list_css_class}' data-page-length='${this.page_length}'>
<div class='item-list-header'>
<h3>${this.title}</h3>
</div>
<div class='item-list'></div>
<div class='list-state'>
<div class='loading'>
<p class='text-muted text-center'>${__('Loading...')}</p>
</div>
<div class='done hide'>
<p class='text-muted text-center'>${__('No more results')}</p>
</div>
<div class='more text-right'>
<button class='btn btn-default btn-sm'>${__('More')}</div>
</div>
</div>
</div>`)
.appendTo(this.parent);
this.$item_list_title = this.container.find('.item-list-header h3');
this.$list = this.container.find('.item-list');
this.$loading = this.container.find('.loading').hide();
this.$more = this.container.find('.more').hide();
this.$done = this.container.find('.done');
this.$more.on('click', () => {
this.next_page();
});
this.next_page();
}
refresh(filters = this.filters) {
this.reset();
this.set_filters(filters);
this.next_page();
}
reset() {
this.$list.empty();
}
set_filters(filters) {
this.filters = filters;
}
next_page() {
this.$item_list_title.html(this.title);
const start = this.$list.find('.hub-item-wrapper').length;
this.$loading.show();
// build args
let args = {
start: start,
// query one extra
limit: this.page_length + 1
};
Object.assign(args, this.filters);
console.log("filters: ", args);
args.order_by = this.order_by;
args.by_item_codes = this.by_item_codes;
frappe.call({
method: this.method,
args: args,
callback: (r) => {
let items = r.message;
console.log("items: ", items);
this.render_items(items);
}
});
}
render_items(items) {
if(items) {
// clear any filler divs
this.$list.find('.filler').remove();
let done = 0;
console.log("items length", items.length);
if(items.length && items.length > this.page_length) {
// remove the extra queried
items.pop();
} else {
done = 1;
}
items.forEach((item) => {
this.make_item_card(item).appendTo(this.$list);
});
const remainder = items.length % 4;
if (remainder > 0) {
// fill with filler divs to make flexbox happy
Array.from(Array(remainder))
.map(r => $('<div class="filler">').css('width', '200px').appendTo(this.$list));
}
this.update_list_state(done);
} else {
this.update_list_state(1);
}
}
update_list_state(done=0) {
this.$loading.hide();
if(done) {
this.$done.removeClass('hide');
this.$more.hide();
} else {
this.$more.show();
this.$done.addClass('hide');
}
}
make_item_card(item) {
let $item_card = $(`
<div class="hub-item-wrapper" style="max-width: ${this.img_size}px;">
<a class="item-link" href>
<div class="hub-item-image">
${ this.get_item_image(item) }
</div>
<div class="hub-item-title">
<h5 class="bold">
${!item.seen ? item.item_name : `<span class="indicator blue">${item.item_name}</span>`}
<h5>
</div>
</a>
<div class="company-link">
<a data-company-name="${ item.company_name }" class="">${ item.company_name }</a>
</div>
<div>${ item.formatted_price ? item.formatted_price : ''}</div>
</div>
`);
$item_card.find(".item-link").click((e) => {
e.preventDefault();
this.on_item_click && this.on_item_click(item.name);
});
return $item_card;
}
get_item_image(item, size=this.img_size) {
const _size = size + 'px';
const item_image = item.image ?
`<img src="${item.image}"><span class="helper"></span>` :
`<div class="standard-image">${item.item_name[0]}</div>`;
return `
<div class="img-wrapper"
style="max-width: ${_size}; width: ${_size}; height: ${_size};">
${item_image}
</div>`;
}
}

View File

@ -1,21 +0,0 @@
{
"content": null,
"creation": "2015-02-18 05:17:17.301735",
"docstatus": 0,
"doctype": "Page",
"modified": "2015-02-18 05:17:17.301735",
"modified_by": "Administrator",
"module": "Hub Node",
"name": "hub",
"owner": "Administrator",
"page_name": "hub",
"roles": [
{
"role": "All"
}
],
"script": null,
"standard": "Yes",
"style": null,
"title": "Hub"
}

View File

@ -502,4 +502,6 @@ erpnext.patches.v10_0.taxes_issue_with_pos
erpnext.patches.v10_0.set_qty_in_transactions_based_on_serial_no_input
erpnext.patches.v10_0.show_leaves_of_all_department_members_in_calendar
erpnext.patches.v10_0.update_status_in_purchase_receipt
erpnext.patches.v10_0.update_address_template_for_india
erpnext.patches.v10_0.update_address_template_for_india
erpnext.patches.v10_0.set_discount_amount
erpnext.patches.v10_0.recalculate_gross_margin_for_project

View File

@ -0,0 +1,14 @@
# Copyright (c) 2017, Frappe and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
def execute():
frappe.reload_doc('projects', 'doctype', 'project')
for d in frappe.db.sql(""" select name from `tabProject` where
ifnull(total_consumed_material_cost, 0 ) > 0 and ifnull(total_billed_amount, 0) > 0""", as_dict=1):
doc = frappe.get_doc("Project", d.name)
doc.calculate_gross_margin()
doc.db_set('gross_margin', doc.gross_margin)
doc.db_set('per_gross_margin', doc.per_gross_margin)

View File

@ -0,0 +1,56 @@
import frappe
def execute():
frappe.reload_doc("accounts", "doctype", "sales_invoice_item")
frappe.reload_doc('accounts', 'doctype', 'purchase_invoice_item')
frappe.reload_doc('buying', 'doctype', 'purchase_order_item')
frappe.reload_doc('buying', 'doctype', 'supplier_quotation_item')
frappe.reload_doc('selling', 'doctype', 'sales_order_item')
frappe.reload_doc('selling', 'doctype', 'quotation_item')
frappe.reload_doc('stock', 'doctype', 'delivery_note_item')
frappe.reload_doc('stock', 'doctype', 'purchase_receipt_item')
selling_doctypes = ["Sales Order Item", "Sales Invoice Item", "Delivery Note Item", "Quotation Item"]
buying_doctypes = ["Purchase Order Item", "Purchase Invoice Item", "Purchase Receipt Item", "Supplier Quotation Item"]
for doctype in selling_doctypes:
values = frappe.db.sql('''
SELECT
discount_percentage, rate_with_margin, price_list_rate, name
FROM
`tab%s`
WHERE
ifnull(discount_percentage, 0) > 0
''' % (doctype), as_dict=True)
calculate_discount(doctype, values)
for doctype in buying_doctypes:
values = frappe.db.sql('''
SELECT
discount_percentage, price_list_rate, name
FROM
`tab%s`
WHERE
discount_percentage > 0
''' % (doctype), as_dict=True)
calculate_discount(doctype, values)
def calculate_discount(doctype, values):
rate = None
if not values: return
for d in values:
if d.rate_with_margin and d.rate_with_margin > 0:
rate = d.rate_with_margin
else:
rate = d.price_list_rate
discount_value = rate * d.get('discount_percentage') / 100
frappe.db.sql('''
UPDATE
`tab%s`
SET
discount_amount = %s
WHERE
name = '%s'
''' % (doctype, discount_value, d.get('name')))

View File

@ -14,6 +14,7 @@
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@ -41,10 +42,43 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_3",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@ -71,10 +105,75 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "show_availability_status",
"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": "Show Availability Status",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "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,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@ -103,6 +202,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}
],
@ -116,7 +216,7 @@
"issingle": 1,
"istable": 0,
"max_attachments": 0,
"modified": "2017-11-07 19:34:33.055048",
"modified": "2018-08-14 17:59:58.473100",
"modified_by": "Administrator",
"module": "Portal",
"name": "Products Settings",

View File

@ -69,6 +69,7 @@ class Project(Document):
self.tasks = []
self.load_tasks()
self.send_welcome_email()
self.update_percent_complete()
def validate_project_name(self):
if self.get("__islocal") and frappe.db.exists("Project", self.project_name):
@ -236,9 +237,13 @@ class Project(Document):
self.update_purchase_costing()
self.update_sales_amount()
self.update_billed_amount()
self.calculate_gross_margin()
self.gross_margin = flt(self.total_billed_amount) - (flt(self.total_costing_amount) + flt(self.total_expense_claim) + flt(self.total_purchase_cost))
def calculate_gross_margin(self):
expense_amount = (flt(self.total_costing_amount) + flt(self.total_expense_claim)
+ flt(self.total_purchase_cost) + flt(self.get('total_consumed_material_cost', 0)))
self.gross_margin = flt(self.total_billed_amount) - expense_amount
if self.total_billed_amount:
self.per_gross_margin = (self.gross_margin / flt(self.total_billed_amount)) *100

View File

@ -113,8 +113,8 @@ erpnext.buying.BuyingController = erpnext.TransactionController.extend({
var item = frappe.get_doc(cdt, cdn);
frappe.model.round_floats_in(item, ["price_list_rate", "discount_percentage"]);
item.rate = flt(item.price_list_rate * (1 - item.discount_percentage / 100.0),
precision("rate", item));
item.discount_amount = flt(item.price_list_rate) * flt(item.discount_percentage) / 100;
item.rate = flt((item.price_list_rate) - (item.discount_amount), precision('rate', item));
this.calculate_taxes_and_totals();
},

View File

@ -16,8 +16,8 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
item.rate = flt(item.rate_with_margin , precision("rate", item));
if(item.discount_percentage){
var discount_value = flt(item.rate_with_margin) * flt(item.discount_percentage) / 100;
item.rate = flt((item.rate_with_margin) - (discount_value), precision('rate', item));
item.discount_amount = flt(item.rate_with_margin) * flt(item.discount_percentage) / 100;
item.rate = flt((item.rate_with_margin) - (item.discount_amount), precision('rate', item));
}
},

View File

@ -790,6 +790,38 @@
"unique": 0,
"width": "100px"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "discount_percentage",
"fieldname": "discount_amount",
"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": "Discount Amount",
"length": 0,
"no_copy": 0,
"options": "currency",
"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,
@ -1766,7 +1798,7 @@
"istable": 1,
"max_attachments": 0,
"menu_index": 0,
"modified": "2017-12-14 09:39:17.180709",
"modified": "2018-08-06 05:18:38.135668",
"modified_by": "Administrator",
"module": "Selling",
"name": "Quotation Item",

View File

@ -820,6 +820,38 @@
"unique": 0,
"width": "70px"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "discount_percentage",
"fieldname": "discount_amount",
"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": "Discount Amount",
"length": 0,
"no_copy": 0,
"options": "currency",
"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,
@ -2146,7 +2178,7 @@
"istable": 1,
"max_attachments": 0,
"menu_index": 0,
"modified": "2017-11-30 14:05:39.173834",
"modified": "2018-08-06 05:17:51.297862",
"modified_by": "Administrator",
"module": "Selling",
"name": "Sales Order Item",

View File

@ -145,6 +145,15 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({
this.set_gross_profit(item);
},
discount_amount: function(doc, cdt, cdn) {
var item = frappe.get_doc(cdt, cdn);
if(!item.price_list_rate) {
item.discount_amount = 0.0;
} else {
this.price_list_rate(doc, cdt, cdn);
}
},
commission_rate: function() {
this.calculate_commission();
refresh_field("total_commission");

View File

@ -138,6 +138,9 @@ def get_item_for_list_in_html(context):
if (context.get("website_image") or "").startswith("files/"):
context["website_image"] = "/" + urllib.quote(context["website_image"])
context["show_availability_status"] = cint(frappe.db.get_single_value('Products Settings',
'show_availability_status'))
products_template = 'templates/includes/products_as_grid.html'
if cint(frappe.db.get_single_value('Products Settings', 'products_as_list')):
products_template = 'templates/includes/products_as_list.html'

View File

@ -819,6 +819,38 @@
"unique": 0,
"width": "100px"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "discount_percentage",
"fieldname": "discount_amount",
"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": "Discount Amount",
"length": 0,
"no_copy": 0,
"options": "currency",
"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,
@ -2139,7 +2171,7 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2017-11-30 14:07:12.217563",
"modified": "2018-08-06 05:18:26.132899",
"modified_by": "Administrator",
"module": "Stock",
"name": "Delivery Note Item",

View File

@ -759,6 +759,38 @@
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "discount_percentage",
"fieldname": "discount_amount",
"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": "Discount Amount",
"length": 0,
"no_copy": 0,
"options": "currency",
"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,
@ -2290,7 +2322,7 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2017-12-06 13:50:08.201145",
"modified": "2018-08-06 05:16:48.590787",
"modified_by": "Administrator",
"module": "Stock",
"name": "Purchase Receipt Item",

View File

@ -82,6 +82,7 @@ class StockEntry(StockController):
if self.purchase_order and self.purpose == "Subcontract":
self.update_purchase_order_supplied_items()
self.make_gl_entries_on_cancel()
self.update_cost_in_project()
def validate_purpose(self):
valid_purposes = ["Material Issue", "Material Receipt", "Material Transfer", "Material Transfer for Manufacture",
@ -107,8 +108,8 @@ class StockEntry(StockController):
se.docstatus = 1 and se.project = %s and sed.parent = se.name
and (sed.t_warehouse is null or sed.t_warehouse = '')""", self.project, as_list=1)
if amount:
frappe.db.set_value('Project', self.project, 'total_consumed_material_cost', amount[0][0])
amount = amount[0][0] if amount else 0
frappe.db.set_value('Project', self.project, 'total_consumed_material_cost', amount)
def validate_item(self):
stock_items = self.get_stock_items()

View File

@ -139,7 +139,7 @@ class Warehouse(NestedSet):
@frappe.whitelist()
def get_children(doctype, parent=None, company=None, is_root=False):
from erpnext.stock.utils import get_stock_value_on
from erpnext.stock.utils import get_stock_value_from_bin
if is_root:
parent = ""
@ -154,7 +154,7 @@ def get_children(doctype, parent=None, company=None, is_root=False):
# return warehouses
for wh in warehouses:
wh["balance"] = get_stock_value_on(warehouse=wh.value)
wh["balance"] = get_stock_value_from_bin(warehouse=wh.value)
return warehouses
@frappe.whitelist()
@ -170,4 +170,4 @@ def add_node():
@frappe.whitelist()
def convert_to_group_or_ledger():
args = frappe.form_dict
return frappe.get_doc("Warehouse", args.docname).convert_to_group_or_ledger()
return frappe.get_doc("Warehouse", args.docname).convert_to_group_or_ledger()

View File

@ -481,8 +481,8 @@ def get_projected_qty(item_code, warehouse):
@frappe.whitelist()
def get_bin_details(item_code, warehouse):
return frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse},
["projected_qty", "actual_qty"], as_dict=True) \
or {"projected_qty": 0, "actual_qty": 0}
["projected_qty", "actual_qty", "reserved_qty"], as_dict=True) \
or {"projected_qty": 0, "actual_qty": 0, "reserved_qty": 0}
@frappe.whitelist()
def get_serial_no_details(item_code, warehouse, stock_qty, serial_no):

View File

@ -9,21 +9,45 @@ from frappe.utils import flt, cstr, nowdate, nowtime
class InvalidWarehouseCompany(frappe.ValidationError): pass
def get_stock_value_from_bin(warehouse=None, item_code=None):
values = {}
conditions = ""
if warehouse:
conditions += """ and warehouse in (
select w2.name from `tabWarehouse` w1
join `tabWarehouse` w2 on
w1.name = %(warehouse)s
and w2.lft between w1.lft and w1.rgt
) """
values['warehouse'] = warehouse
if item_code:
conditions += " and item_code = %(item_code)s"
values['item_code'] = item_code
query = "select sum(stock_value) from `tabBin` where 1 = 1 %s" % conditions
stock_value = frappe.db.sql(query, values)
return stock_value
def get_stock_value_on(warehouse=None, posting_date=None, item_code=None):
if not posting_date: posting_date = nowdate()
values, condition = [posting_date], ""
if warehouse:
lft, rgt, is_group = frappe.db.get_value("Warehouse", warehouse, ["lft", "rgt", "is_group"])
if is_group:
values.extend([lft, rgt])
condition += "and exists (\
select name from `tabWarehouse` wh where wh.name = sle.warehouse\
and wh.lft >= %s and wh.rgt <= %s)"
else:
values.append(warehouse)
condition += " AND warehouse = %s"
@ -43,7 +67,7 @@ def get_stock_value_on(warehouse=None, posting_date=None, item_code=None):
for sle in stock_ledger_entries:
if not sle_map.has_key((sle.item_code, sle.warehouse)):
sle_map[(sle.item_code, sle.warehouse)] = flt(sle.stock_value)
return sum(sle_map.values())
@frappe.whitelist()
@ -73,17 +97,17 @@ def get_latest_stock_qty(item_code, warehouse=None):
values, condition = [item_code], ""
if warehouse:
lft, rgt, is_group = frappe.db.get_value("Warehouse", warehouse, ["lft", "rgt", "is_group"])
if is_group:
values.extend([lft, rgt])
condition += "and exists (\
select name from `tabWarehouse` wh where wh.name = tabBin.warehouse\
and wh.lft >= %s and wh.rgt <= %s)"
else:
values.append(warehouse)
condition += " AND warehouse = %s"
actual_qty = frappe.db.sql("""select sum(actual_qty) from tabBin
where item_code=%s {0}""".format(condition), values)[0][0]
@ -219,4 +243,3 @@ def validate_warehouse_company(warehouse, company):
def is_group_warehouse(warehouse):
if frappe.db.get_value("Warehouse", warehouse, "is_group"):
frappe.throw(_("Group node warehouse is not allowed to select for transactions"))

View File

@ -5,10 +5,12 @@
<div class="product-image-img">
{{ product_image_square(thumbnail or website_image) }}
<div class="product-text" itemprop="name">{{ item_name }}</div>
{% if in_stock or not is_stock_item %}
<div style='color: green'> <i class='fa fa-check'></i> {{ _("In stock") }}</div>
{% else %}
<div style='color: red'> <i class='fa fa-close'></i> {{ _("Not in stock") }}</div>
{% if show_availability_status %}
{% if in_stock or not is_stock_item %}
<div style='color: green'> <i class='fa fa-check'></i> {{ _("In stock") }}</div>
{% else %}
<div style='color: red'> <i class='fa fa-close'></i> {{ _("Not in stock") }}</div>
{% endif %}
{% endif %}
</div>
</div>