diff --git a/accounts/page/general_ledger/general_ledger.js b/accounts/page/general_ledger/general_ledger.js index cbe88fb4e2f..eca9dea97af 100644 --- a/accounts/page/general_ledger/general_ledger.js +++ b/accounts/page/general_ledger/general_ledger.js @@ -21,179 +21,218 @@ wn.pages['general-ledger'].onload = function(wrapper) { single_column: true }); - erpnext.general_ledger = new wn.views.GridReport({ - title: "General Ledger", - page: wrapper, - parent: $(wrapper).find('.layout-main'), - appframe: wrapper.appframe, - doctypes: ["Company", "Account", "GL Entry", "Cost Center"], - - setup_columns: function() { - this.columns = [ - {id: "posting_date", name: "Posting Date", field: "posting_date", width: 100, - formatter: this.date_formatter}, - {id: "account", name: "Account", field: "account", width: 240, - link_formatter: { - filter_input: "account", - open_btn: true, - }}, - {id: "debit", name: "Debit", field: "debit", width: 100, - formatter: this.currency_formatter}, - {id: "credit", name: "Credit", field: "credit", width: 100, - formatter: this.currency_formatter}, - {id: "voucher_type", name: "Voucher Type", field: "voucher_type", width: 120}, - {id: "voucher_no", name: "Voucher No", field: "voucher_no", width: 160, - link_formatter: { - filter_input: "voucher_no", - open_btn: true, - doctype: "dataContext.voucher_type" - }}, - {id: "remarks", name: "Remarks", field: "remarks", width: 200, - formatter: this.text_formatter}, - - ]; - }, - filters: [ - {fieldtype:"Select", label: "Company", link:"Company", default_value: "Select Company...", - filter: function(val, item, opts) { - return item.company == val || val == opts.default_value; - }}, - {fieldtype:"Select", label: "Account", link:"Account", default_value: "Select Account...", - filter: function(val, item, opts, me) { - if(val == opts.default_value) { - return true; - } else { - // true if GL Entry belongs to selected - // account ledger or group - return me.is_child_account(val, item.account); - } - }}, - {fieldtype:"Data", label: "Voucher No", - filter: function(val, item, opts) { - if(!val) return true; - return (item.voucher_no && item.voucher_no.indexOf(val)!=-1); - }}, - {fieldtype:"Date", label: "From Date", filter: function(val, item) { - return dateutil.str_to_obj(val) <= dateutil.str_to_obj(item.posting_date); - }}, - {fieldtype:"Label", label: "To"}, - {fieldtype:"Date", label: "To Date", filter: function(val, item) { - return dateutil.str_to_obj(val) >= dateutil.str_to_obj(item.posting_date); - }}, - {fieldtype:"Button", label: "Refresh", icon:"icon-refresh icon-white", cssClass:"btn-info"}, - {fieldtype:"Button", label: "Reset Filters"} - ], - is_child_account: function(account, item_account) { - account = this.account_by_name[account]; - item_account = this.account_by_name[item_account]; - - return (item_account.lft >= account.lft && item_account.rgt <= account.rgt) - }, - prepare_data: function() { - // add Opening, Closing, Totals rows - // if filtered by account and / or voucher - var data = wn.report_dump.data["GL Entry"]; - var out = []; - - if(!this.account_by_name) - this.account_by_name = this.make_name_map(wn.report_dump.data["Account"]); - - var me = this; - - var from_date = dateutil.str_to_obj(this.from_date); - var to_date = dateutil.str_to_obj(this.to_date); - - if(to_date < from_date) { - msgprint("From Date must be before To Date"); - return; - } - - var opening = { - account: "Opening", debit: 0.0, credit: 0.0, - id:"_opening", _show: true, _style: "font-weight: bold" - } - var totals = { - account: "Totals", debit: 0.0, credit: 0.0, - id:"_totals", _show: true, _style: "font-weight: bold" - } - - $.each(data, function(i, item) { - if((!me.is_default("account") ? me.is_child_account(me.account, item.account) : true) && - (me.voucher_no ? item.voucher_no==me.voucher_no : true)) { - - var date = dateutil.str_to_obj(item.posting_date); - - if(date < from_date) { - opening.debit += item.debit; - opening.credit += item.credit; - } else if(date <= to_date) { - totals.debit += item.debit; - totals.credit += item.credit; - } - - if(me.apply_filters(item)) { - out.push(item); - } - } - }) - - var closing = { - account: "Closing (Opening + Totals)", debit: opening.debit + totals.debit, - credit: opening.credit + totals.credit, - id:"_closing", _show: true, _style: "font-weight: bold" - } - - - if(!me.is_default("account")) { - if(me.account_by_name[me.account].debit_or_credit == "Debit") { - opening.debit -= opening.credit; opening.credit = 0; - closing.debit -= closing.credit; closing.credit = 0; - } else { - opening.credit -= opening.debit; opening.debit = 0; - closing.credit -= closing.debit; closing.debit = 0; - } - var out = [opening].concat(out).concat([totals, closing]); - } else { - var out = out.concat([totals]); - } - - this.data = out; - }, - get_plot_data: function() { - var data = []; - var me = this; - if(me.is_default("account") || me.voucher_no) return false; - var debit_or_credit = me.account_by_name[me.account].debit_or_credit; - var balance = debit_or_credit=="Debit" ? me.data[0].debit : me.data[0].credit; - data.push({ - label: me.account, - data: [[dateutil.str_to_obj(me.from_date).getTime(), balance]] - .concat($.map(me.data, function(col, idx) { - if (col.posting_date) { - var diff = (debit_or_credit == "Debit" ? 1 : -1) * (flt(col.debit) - flt(col.credit)); - balance += diff; - return [[dateutil.str_to_obj(col.posting_date).getTime(), balance - diff], - [dateutil.str_to_obj(col.posting_date).getTime(), balance]] - } - return null; - })).concat([ - // closing - [dateutil.str_to_obj(me.to_date).getTime(), balance] - ]), - points: {show: true}, - lines: {show: true, fill: true}, - }); - return data; - }, - get_plot_options: function() { - return { - grid: { hoverable: true, clickable: true }, - xaxis: { mode: "time", - min: dateutil.str_to_obj(this.from_date).getTime(), - max: dateutil.str_to_obj(this.to_date).getTime() } - } - }, - }); + erpnext.general_ledger = new erpnext.GeneralLedger(wrapper); } +erpnext.GeneralLedger = wn.views.GridReport.extend({ + init: function(wrapper) { + this._super({ + title: "General Ledger", + page: wrapper, + parent: $(wrapper).find('.layout-main'), + appframe: wrapper.appframe, + doctypes: ["Company", "Account", "GL Entry", "Cost Center"], + }); + }, + setup_columns: function() { + this.columns = [ + {id: "posting_date", name: "Posting Date", field: "posting_date", width: 100, + formatter: this.date_formatter}, + {id: "account", name: "Account", field: "account", width: 240, + link_formatter: { + filter_input: "account", + open_btn: true, + }}, + {id: "debit", name: "Debit", field: "debit", width: 100, + formatter: this.currency_formatter}, + {id: "credit", name: "Credit", field: "credit", width: 100, + formatter: this.currency_formatter}, + {id: "voucher_type", name: "Voucher Type", field: "voucher_type", width: 120}, + {id: "voucher_no", name: "Voucher No", field: "voucher_no", width: 160, + link_formatter: { + filter_input: "voucher_no", + open_btn: true, + doctype: "dataContext.voucher_type" + }}, + {id: "remarks", name: "Remarks", field: "remarks", width: 200, + formatter: this.text_formatter}, + + ]; + }, + filters: [ + {fieldtype:"Select", label: "Company", link:"Company", default_value: "Select Company...", + filter: function(val, item, opts) { + return item.company == val || val == opts.default_value; + }}, + {fieldtype:"Select", label: "Account", link:"Account", default_value: "Select Account...", + filter: function(val, item, opts, me) { + if(val == opts.default_value) { + return true; + } else { + // true if GL Entry belongs to selected + // account ledger or group + return me.is_child_account(val, item.account); + } + }}, + {fieldtype:"Data", label: "Voucher No", + filter: function(val, item, opts) { + if(!val) return true; + return (item.voucher_no && item.voucher_no.indexOf(val)!=-1); + }}, + {fieldtype:"Date", label: "From Date", filter: function(val, item) { + return dateutil.str_to_obj(val) <= dateutil.str_to_obj(item.posting_date); + }}, + {fieldtype:"Label", label: "To"}, + {fieldtype:"Date", label: "To Date", filter: function(val, item) { + return dateutil.str_to_obj(val) >= dateutil.str_to_obj(item.posting_date); + }}, + {fieldtype:"Button", label: "Refresh", icon:"icon-refresh icon-white", cssClass:"btn-info"}, + {fieldtype:"Button", label: "Reset Filters"} + ], + setup_filters: function() { + this._super(); + var me = this; + + // filter accounts options by company + var accounts_by_company = this.make_accounts_by_company(); + this.filter_inputs.company && this.filter_inputs.company.change(function() { + var $filter = me.filter_inputs.account; + var company = $(this).val(); + var default_company = me.filter_inputs.company.get(0).opts.default_value; + $filter.empty().add_options([$filter.get(0).opts.default_value].concat( + $.map(wn.report_dump.data["Account"], function(ac) { + return (accounts_by_company[company].indexOf(ac.name)!=-1 || + company===default_company) ? ac.name : null; + }))); + me.filter_inputs.refresh.click(); + }); + + this.filter_inputs.account && this.filter_inputs.account.change(function() { + me.filter_inputs.refresh.click(); + }); + }, + init_filter_values: function() { + this._super(); + this.filter_inputs.company.change(); + }, + make_accounts_by_company: function() { + var accounts_by_company = {}; + var me = this; + $.each(wn.report_dump.data["Account"], function(i, ac) { + if(!accounts_by_company[ac.company]) accounts_by_company[ac.company] = []; + accounts_by_company[ac.company].push(ac.name); + }); + return accounts_by_company; + }, + is_child_account: function(account, item_account) { + account = this.account_by_name[account]; + item_account = this.account_by_name[item_account]; + + return (item_account.lft >= account.lft && item_account.rgt <= account.rgt) + }, + prepare_data: function() { + // add Opening, Closing, Totals rows + // if filtered by account and / or voucher + var data = wn.report_dump.data["GL Entry"]; + var out = []; + + if(!this.account_by_name) + this.account_by_name = this.make_name_map(wn.report_dump.data["Account"]); + + var me = this; + + var from_date = dateutil.str_to_obj(this.from_date); + var to_date = dateutil.str_to_obj(this.to_date); + + if(to_date < from_date) { + msgprint("From Date must be before To Date"); + return; + } + + var opening = { + account: "Opening", debit: 0.0, credit: 0.0, + id:"_opening", _show: true, _style: "font-weight: bold" + } + var totals = { + account: "Totals", debit: 0.0, credit: 0.0, + id:"_totals", _show: true, _style: "font-weight: bold" + } + + $.each(data, function(i, item) { + if((me.is_default("company") ? true : me.apply_filter(item, "company")) && + (!me.is_default("account") ? me.is_child_account(me.account, item.account) + : true) && (me.voucher_no ? item.voucher_no==me.voucher_no : true)) { + var date = dateutil.str_to_obj(item.posting_date); + + if(date < from_date) { + opening.debit += item.debit; + opening.credit += item.credit; + } else if(date <= to_date) { + totals.debit += item.debit; + totals.credit += item.credit; + } + + if(me.apply_filters(item)) { + out.push(item); + } + } + }) + + var closing = { + account: "Closing (Opening + Totals)", debit: opening.debit + totals.debit, + credit: opening.credit + totals.credit, + id:"_closing", _show: true, _style: "font-weight: bold" + } + + + if(!me.is_default("account")) { + if(me.account_by_name[me.account].debit_or_credit == "Debit") { + opening.debit -= opening.credit; opening.credit = 0; + closing.debit -= closing.credit; closing.credit = 0; + } else { + opening.credit -= opening.debit; opening.debit = 0; + closing.credit -= closing.debit; closing.debit = 0; + } + var out = [opening].concat(out).concat([totals, closing]); + } else { + var out = out.concat([totals]); + } + + this.data = out; + }, + get_plot_data: function() { + var data = []; + var me = this; + if(me.is_default("account") || me.voucher_no) return false; + var debit_or_credit = me.account_by_name[me.account].debit_or_credit; + var balance = debit_or_credit=="Debit" ? me.data[0].debit : me.data[0].credit; + data.push({ + label: me.account, + data: [[dateutil.str_to_obj(me.from_date).getTime(), balance]] + .concat($.map(me.data, function(col, idx) { + if (col.posting_date) { + var diff = (debit_or_credit == "Debit" ? 1 : -1) * (flt(col.debit) - flt(col.credit)); + balance += diff; + return [[dateutil.str_to_obj(col.posting_date).getTime(), balance - diff], + [dateutil.str_to_obj(col.posting_date).getTime(), balance]] + } + return null; + })).concat([ + // closing + [dateutil.str_to_obj(me.to_date).getTime(), balance] + ]), + points: {show: true}, + lines: {show: true, fill: true}, + }); + return data; + }, + get_plot_options: function() { + return { + grid: { hoverable: true, clickable: true }, + xaxis: { mode: "time", + min: dateutil.str_to_obj(this.from_date).getTime(), + max: dateutil.str_to_obj(this.to_date).getTime() } + } + }, +}); \ No newline at end of file diff --git a/erpnext/patches/september_2012/event_permission.py b/erpnext/patches/september_2012/event_permission.py new file mode 100644 index 00000000000..2f1a848950c --- /dev/null +++ b/erpnext/patches/september_2012/event_permission.py @@ -0,0 +1,15 @@ +import webnotes +from webnotes.model.code import get_obj +from webnotes.model.doc import addchild + +def execute(): + existing = webnotes.conn.sql("""select name from `tabDocPerm` + where permlevel=0 and parent='Event' and role='System Manager' + and cancel=1""") + if not existing: + ev_obj = get_obj("DocType", "Event", with_children=1) + ch = addchild(ev_obj.doc, "permissions", "DocPerm") + ch.permlevel = 0 + ch.role = 'System Manager' + ch.read = ch.write = ch.create = ch.cancel = 1 + ch.save() \ No newline at end of file diff --git a/erpnext/patches/september_2012/reload_gross_profit.py b/erpnext/patches/september_2012/reload_gross_profit.py new file mode 100644 index 00000000000..0a3f9efed7d --- /dev/null +++ b/erpnext/patches/september_2012/reload_gross_profit.py @@ -0,0 +1,21 @@ +# ERPNext - web based ERP (http://erpnext.com) +# Copyright (C) 2012 Web Notes Technologies Pvt Ltd +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from __future__ import unicode_literals +def execute(): + # reload gross profit report + from webnotes.modules import reload_doc + reload_doc('selling', 'search_criteria', 'gross_profit') \ No newline at end of file diff --git a/erpnext/patches/september_2012/repost_stock.py b/erpnext/patches/september_2012/repost_stock.py new file mode 100644 index 00000000000..c6b6ce39f77 --- /dev/null +++ b/erpnext/patches/september_2012/repost_stock.py @@ -0,0 +1,31 @@ +# ERPNext - web based ERP (http://erpnext.com) +# Copyright (C) 2012 Web Notes Technologies Pvt Ltd +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from __future__ import unicode_literals +def execute(): + import webnotes + from webnotes.model.code import get_obj + bin = webnotes.conn.sql("select name from `tabBin`") + i=0 + for d in bin: + try: + get_obj('Bin', d[0]).update_entries_after('2000-01-01', '12:05') + except: + pass + i += 1 + if i%100 == 0: + webnotes.conn.sql("commit") + webnotes.conn.sql("start transaction") \ No newline at end of file diff --git a/patches/patch_list.py b/patches/patch_list.py index d0ec56d6eca..e14be4b8146 100644 --- a/patches/patch_list.py +++ b/patches/patch_list.py @@ -588,4 +588,16 @@ patch_list = [ 'patch_module': 'patches.september_2012', 'patch_file': 'plot_patch', }, + { + 'patch_module': 'patches.september_2012', + 'patch_file': 'event_permission', + }, + { + 'patch_module': 'patches.september_2012', + 'patch_file': 'repost_stock', + }, + { + 'patch_module': 'patches.september_2012', + 'patch_file': 'reload_gross_profit', + }, ] diff --git a/selling/search_criteria/gross_profit/gross_profit.py b/selling/search_criteria/gross_profit/gross_profit.py index 97821d3aecb..ba3425eccc3 100644 --- a/selling/search_criteria/gross_profit/gross_profit.py +++ b/selling/search_criteria/gross_profit/gross_profit.py @@ -8,15 +8,17 @@ # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with this program. If not, see . +# along with this program. If not, see . # Add Columns # ------------ from __future__ import unicode_literals +from webnotes.utils import flt + colnames[colnames.index('Rate*')] = 'Rate' col_idx['Rate'] = col_idx['Rate*'] col_idx.pop('Rate*') @@ -24,55 +26,66 @@ colnames[colnames.index('Amount*')] = 'Amount' col_idx['Amount'] = col_idx['Amount*'] col_idx.pop('Amount*') -columns = [['Valuation Rate','Currency','150px',''], - ['Valuation Amount','Currency','150px',''], - ['Gross Profit (%)','Currrency','150px',''], - ['Gross Profit','Currency','150px','']] +columns = [ + ['Purchase Cost','Currency','150px',''], + ['Gross Profit','Currency','150px',''], + ['Gross Profit (%)','Currrency','150px',''] +] for c in columns: - colnames.append(c[0]) - coltypes.append(c[1]) - colwidths.append(c[2]) - coloptions.append(c[3]) - col_idx[c[0]] = len(colnames)-1 + colnames.append(c[0]) + coltypes.append(c[1]) + colwidths.append(c[2]) + coloptions.append(c[3]) + col_idx[c[0]] = len(colnames)-1 -out, tot_amount, tot_val_amount, tot_gross_profit = [], 0, 0, 0 +sle = sql(""" + select + actual_qty, incoming_rate, voucher_no, item_code, warehouse + from + `tabStock Ledger Entry` + where + voucher_type = 'Delivery Note' + and ifnull(is_cancelled, 'No') = 'No' + order by posting_date desc, posting_time desc, name desc +""", as_dict=1) +def get_purchase_cost(dn, item, wh, qty): + from webnotes.utils import flt + global sle + purchase_cost = 0 + packing_items = sql("select item_code, qty from `tabSales BOM Item` where parent = %s", item) + if packing_items: + packing_items = [[t[0], flt(t[1])*qty] for t in packing_items] + else: + packing_items = [[item, qty]] + for d in sle: + if d['voucher_no'] == dn and [d['item_code'], flt(abs(d['actual_qty']))] in packing_items: + purchase_cost += flt(d['incoming_rate'])*flt(abs(d['actual_qty'])) + return purchase_cost + +out, tot_amount, tot_pur_cost = [], 0, 0 for r in res: - tot_val_rate = 0 - packing_list_items = sql("select item_code, warehouse, qty from `tabDelivery Note Packing Item` where parent = %s and parent_item = %s", (r[col_idx['ID']], r[col_idx['Item Code']])) - for d in packing_list_items: - if d[1]: - val_rate = sql("select valuation_rate from `tabStock Ledger Entry` where item_code = %s and warehouse = %s and voucher_type = 'Delivery Note' and voucher_no = %s and is_cancelled = 'No'", (d[0], d[1], r[col_idx['ID']])) - val_rate = val_rate and val_rate[0][0] or 0 - if r[col_idx['Quantity']]: tot_val_rate += (flt(val_rate) * flt(d[2]) / flt(r[col_idx['Quantity']])) - else: tot_val_rate = 0 - - r.append(fmt_money(tot_val_rate)) - - val_amount = flt(tot_val_rate) * flt(r[col_idx['Quantity']]) - r.append(fmt_money(val_amount)) - - gp = flt(r[col_idx['Amount']]) - flt(val_amount) - - if val_amount: gp_percent = gp * 100 / val_amount - else: gp_percent = gp - - r.append(fmt_money(gp_percent)) - r.append(fmt_money(gp)) - out.append(r) - - tot_gross_profit += flt(gp) - tot_amount += flt(r[col_idx['Amount']]) - tot_val_amount += flt(val_amount) + purchase_cost = get_purchase_cost(r[col_idx['ID']], r[col_idx['Item Code']], \ + r[col_idx['Warehouse']], r[col_idx['Quantity']]) + r.append(purchase_cost) + + gp = flt(r[col_idx['Amount']]) - flt(purchase_cost) + gp_percent = r[col_idx['Amount']] and purchase_cost and \ + round((gp*100/flt(r[col_idx['Amount']])), 2) or 0 + r.append(fmt_money(gp)) + r.append(fmt_money(gp_percent)) + out.append(r) + + tot_amount += flt(r[col_idx['Amount']]) + tot_pur_cost += flt(purchase_cost) # Add Total Row -# -------------- l_row = ['' for i in range(len(colnames))] -l_row[col_idx['Quantity']] = 'TOTALS' +l_row[col_idx['Project Name']] = 'TOTALS' l_row[col_idx['Amount']] = fmt_money(tot_amount) -l_row[col_idx['Valuation Amount']] = fmt_money(tot_val_amount) -if tot_val_amount: l_row[col_idx['Gross Profit (%)']] = fmt_money((tot_amount - tot_val_amount) * 100 / tot_val_amount) -else: l_row[col_idx['Gross Profit (%)']] = fmt_money(tot_amount) -l_row[col_idx['Gross Profit']] = fmt_money(tot_gross_profit) +l_row[col_idx['Purchase Cost']] = fmt_money(tot_pur_cost) +l_row[col_idx['Gross Profit']] = fmt_money(flt(tot_amount) - flt(tot_pur_cost)) +l_row[col_idx['Gross Profit (%)']] = tot_amount and \ + round((flt(tot_amount) - flt(tot_pur_cost))*100 / flt(tot_amount), 2) out.append(l_row) \ No newline at end of file diff --git a/selling/search_criteria/gross_profit/gross_profit.txt b/selling/search_criteria/gross_profit/gross_profit.txt index b98ced61981..1f9cbc7e109 100644 --- a/selling/search_criteria/gross_profit/gross_profit.txt +++ b/selling/search_criteria/gross_profit/gross_profit.txt @@ -3,23 +3,23 @@ # These values are common in all dictionaries { - 'creation': '2012-04-03 12:49:51', - 'docstatus': 0, - 'modified': '2012-04-03 12:49:51', - 'modified_by': u'Administrator', - 'owner': u'Administrator' + u'creation': '2012-05-14 18:22:18', + u'docstatus': 0, + u'modified': '2012-09-24 14:11:39', + u'modified_by': u'Administrator', + u'owner': u'Administrator' }, # These values are common for all Search Criteria { - 'columns': u'Delivery Note\x01ID,Delivery Note\x01Posting Date,Delivery Note\x01Posting Time,Delivery Note Item\x01Item Code,Delivery Note Item\x01Item Name,Delivery Note Item\x01Description,Delivery Note\x01Project Name,Delivery Note Item\x01Quantity,Delivery Note Item\x01Rate*,Delivery Note Item\x01Amount*', + 'columns': u'Delivery Note\x01ID,Delivery Note\x01Posting Date,Delivery Note\x01Posting Time,Delivery Note Item\x01Item Code,Delivery Note Item\x01Item Name,Delivery Note Item\x01Description,Delivery Note Item\x01Warehouse,Delivery Note\x01Project Name,Delivery Note Item\x01Quantity,Delivery Note Item\x01Rate*,Delivery Note Item\x01Amount*', 'criteria_name': u'Gross Profit', 'description': u'Invoice wise', 'doc_type': u'Delivery Note Item', - 'doctype': 'Search Criteria', - 'filters': u"{'Delivery Note\x01Submitted':1,'Delivery Note\x01Status':'','Delivery Note\x01Fiscal Year':''}", + u'doctype': u'Search Criteria', + 'filters': u'{"Delivery Note\\u0001Submitted":1,"Delivery Note\\u0001Status":[],"Delivery Note\\u0001Fiscal Year":[]}', 'module': u'Selling', - 'name': '__common__', + u'name': u'__common__', 'page_len': 50, 'parent_doc_type': u'Delivery Note', 'sort_by': u'`tabDelivery Note`.`name`', @@ -29,7 +29,7 @@ # Search Criteria, gross_profit { - 'doctype': 'Search Criteria', - 'name': u'gross_profit' + u'doctype': u'Search Criteria', + u'name': u'gross_profit' } ] \ No newline at end of file diff --git a/startup/report_data_map.py b/startup/report_data_map.py index b2be5665656..a098e33ef3a 100644 --- a/startup/report_data_map.py +++ b/startup/report_data_map.py @@ -31,8 +31,8 @@ data_map = { # Accounts "Account": { - "columns": ["name", "parent_account", "lft", "rgt", "debit_or_credit", "is_pl_account", - "company"], + "columns": ["name", "parent_account", "lft", "rgt", "debit_or_credit", + "is_pl_account", "company"], "order_by": "lft" }, "Cost Center": { diff --git a/stock/doctype/bin/bin.py b/stock/doctype/bin/bin.py index bff7abb03e2..648d5812319 100644 --- a/stock/doctype/bin/bin.py +++ b/stock/doctype/bin/bin.py @@ -40,10 +40,9 @@ class DocType: self.doc = doc self.doclist = doclist - # ------------- - # stock update - # ------------- - def update_stock(self, actual_qty=0, reserved_qty=0, ordered_qty=0, indented_qty=0, planned_qty=0, dt=None, sle_id='', posting_time='', serial_no = '', is_cancelled = 'No',doc_type='',doc_name='',is_amended='No'): + def update_stock(self, actual_qty=0, reserved_qty=0, ordered_qty=0, indented_qty=0, \ + planned_qty=0, dt=None, sle_id='', posting_time='', serial_no = '', \ + is_cancelled = 'No',doc_type='',doc_name='',is_amended='No'): if not dt: dt = nowdate() @@ -73,13 +72,13 @@ class DocType: """ if sql("select name from `tabItem` where ifnull(has_serial_no, 'No') = 'Yes' and name = '%s'" % self.doc.item_code): sr_count = sql("""select count(name) from `tabSerial No` - where item_code = '%s' and warehouse = '%s' - and status ='In Store' and docstatus != 2 - """ % (self.doc.item_code, self.doc.warehouse))[0][0] + where item_code = '%s' and warehouse = '%s' + and status ='In Store' and docstatus != 2 + """ % (self.doc.item_code, self.doc.warehouse))[0][0] if sr_count != self.doc.actual_qty: - msg = """Actual Qty(%s) in Bin is mismatched with total number(%s) of serial no in store - for item: '%s' and warehouse: '%s'""" % \ + msg = """Actual Qty(%s) in Bin is mismatched with total number(%s) + of serial no in store for item: %s and warehouse: %s""" % \ (self.doc.actual_qty, sr_count, self.doc.item_code, self.doc.warehouse) msgprint(msg, raise_exception=1) @@ -119,7 +118,6 @@ class DocType: return sle and sle[0] or {} - def get_sle_prev_timebucket(self, posting_date = '1900-01-01', posting_time = '12:00'): """get previous stock ledger entry before current time-bucket""" # get the last sle before the current time-bucket, so that all values @@ -139,8 +137,6 @@ class DocType: return sle and sle[0] or {} - - #------------------------------------------------------------- def validate_negative_stock(self, cqty, s): """ validate negative stock for entries current datetime onwards @@ -157,33 +153,32 @@ class DocType: s['posting_date'], s['posting_time'], s['voucher_type'], s['voucher_no']), \ raise_exception=1) - - # ------------------------------------ - def get_serialized_inventory_values(self, val_rate, in_rate, opening_qty, actual_qty, is_cancelled, serial_nos): + def get_serialized_inventory_values(self, val_rate, in_rate, opening_qty, \ + actual_qty, is_cancelled, serial_nos): """ get serialized inventory values """ if flt(in_rate) < 0: # wrong incoming rate in_rate = val_rate - elif flt(in_rate) == 0: # In case of delivery/stock issue, get average purchase rate of serial nos of current entry - in_rate = flt(sql("select ifnull(avg(purchase_rate), 0) from `tabSerial No` where name in (%s)" % (serial_nos))[0][0]) + elif flt(in_rate) == 0 or flt(actual_qty) < 0: + # In case of delivery/stock issue, get average purchase rate + # of serial nos of current entry + in_rate = flt(sql("""select ifnull(avg(purchase_rate), 0) + from `tabSerial No` where name in (%s)""" % (serial_nos))[0][0]) if in_rate and val_rate == 0: # First entry val_rate = in_rate # val_rate is same as previous entry if val_rate is negative # Otherwise it will be calculated as per moving average - elif opening_qty + actual_qty > 0 and ((opening_qty * val_rate) + (actual_qty * in_rate)) > 0: - val_rate = ((opening_qty *val_rate) + (actual_qty * in_rate)) / (opening_qty + actual_qty) - stock_val = val_rate - return val_rate, stock_val + elif opening_qty + actual_qty > 0 and ((opening_qty * val_rate) + \ + (actual_qty * in_rate)) > 0: + val_rate = ((opening_qty *val_rate) + (actual_qty * in_rate)) / \ + (opening_qty + actual_qty) + return val_rate, in_rate - - - # ------------------------------------ - # get moving average inventory values - # ------------------------------------ def get_moving_average_inventory_values(self, val_rate, in_rate, opening_qty, actual_qty, is_cancelled): - if flt(in_rate) == 0: # In case of delivery/stock issue in_rate = 0 or wrong incoming rate + if flt(in_rate) == 0 or flt(actual_qty) < 0: + # In case of delivery/stock issue in_rate = 0 or wrong incoming rate in_rate = val_rate # val_rate is same as previous entry if : @@ -191,18 +186,15 @@ class DocType: # 2. cancelled entry # 3. val_rate is negative # Otherwise it will be calculated as per moving average - if actual_qty > 0 and (opening_qty + actual_qty) > 0 and is_cancelled == 'No' and ((opening_qty * val_rate) + (actual_qty * in_rate)) > 0: + if actual_qty > 0 and (opening_qty + actual_qty) > 0 and is_cancelled == 'No' \ + and ((opening_qty * val_rate) + (actual_qty * in_rate)) > 0: opening_qty = opening_qty > 0 and opening_qty or 0 - val_rate = ((opening_qty *val_rate) + (actual_qty * in_rate)) / (opening_qty + actual_qty) + val_rate = ((opening_qty *val_rate) + (actual_qty * in_rate)) / \ + (opening_qty + actual_qty) elif (opening_qty + actual_qty) <= 0: val_rate = 0 - stock_val = val_rate - return val_rate, stock_val + return val_rate, in_rate - - # -------------------------- - # get fifo inventory values - # -------------------------- def get_fifo_inventory_values(self, in_rate, actual_qty): # add batch to fcfs balance if actual_qty > 0: @@ -210,49 +202,54 @@ class DocType: # remove from fcfs balance else: + incoming_cost = 0 withdraw = flt(abs(actual_qty)) while withdraw: if not self.fcfs_bal: break # nothing in store batch = self.fcfs_bal[0] - + if batch[0] <= withdraw: # not enough or exactly same qty in current batch, clear batch + incoming_cost += flt(batch[1])*flt(batch[0]) withdraw -= batch[0] self.fcfs_bal.pop(0) + + else: # all from current batch + incoming_cost += flt(batch[1])*flt(withdraw) batch[0] -= withdraw withdraw = 0 + + in_rate = incoming_cost / flt(abs(actual_qty)) fcfs_val = sum([flt(d[0])*flt(d[1]) for d in self.fcfs_bal]) fcfs_qty = sum([flt(d[0]) for d in self.fcfs_bal]) val_rate = fcfs_qty and fcfs_val / fcfs_qty or 0 - return val_rate + return val_rate, in_rate - # ------------------- - # get valuation rate - # ------------------- def get_valuation_rate(self, val_method, serial_nos, val_rate, in_rate, stock_val, cqty, s): if serial_nos: - val_rate, stock_val = self.get_serialized_inventory_values(val_rate, in_rate, opening_qty = cqty, actual_qty = s['actual_qty'], is_cancelled = s['is_cancelled'], serial_nos = serial_nos) + val_rate, in_rate = self.get_serialized_inventory_values( \ + val_rate, in_rate, opening_qty = cqty, actual_qty = s['actual_qty'], \ + is_cancelled = s['is_cancelled'], serial_nos = serial_nos) elif val_method == 'Moving Average': - val_rate, stock_val = self.get_moving_average_inventory_values(val_rate, in_rate, opening_qty = cqty, actual_qty = s['actual_qty'], is_cancelled = s['is_cancelled']) + val_rate, in_rate = self.get_moving_average_inventory_values( \ + val_rate, in_rate, opening_qty = cqty, actual_qty = s['actual_qty'], \ + is_cancelled = s['is_cancelled']) elif val_method == 'FIFO': - val_rate = self.get_fifo_inventory_values(in_rate, actual_qty = s['actual_qty']) - return val_rate, stock_val + val_rate, in_rate = self.get_fifo_inventory_values(in_rate, \ + actual_qty = s['actual_qty']) + return val_rate, in_rate - - # ---------------- - # get stock value - # ---------------- - def get_stock_value(self, val_method, cqty, stock_val, serial_nos): + def get_stock_value(self, val_method, cqty, val_rate, serial_nos): if serial_nos: - stock_val = flt(stock_val) * flt(cqty) + stock_val = flt(val_rate) * flt(cqty) elif val_method == 'Moving Average': - stock_val = flt(cqty) > 0 and flt(stock_val) * flt(cqty) or 0 + stock_val = flt(cqty) > 0 and flt(val_rate) * flt(cqty) or 0 elif val_method == 'FIFO': stock_val = sum([flt(d[0])*flt(d[1]) for d in self.fcfs_bal]) return stock_val @@ -296,8 +293,10 @@ class DocType: and timestamp(posting_date, posting_time) > timestamp(%s, %s) order by timestamp(posting_date, posting_time) asc, name asc""", \ (self.doc.item_code, self.doc.warehouse, \ - prev_sle.get('posting_date','1900-01-01'), prev_sle.get('posting_time', '12:00')), as_dict = 1) - for sle in sll: + prev_sle.get('posting_date','1900-01-01'), \ + prev_sle.get('posting_time', '12:00')), as_dict = 1) + + for sle in sll: # block if stock level goes negative on any date if val_method != 'Moving Average' or flt(allow_negative_stock) == 0: self.validate_negative_stock(cqty, sle) @@ -305,36 +304,34 @@ class DocType: stock_val, in_rate = 0, sle['incoming_rate'] # IN serial_nos = sle["serial_no"] and ("'"+"', '".join(cstr(sle["serial_no"]).split('\n')) \ + "'") or '' - # Get valuation rate - val_rate, stock_val = self.get_valuation_rate(val_method, serial_nos, \ - val_rate, in_rate, stock_val, cqty, sle) - + val_rate, in_rate = self.get_valuation_rate(val_method, serial_nos, \ + val_rate, in_rate, stock_val, cqty, sle) # Qty upto the sle cqty += sle['actual_qty'] - # Stock Value upto the sle - stock_val = self.get_stock_value(val_method, cqty, stock_val, serial_nos) - - # update current sle --> will it be good to update incoming rate in sle - # for outgoing stock entry????? + stock_val = self.get_stock_value(val_method, cqty, val_rate, serial_nos) + # update current sle sql("""update `tabStock Ledger Entry` - set bin_aqat=%s, valuation_rate=%s, fcfs_stack=%s, stock_value=%s - where name=%s""", (cqty, flt(val_rate), cstr(self.fcfs_bal), stock_val, sle['name'])) + set bin_aqat=%s, valuation_rate=%s, fcfs_stack=%s, stock_value=%s, + incoming_rate = %s where name=%s""", \ + (cqty, flt(val_rate), cstr(self.fcfs_bal), stock_val, in_rate, sle['name'])) # update the bin if sll or not prev_sle: - sql("update `tabBin` set valuation_rate=%s, actual_qty=%s, stock_value = %s, projected_qty = (actual_qty + indented_qty + ordered_qty + planned_qty - reserved_qty) where name=%s", \ - (flt(val_rate), cqty, flt(stock_val), self.doc.name)) - - + sql("""update `tabBin` set valuation_rate=%s, actual_qty=%s, stock_value = %s, + projected_qty = (actual_qty + indented_qty + ordered_qty + planned_qty - + reserved_qty) where name=%s + """, (flt(val_rate), cqty, flt(stock_val), self.doc.name)) def reorder_item(self,doc_type,doc_name): """ Reorder item if stock reaches reorder level""" if get_value('Global Defaults', None, 'auto_indent'): #check if re-order is required - ret = sql("select re_order_level, item_name, description, brand, item_group, lead_time_days, min_order_qty, email_notify, re_order_qty from tabItem where name = %s", (self.doc.item_code), as_dict=1) + ret = sql("""select re_order_level, item_name, description, brand, item_group, + lead_time_days, min_order_qty, email_notify, re_order_qty + from tabItem where name = %s""", (self.doc.item_code), as_dict=1) current_qty = sql(""" select sum(t1.actual_qty) + sum(t1.indented_qty) + sum(t1.ordered_qty) -sum(t1.reserved_qty) @@ -349,17 +346,16 @@ class DocType: (flt(ret[0]['re_order_level']) > flt(current_qty[0][0])): self.create_auto_indent(ret[0], doc_type, doc_name, current_qty[0][0]) - - def create_auto_indent(self, i , doc_type, doc_name, cur_qty): """ Create indent on reaching reorder level """ - indent = Document('Purchase Request') indent.transaction_date = nowdate() indent.naming_series = 'IDT' indent.company = get_defaults()['company'] indent.fiscal_year = get_defaults()['fiscal_year'] - indent.remark = "This is an auto generated Purchase Request. It was raised because the (actual + ordered + indented - reserved) quantity reaches re-order level when %s %s was created"%(doc_type,doc_name) + indent.remark = """This is an auto generated Purchase Request. + It was raised because the (actual + ordered + indented - reserved) quantity + reaches re-order level when %s %s was created""" % (doc_type,doc_name) indent.save(1) indent_obj = get_obj('Purchase Request',indent.name,with_children=1) indent_details_child = addchild(indent_obj.doc,'indent_details','Purchase Request Item',0) @@ -377,27 +373,26 @@ class DocType: indent_obj.validate() set(indent_obj.doc,'docstatus',1) indent_obj.on_submit() - msgprint("Item: " + self.doc.item_code + " is to be re-ordered. Purchase Request %s raised. It was generated from %s %s"%(indent.name,doc_type, doc_name )) + msgprint("""Item: %s is to be re-ordered. Purchase Request %s raised. + It was generated from %s %s""" % + (self.doc.item_code, indent.name,doc_type, doc_name )) if(i['email_notify']): self.send_email_notification(doc_type,doc_name) - - def send_email_notification(self,doc_type,doc_name): """ Notify user about auto creation of indent""" from webnotes.utils.email_lib import sendmail - email_list=[d[0] for d in sql("select parent from tabUserRole where role in ('Purchase Manager','Material Manager') and parent not in ('Administrator', 'All', 'Guest')")] - msg='A Purchase Request has been raised for item %s: %s on %s '%(doc_type, doc_name, nowdate()) + email_list=[d[0] for d in sql("""select parent from tabUserRole + where role in ('Purchase Manager','Material Manager') + and parent not in ('Administrator', 'All', 'Guest')""")] + msg="""A Purchase Request has been raised + for item %s: %s on %s """ % (doc_type, doc_name, nowdate()) sendmail(email_list, subject='Auto Purchase Request Generation Notification', msg = msg) - - def validate(self): self.validate_mandatory() - - # set defaults in bin def validate_mandatory(self): qf = ['actual_qty', 'reserved_qty', 'ordered_qty', 'indented_qty'] for f in qf: diff --git a/stock/doctype/stock_reconciliation/stock_reconciliation.py b/stock/doctype/stock_reconciliation/stock_reconciliation.py index d922a11b45c..5416ff80d2c 100644 --- a/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -226,7 +226,7 @@ class DocType: def on_cancel(self): - msgprint("Cancellation of stock reconciliation is temporarily suspended. The feature will come back within 2-3 days.") + msgprint("Cancellation of stock reconciliation is temporarily suspended. The feature will come back soon.") raise Exception self.get_reconciliation_data(submit = 0) self.do_stock_reco(is_submit = -1) \ No newline at end of file diff --git a/stock/page/stock_ledger/stock_ledger.js b/stock/page/stock_ledger/stock_ledger.js index a930c23fc42..08350e6d32d 100644 --- a/stock/page/stock_ledger/stock_ledger.js +++ b/stock/page/stock_ledger/stock_ledger.js @@ -175,8 +175,8 @@ erpnext.StockLedger = erpnext.StockGridReport.extend({ if(me.item_code != me.item_code_default && !me.voucher_no) { var closing = { item_code: "On " + dateutil.str_to_user(this.to_date), - balance: (out ? out[out.length-1].balance : 0), qty: 0, - balance_value: (out ? out[out.length-1].balance_value : 0), + balance: (out.length ? out[out.length-1].balance : 0), qty: 0, + balance_value: (out.length ? out[out.length-1].balance_value : 0), id:"_closing", _show: true, _style: "font-weight: bold" }; total_out.balance_value = -total_out.balance_value;